Close #722's allowlist reservation and document strip-marker generations (#736) - #748
Draft
philcunliffe wants to merge 3 commits into
Draft
Close #722's allowlist reservation and document strip-marker generations (#736)#748philcunliffe wants to merge 3 commits into
philcunliffe wants to merge 3 commits into
Conversation
…ons (#736) Documentation-only follow-up to #732: none of #736's three items is a defect, so no runtime behaviour changes. 1. Close #722 verbatim, permanently: an allowlist cannot make the marker trustworthy, since the marker is already forgeable byte-for-byte in plain prose, and no injection channel exists to defend against, since the mediatype class excludes all whitespace and `,`. Both claims re-verified against the current regex before writing them down. 2. Document the two strip-marker generations (pre-#732 fixed `image` sentinel vs. post-#732 verbatim mediatype) in both hypaware-query skill docs, so a querier matches the stable `;base64,<stripped>` substring instead of either full marker. 3. Record why the empty-mediatype fallback (`application/octet-stream`) stays as shipped, next to its definition, including the RFC 2397 alternative and the octet-stream collision cost the issue names. Co-Authored-By: Claude <noreply@anthropic.com>
Closes out the three deferred items from PR #732 / issue #736. None of the three is a defect and no runtime behaviour changed: the diff is comments and skill-doc prose only (confirmed below).
1. Close #722's allowlist reservation, verbatim, permanently
Added to the block comment above
BASE64_DATA_URIinhypaware-core/plugins-workspace/ai-gateway/src/message_projector.js, and posted as a closing note on #722 (issue left open, not closed): #722 (comment)Both claims were re-verified against this worktree's code before writing them down (a prior PR in this series shipped a rationale that turned out false and a review caught it):
Forgeable in plain prose, allowlist or not.
<stripped>starts with<, which is outside the payload class[A-Za-z0-9+/=_-]+, so the regex never matches a forged marker at all. An allowlist on the mediatype cannot help: the wire (or an ordinary chat message) can already produce a byte-identical "structured-looking" marker that never went through the stripper.No injection channel exists to close.
The mediatype class
[^\s,]{0,255}?excludes all whitespace and,, so a mediatype containing either never lets the prefix reach;base64,; the string just fails to match and passes through untouched. No log-line or CSV-cell splice is reachable through this marker.Both commands above were run directly in this worktree via
node -e, output as shown.The comment also names the comma as the load-bearing idempotency lock (already documented a few lines up in the file): every
;base64,ends in a,, which the mediatype class excludes, so a captured mediatype can never itself contain;base64,.No LLP has an open reservation on this topic (checked
llp/0016-ai-gateway.decision.md, the LLP index, and grepped the corpus forbase64,allowlist,data URI,#718/#719/#722); neither #718's nor #722's PR touchedllp/. So there is no LLP edit in this PR beyond the source comment and the #722 note.2. Document the two marker generations for queriers
Added one bullet to both
hypaware-core/plugins-workspace/claude/skills/hypaware-query/SKILL.mdand the codex copy, next to the existingcontent_textcolumn description:Confirmed both literal strings by running the current projector over both shapes:
3. Empty-mediatype fallback: kept as shipped
Added a comment next to
const UNKNOWN_MEDIATYPE = 'application/octet-stream'recording why it stays: the RFC 2397 alternative (text/plain;charset=US-ASCIIfor an omitted mediatype) answers "what would a browser render this as," not "what did the row actually see," and is no more discriminating in a search than the current sentinel; the accepted cost is that it collides indistinguishably with a genuineapplication/octet-streampayload and a literal search fordata:;base64no longer finds the row. No behaviour change; the existing test (an empty mediatype falls back to application/octet-stream) and existing regex logic are untouched.Out of scope
The over-255-char mediatype case is untouched, per the issue (belongs to #718's open
content_textcap question).Verification
Confirmed the diff to
message_projector.jstouches only comments (no code/logic lines changed):No behaviour changed, so no revert/watch-fail/restore discriminating test was written; the existing 22/22 in
test/plugins/ai-gateway-content-data-uri.test.jscontinue to pin the marker behaviour unmodified.Checks run in a fresh worktree after
npm install:npm test— 4024 pass / 0 fail / 1 pre-existing skipnpm run typecheck— clean, no outputnpm run smoke -- gateway_claude_capture—smoke gateway_claude_capture: ok