Skip to content

refactor(sloppak): one containment guard for manifest-relative paths#1039

Merged
byrongamatos merged 1 commit into
mainfrom
refactor/sloppak-path-containment-helper
Jul 23, 2026
Merged

refactor(sloppak): one containment guard for manifest-relative paths#1039
byrongamatos merged 1 commit into
mainfrom
refactor/sloppak-path-containment-helper

Conversation

@gionnibgud

@gionnibgud gionnibgud commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

What

Pure refactor of lib/sloppak.py: seven copies of the manifest-path containment guard become one _resolve_pack_path(source_dir, rel, label) helper.

Every manifest key that names a file (original_audio, drum_tab, arrangement file, notation, song_timeline, lyrics, keys) carried its own copy of the same block — resolve, prove containment under source_dir, warn and skip on ValueError, warn and skip on OSError. That's seven chances for the next side-file to get a security check subtly wrong by copying the wrong neighbour, and the guard is the thing standing between a crafted manifest and an arbitrary file read.

No behaviour change, no new validation.

Deliberately preserved

Each of these looked like something to unify and isn't:

  • Both exception branches, with their different messages. ValueError means the path resolved outside the pack (crafted or broken manifest); OSError means it couldn't be resolved at all (symlink loop, permissions). Reading "escapes source_dir" and reading "path resolution failed" send an operator to very different places.
  • Per-call-site control flow. The helper returns Path | None and says nothing about what to do next, so the two sites that return, the one that continues, and the four that fall through to an is not None test each keep the shape they had.
  • The existence-check asymmetry. Some sites test .exists() (or .is_file()) after resolving and some don't — deliberate, since a missing optional side-file is silent while a missing arrangement skips an entry. Existence stays out of the helper entirely.

Verification

  • Full suite 2774 passed / 4 skipped, identical before and after.
  • Log output is byte-identical — the hardcoded labels become a %s argument that renders to the same text. Verified by triggering the guard for each label and diffing the rendered records. (Worth noting since the raw LogRecord.msg/args do change, which would matter to structured-log consumers; nothing in-tree reads them.)
  • The five loader-level traversal tests that previously covered five separate copies of the guard now all exercise the same function, so coverage of the guard goes up without new tests.

feedpak surface

  • This PR does not change how the app reads/writes feedpaks (manifest keys, pack files, folder layout)

Checklist

  • CHANGELOG.md [Unreleased] updated (user-visible changes) — n/a, internal refactor with no user-visible change
  • Tests added/updated for new behaviour — n/a, no new behaviour; existing suite is the check
  • Commits are DCO signed off (git commit -s)

Summary by CodeRabbit

  • Bug Fixes
    • Improved protection against unsafe or invalid file paths in song manifests.
    • Added clearer warnings when referenced files cannot be safely resolved.
    • Prevented missing or inaccessible audio, drum tabs, arrangements, notation, timelines, lyrics, and key data from interrupting song loading.

Every manifest key that names a file carried its own copy of the same
traversal guard: resolve, prove containment under source_dir, warn and
skip on ValueError, warn and skip on OSError. Seven copies —
original_audio, drum_tab, arrangement, notation, song_timeline, lyrics,
keys — which is seven chances for the next side-file to get a security
check subtly wrong by copying the wrong neighbour.

Route them all through `_resolve_pack_path(source_dir, rel, label)`.

Deliberately preserved, because each was load-bearing:

- Both exception branches, with their different messages. ValueError
  means the path resolved outside the pack (a crafted or broken
  manifest); OSError means it could not be resolved at all (symlink
  loop, permissions). They send an operator to different places.
- Per-call-site control flow. The helper returns `Path | None` and says
  nothing about what to do next, so the two sites that return, the one
  that continues, and the four that fall through to an `is not None`
  test each keep the shape they had.
- The existence-check asymmetry. Some sites test `.exists()` (or
  `.is_file()`) after resolving and some do not, which is intentional —
  a missing optional side-file is silent, a missing arrangement skips an
  entry — so existence stays out of the helper entirely.

Pure refactor: no behaviour change and no new validation. Log output is
byte-identical (the hardcoded labels become a `%s` argument rendering to
the same text). Full suite is unchanged at 2774 passed / 4 skipped
before and after, and the five loader-level traversal tests that used to
cover five separate copies of the guard now all exercise the same
function.

Signed-off-by: gionnibgud <gionnibgud@gmail.com>
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d5fbb33c-4541-4ec5-93b7-c118327a9aab

📥 Commits

Reviewing files that changed from the base of the PR and between 8297afc and f1f0b48.

📒 Files selected for processing (1)
  • lib/sloppak.py

📝 Walkthrough

Walkthrough

Adds centralized safe resolution for sloppak manifest-relative paths and applies it across full-mix, drum-tab, arrangement, notation, timeline, lyrics, and keys loading.

Changes

Sloppak path safety

Layer / File(s) Summary
Shared path resolution
lib/sloppak.py
Adds _resolve_pack_path to reject paths escaping the sloppak directory and log resolution failures.
Side-file loader integration
lib/sloppak.py
Routes manifest side-file paths through the resolver and skips missing or unsafe files consistently across song loading paths.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested reviewers: byrongamatos, chrisbewithyou

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: consolidating manifest-path containment checks into one helper.
Description check ✅ Passed The description covers What, feedpak surface, and checklist sections, and it documents the refactor and verification adequately.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/sloppak-path-containment-helper

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@byrongamatos
byrongamatos merged commit 1a7e2bf into main Jul 23, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants