harden(save): sanitize --save-suffix into a path-safe token#736
harden(save): sanitize --save-suffix into a path-safe token#736nate-avra wants to merge 1 commit into
Conversation
Greptile SummaryThis PR adds a
Confidence Score: 5/5Safe to merge — tightly scoped defensive hardening with correct implementation and direct test coverage. The new No files require special attention. Important Files Changed
|
The user-provided --save-suffix was glued directly into the saved-report
filename without sanitization. Not exploitable today (the topic is slugified,
and --save-suffix is fixed to 'v3' in the skill flow / only ever set from the
user's own shell var, never network/topic input), but a future refactor of the
filename prefix/suffix joining could reintroduce a real path traversal.
Restrict the suffix to the same [a-z0-9-] class as the topic slug via a new
sanitize_suffix() helper, applied at both the real-path (save_output) and
display-path (compute_save_path_display) sites so they stay consistent. A
suffix that sanitizes to nothing simply drops (no suffix part). Ordinary
values ('v3', 'gemini', a client slug) are unchanged.
Adds regression tests: a traversal suffix is flattened in the display path and
save_output writes stay inside the target directory.
1996f6a to
892e7b8
Compare
What
--save-suffixis concatenated into the saved-report filename without sanitization (save_output,compute_save_path_display). This restricts it to the same[a-z0-9-]class already used for the topic slug.Why
Not exploitable today — the topic goes through
slugify(), and--save-suffixis fixed tov3in the SKILL.md flow and only ever templated from a user's own shell variable in the CONFIGURATION.md wrapper examples, never from network/topic input. But the suffix is glued onto the fixed filename prefix ({slug}-{raw_label}) with no separator boundary, so a future refactor of how prefix/suffix are joined could turn it into a real path traversal. This closes that latent seam defensively rather than relying on the current prefix shape to neutralize it.How
sanitize_suffix()helper — mirrorsslugify()but without the"last30days"fallback, so a suffix that sanitizes to nothing simply drops (no suffix part).save_output) and display-path (compute_save_path_display) sites so the footer path and the written path stay consistent.v3,gemini, a client slug) are unchanged.Tests
test_save_output_uses_raw_html_extension_and_suffixwith a traversal subcase (../../etc→ flattened-etc, no..in the path).test_save_suffix_cannot_escape_save_directory:save_output(..., suffix="../../ESCAPED")writes inside the target dir and the traversal fragment never lands in the filename.