Stop refusing _manifest.json, and test the rule instead of guessing it - #69
Conversation
The filename guard added for the path-traversal finding required the first character to be alphanumeric. `_manifest.json` is a real release asset, has been on disk since July, and was silently refused from the first run -- the cron log filled with "refusing" lines for it against every release on the page. Guessing at the character set upstream is allowed to use was the mistake. The rule is structural now: a name is a plain filename when basename leaves it unchanged, which rules out slashes and `..` and `.`, and when it does not start with a dot, which keeps an asset from colliding with the state file or the size manifests. Nothing else is assumed. Verified against a table of real asset names and the traversal cases the review raised, and on the host: 495 assets published rather than 494, and _manifest.json mirrored and tracked again. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MFwmYHCbci2esgc8AMmzJR
PR Summary by QodoAccept underscore-prefixed release assets by using structural filename validation
AI Description
Diagram
High-Level Assessment
Files changed (1)
|
Code Review by Qodo
1.
|
Dropping the character allowlist also dropped the only thing keeping a newline out of an asset name, and every name that passes the guard is interpolated into a log line -- so a crafted name could forge entries in the cron log. Control characters are the one character rule worth keeping, and keeping it at the guard rather than at each log line means the log stays readable instead of every message being wrapped in .inspect. Table now covers an embedded newline, a tab and DEL alongside the traversal cases. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MFwmYHCbci2esgc8AMmzJR
Found while verifying the host after the block volume was detached: the cron log was filling with
_manifest.jsonis a real release asset — it has been in/srv/github-releasessince 25 July. The filename guard added in #67 for the path-traversal finding spelled the rule as a character class,/\A[A-Za-z0-9][A-Za-z0-9._+-]*\z/, which requires the first character to be alphanumeric and so refused it from the very first run.Guessing at the character set upstream is allowed to use was the mistake. The rule is structural now — a name is a plain filename when:
basenameleaves it unchanged, which rules outa/b,../../etc/cron.d/evil,..and..mirror-state.jsonor the size manifestsNothing is assumed about which characters upstream may use, so the guard defends the actual threat without refusing files we meant to keep.
Verification
Table-tested against real asset names and every traversal case from the #67 review:
openipc.gk7605v100-nor-lite.tgz_manifest.jsonsizes.t31x-lite.jsonu-boot-t40xp-universal.bin../../etc/cron.d/evila/b../..rootfs.sizes/.mirror-state.json/.tmp-mirror-xAnd on the host: 495 assets published rather than 494,
_manifest.jsonfetched and tracked in the state file again, norefusinglines left in the log.🤖 Generated with Claude Code
https://claude.ai/code/session_01MFwmYHCbci2esgc8AMmzJR