Skip to content

protect: expose uploaded-file data (content/type) for rule-driven inspection - #115

Merged
patchstackdave merged 1 commit into
mainfrom
protect/upload-content-inspection
Aug 13, 2026
Merged

protect: expose uploaded-file data (content/type) for rule-driven inspection#115
patchstackdave merged 1 commit into
mainfrom
protect/upload-content-inspection

Conversation

@patchstackdave

@patchstackdave patchstackdave commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

What

The engine could see an upload's filename and the raw multipart blob, but not file content as a first-class source (file_contains was a no-op). So a polyglot — a valid image with a malicious payload inside (the ImageMagick "PNG that isn't a PNG" RCE class, webshell-in-image) — slipped past a filename rule.

Expose the data, keep detection in rules. The multipart parser now captures each file part as { filename, type, content }, and the resolver exposes three data sources:

  • files.<name>.content — the file's bytes, for a contains/regex signature rule.
  • files.<name>.type — the part's declared content-type.
  • files.<name>.filename — the filename (same as bare files.<name>).

No detection heuristics live in the engine — what counts as malicious is composed in rules (SaaS-delivered, versionable), matching the "detection is fully rule-driven, nothing hardcoded" principle. For example a declared-type-vs-content mismatch is just two inclusive conditions:

{ "parameter": "rules", "rules": [
  { "parameter": "files.f.type",    "match": { "type": "regex", "value": "/^image\\//i" }, "inclusive": true },
  { "parameter": "files.f.content", "match": { "type": "regex", "value": "/^\\s*<[a-z!?]/i" }, "inclusive": true }
] }

That ^\s*< head anchor + the type gate catch svg-as-png / php-as-png with very low false-positive — and deliberately do not match a bare <script> in a real image's EXIF/XMP metadata (a genuine binary image head never starts with <). Content signatures (webshell / ImageMagick MSL) are a second OR'd condition. Full template ships in the triage-vpatch-npm skill.

Backward-compatible: bare files.<name> still returns the filename.

Notes / limits

  • Content rides inside the already-capped inspection body, so a payload past the cap may not be seen — the documented prefix-scan tradeoff. Best pinned to the vulnerable upload endpoint (via reachability) so it doesn't scan every request.
  • Binary magic bytes are lossy in the decoded string, so detection anchors on ASCII markers + the structural ^< head check rather than raw magic bytes.

Tests

tests/protect/upload-inspection.test.ts covers content signatures, the rule-composed type mismatch (php-as-png / svg-as-png flagged; real image with <script> in metadata NOT flagged), the .type/.filename sources, and filename backward-compat. Full suite green (651), typecheck clean.

@coderbuds

coderbuds Bot commented Aug 13, 2026

Copy link
Copy Markdown

Implements robust file-upload inspection with content signatures and type mismatch detection.

🎯 Quality: 100% Elite · 📦 Size: Medium

📈 This month: Your 52nd PR — above team average · Averaging Excellent

See how your team is trending →

The engine saw an upload's filename and the raw multipart blob, but not file
content as a first-class thing (file_contains was a no-op), so a polyglot — a
valid image with a malicious payload inside (the ImageMagick "PNG that isn't a
PNG" RCE class) — passed a filename rule.

Expose the DATA, keep the detection in rules. The multipart parser now captures
each file part as { filename, type, content }, and the resolver exposes:
  - files.<name>.content   the file bytes (contains/regex signature rules)
  - files.<name>.type      the part's declared content-type
  - files.<name>.filename  the filename (same as bare files.<name>)
Bare files.<name> still returns the filename, so existing filename rules are
unchanged. No detection heuristics live in the engine — "what's malicious"
(webshell signatures, declared-type-vs-content mismatch) is composed in rules
(see the triage-vpatch-npm upload template): e.g. mismatch = `files.f.type`
matches ^image/ AND `files.f.content` head is markup (`^\s*<`), two inclusive
conditions — which also avoids the false positive of <script> in an image's
EXIF/XMP metadata. Content rides inside the already-capped body.

Adds tests/protect/upload-inspection.test.ts; updates multipart shape assertions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@patchstackdave
patchstackdave force-pushed the protect/upload-content-inspection branch from 26f7b38 to b65137c Compare August 13, 2026 07:57
@patchstackdave patchstackdave changed the title protect: file-upload content inspection (content signatures + type mismatch) protect: expose uploaded-file data (content/type) for rule-driven inspection Aug 13, 2026
@patchstackdave

Copy link
Copy Markdown
Contributor Author

/review

@patchstackdave
patchstackdave merged commit 81e590d into main Aug 13, 2026
5 checks passed
@patchstackdave
patchstackdave deleted the protect/upload-content-inspection branch August 13, 2026 08:03
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