Skip to content

Follow-up: strip marker for non-image base64 payloads should record the real mediatype (from PR #719) #722

Description

@philcunliffe

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    neutral:fixDelegate this issue to neutral for an autonomous fix attempt (reproduce -> fix -> PR)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions