Deferred from PR #719 (which fixed #718's 124.5 MB/day regression). Not a blocker for that PR: the marker is truthful about the presence of a stripped payload even when wrong about its type, and no downstream consumer of content_text currently parses or branches on the marker's mime field.
What is wrong
stripBase64DataUris in hypaware-core/plugins-workspace/ai-gateway/src/message_projector.js replaces every data:<mime>;base64,<payload> in content_text with a fixed sentinel:
// message_projector.js:1151-1152
const BASE64_DATA_URI = /data:[^\s,]{0,255}?;base64,[A-Za-z0-9+/=_-]+/g
const STRIPPED_DATA_URI = 'data:image;base64,<stripped>'
The marker always says image, even when the stripped payload was application/pdf, audio/*, or anything else. The row then asserts something false about what was captured: a search for data:application/pdf over content_text silently misses rows where such a payload was present, because it has been rewritten to claim it was an image.
Why it was deferred rather than fixed
The fixed-constant marker is the suggested snippet from #718 itself, and #718 lists "the mime allowlist" as a point worth deciding rather than assuming. Review rounds 1 and 2 on PR #719 both flagged it and left it deliberately; test/plugins/ai-gateway-content-data-uri.test.js pins the current behaviour as intentional.
Reachability today is thin. The only production trigger on record is Codex view_image, which is PNG-only by construction, and nothing in the current codex or claude plugin source emits an input_file / application/pdf / input_audio data URI through this string path. The surrounding JSON ("type":"input_image","image_url":"data:image;base64,<stripped>") is left untouched by the regex, so the structural context a human or the n-gram index relies on for "was there an attachment here" survives intact.
Suggested fix
Verify against current line numbers before landing:
const BASE64_DATA_URI = /data:([^\s,]{0,255}?);base64,[A-Za-z0-9+/=_-]+/g
return text.replace(BASE64_DATA_URI, (_m, mime) => `data:${mime || 'application/octet-stream'};base64,<stripped>`)
This captures the matched mediatype and echoes it back, falling back to application/octet-stream for the empty-mediatype case the existing bare: data:;base64,... test already covers.
The decision needed before landing
Echo the mediatype verbatim (simplest, matches what was on the wire), or apply an allowlist/normalization (collapse image/png, image/jpeg and friends to a known set, bucket everything else as application/octet-stream) to avoid storing an attacker-influenced or malformed mediatype string in content_text. The regex already caps the mediatype at 255 chars, so an allowlist is defense in depth, not a correctness requirement.
Test to update
test/plugins/ai-gateway-content-data-uri.test.js, the case a non-image mime type is stripped as well: its expected value and its "deliberate" comment both need updating to the new intent.
Deferred from PR #719 (which fixed #718's 124.5 MB/day regression). Not a blocker for that PR: the marker is truthful about the presence of a stripped payload even when wrong about its type, and no downstream consumer of
content_textcurrently parses or branches on the marker's mime field.What is wrong
stripBase64DataUrisinhypaware-core/plugins-workspace/ai-gateway/src/message_projector.jsreplaces everydata:<mime>;base64,<payload>incontent_textwith a fixed sentinel:The marker always says
image, even when the stripped payload wasapplication/pdf,audio/*, or anything else. The row then asserts something false about what was captured: a search fordata:application/pdfovercontent_textsilently misses rows where such a payload was present, because it has been rewritten to claim it was an image.Why it was deferred rather than fixed
The fixed-constant marker is the suggested snippet from #718 itself, and #718 lists "the mime allowlist" as a point worth deciding rather than assuming. Review rounds 1 and 2 on PR #719 both flagged it and left it deliberately;
test/plugins/ai-gateway-content-data-uri.test.jspins the current behaviour as intentional.Reachability today is thin. The only production trigger on record is Codex
view_image, which is PNG-only by construction, and nothing in the currentcodexorclaudeplugin source emits aninput_file/application/pdf/input_audiodata URI through this string path. The surrounding JSON ("type":"input_image","image_url":"data:image;base64,<stripped>") is left untouched by the regex, so the structural context a human or the n-gram index relies on for "was there an attachment here" survives intact.Suggested fix
Verify against current line numbers before landing:
This captures the matched mediatype and echoes it back, falling back to
application/octet-streamfor the empty-mediatype case the existingbare: data:;base64,...test already covers.The decision needed before landing
Echo the mediatype verbatim (simplest, matches what was on the wire), or apply an allowlist/normalization (collapse
image/png,image/jpegand friends to a known set, bucket everything else asapplication/octet-stream) to avoid storing an attacker-influenced or malformed mediatype string incontent_text. The regex already caps the mediatype at 255 chars, so an allowlist is defense in depth, not a correctness requirement.Test to update
test/plugins/ai-gateway-content-data-uri.test.js, the casea non-image mime type is stripped as well: its expected value and its "deliberate" comment both need updating to the new intent.