Skip to content

fix(storage): stop persisting base64 image bytes on thread parts - #7021

Merged
pedrofrxncx merged 2 commits into
mainfrom
fix/redact-base64-thread-parts
Sep 4, 2026
Merged

fix(storage): stop persisting base64 image bytes on thread parts#7021
pedrofrxncx merged 2 commits into
mainfrom
fix/redact-base64-thread-parts

Conversation

@pedrofrxncx

@pedrofrxncx pedrofrxncx commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

ELEC-244.

What

Thread thrd_7xfnXnNkBxn4eeDaNZc0M (electrolux) has three tool_result parts of 130–236 KB each — tool-Read on a .png, with the whole image inlined as {type:"image", source:{type:"base64", data:"iVBORw0…"}}. Across prod: 686 parts, 54 MB of base64 in thread_message_parts.

It is written once and never read back as an image, but it is folded into the message history of every subsequent turn on the thread.

Fix

One guard at serializePayload in apps/api/src/storage/thread-message-parts.ts — the same choke point that already strips NULs, lone surrogates, URL userinfo and GitHub tokens, and the one no new producer can bypass:

  • an image block carrying inline bytes (source.data or data) becomes {type:"text", text:"[image omitted]"} — a text block rather than an image block with gutted data, so a folded message stays a valid content block;
  • a data:<mime>;base64,… URL inside any string is replaced with [base64 data omitted];
  • image blocks that only reference storage (studio-storage:, signed URLs) are left alone — cheap, and the UI needs them.

Live streaming is unaffected; the image still reaches the UI during the run, it just is not durable.

Testing

bun test apps/api/src/storage/thread-message-parts.test.ts — 18 pass, three new cases (inline block redacted, reference block preserved, data URL in text redacted). bun run check clean.

Not in this PR

Backfill of the existing 54 MB — that is a prod write, say the word and I will open it separately.


Summary by cubic

Stops persisting base64 image bytes in thread message parts and backfills the ones already stored — up to 236 KB per part that was otherwise folded into the prompt of every later turn.

  • At serialization time, inline image blocks become an [image omitted] text placeholder and base64 data URLs inside strings are redacted; image blocks that only reference storage stay intact.
  • Addresses ELEC-244.

Migration

  • Migration 202-redact-base64-thread-parts rewrites existing rows with the same serializePayload pass, skipping rows that serialize unchanged, so it is idempotent.
  • No down migration — the image bytes are not recoverable.

Written for commit d02a023. Summary will update on new commits.

Review in cubic

Tool results that read an image (screenshots, crops) land their full base64
payload in `thread_message_parts` — 54 MB across ~700 rows in production, and
it rides along into every later prompt folded from those parts.

Redact at the existing storage choke point, next to the credential stripping:
an image block carrying inline bytes becomes a text placeholder, and a base64
data URL inside a string is dropped. Image blocks that only reference object
storage are untouched — those are cheap and the UI still needs them.
@pedrofrxncx
pedrofrxncx enabled auto-merge (squash) September 4, 2026 20:04
Migration 202 applies the same `serializePayload` pass to what is already
stored: 700 rows holding 74 MB of base64.

Candidates are found in two stages — `pg_column_size` reads the TOAST pointer
without detoasting and cuts 1M rows to ~19k, and only those get detoasted for
the `LIKE`. Payloads are then fetched by id in batches of 20, since the
matching rows do not fit in one result set.

A row with nothing to redact serializes byte-identically and is skipped, so the
migration is idempotent. No `down` — the bytes are not recoverable.
@pedrofrxncx
pedrofrxncx merged commit 8f88771 into main Sep 4, 2026
33 checks passed
@pedrofrxncx
pedrofrxncx deleted the fix/redact-base64-thread-parts branch September 4, 2026 20:11
decocms Bot pushed a commit that referenced this pull request Sep 4, 2026
PR: #7021 fix(storage): stop persisting base64 image bytes on thread parts
Bump type: patch

- decocms (apps/api/package.json): 4.332.0 -> 4.332.1
- @decocms/native (apps/native/package.json): 4.332.0 -> 4.332.1

Deploy-Scope: server
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.

1 participant