Skip to content

fix(media-core): accept unpadded inline base64 images#96437

Merged
vincentkoc merged 3 commits into
openclaw:mainfrom
lin-hongkuan:codex/media-core-unpadded-base64-inline-images
Jun 24, 2026
Merged

fix(media-core): accept unpadded inline base64 images#96437
vincentkoc merged 3 commits into
openclaw:mainfrom
lin-hongkuan:codex/media-core-unpadded-base64-inline-images

Conversation

@lin-hongkuan

@lin-hongkuan lin-hongkuan commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

canonicalizeBase64() rejected every payload whose length was not already a multiple of four. That rejects common unpadded base64 such as SGVsbG8, even though the same bytes are valid once canonical padding is restored.

This also made inline image data URLs fail when the image bytes were valid but the base64 payload omitted trailing = padding.

Why This Change Was Made

The canonicalizer now pads unpadded base64 only when the length remainder is valid for base64 decoding. It still rejects empty input, impossible length remainder 1, more than two padding characters, and any data after padding.

The tests cover both the low-level helper and the inline image sanitizer path so the user-visible data URL behavior is protected.

User Impact

Valid unpadded inline image data URLs can now be normalized and used instead of being dropped as malformed. Malformed or hostile payloads are still rejected before decoding.

Evidence

Direct behavior probe:

$ node --import tsx -e "Promise.all([import('./packages/media-core/src/base64.ts'), import('./packages/media-core/src/inline-image-data-url.ts')]).then(([base64, inline]) => { const png = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4nGNgYAAAAAMAASsJTYQAAAAASUVORK5CYII='; const unpadded = png.replace(/=+$/u, ''); console.log(JSON.stringify({ canonical: ['SGVsbG8', 'TWE', 'TQ', 'S'].map((input) => ({ input, output: base64.canonicalizeBase64(input) })), inlineImage: inline.sanitizeInlineImageDataUrl('data:image/png;base64,' + unpadded) })); })"
{"canonical":[{"input":"SGVsbG8","output":"SGVsbG8="},{"input":"TWE","output":"TWE="},{"input":"TQ","output":"TQ=="},{"input":"S"}],"inlineImage":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4nGNgYAAAAAMAASsJTYQAAAAASUVORK5CYII="}

Node base64 decode reference probe:

$ node -e "for (const s of ['SGVsbG8','SGVsbG8=','TWE','TQ','T']) { try { console.log(JSON.stringify({s, decoded: Buffer.from(s, 'base64').toString('utf8'), bytes: Buffer.from(s, 'base64').length})) } catch (e) { console.log(JSON.stringify({s, error: e.message})) } }"
{"s":"SGVsbG8","decoded":"Hello","bytes":5}
{"s":"SGVsbG8=","decoded":"Hello","bytes":5}
{"s":"TWE","decoded":"Ma","bytes":2}
{"s":"TQ","decoded":"M","bytes":1}
{"s":"T","decoded":"","bytes":0}

Targeted tests:

$ node scripts/run-vitest.mjs run packages/media-core/src/base64.test.ts packages/media-core/src/inline-image-data-url.test.ts

 RUN  v4.1.8 C:/Users/lin20/Documents/New project 3/openclaw


 Test Files  2 passed (2)
      Tests  15 passed (15)
   Start at  21:14:09
   Duration  277ms (transform 66ms, setup 0ms, import 108ms, tests 11ms, environment 0ms)

[test] starting test/vitest/vitest.unit-fast.config.ts
[test] passed 1 Vitest shard in 8.81s

Formatting:

$ node_modules\.bin\oxfmt.cmd --check packages/media-core/src/base64.ts packages/media-core/src/base64.test.ts packages/media-core/src/inline-image-data-url.test.ts
Checking formatting...

All matched files use the correct format.
Finished in 1960ms on 3 files using 32 threads.

Whitespace:

$ git diff --check

AI-assisted

Prepared with Codex. I reviewed the change, understand the touched code path, and kept the PR focused on the bug described above.

@clawsweeper

clawsweeper Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 24, 2026, 11:37 AM ET / 15:37 UTC.

Summary
The PR changes the shared media-core base64 canonicalizer to pad valid unpadded inputs and adds helper plus inline image data URL regression tests.

Reproducibility: yes. from source with high confidence: current main rejects unpadded cleaned base64 before the inline-image sanitizer decodes it, and the branch adds coverage for the accepted unpadded case.

Review metrics: 1 noteworthy metric.

  • Current-main merge delta: 3 files affected; stale GitHub base shows 136 files. This keeps maintainer review focused on the real merge result instead of already-landed main changes.

Stored data model
Persistent data-model change detected: migration/backfill/repair: src/flows/doctor-core-checks.test.ts, migration/backfill/repair: src/flows/doctor-core-checks.ts, migration/backfill/repair: src/flows/doctor-health-contributions.ts, migration/backfill/repair: src/flows/doctor-lint-flow.test.ts, migration/backfill/repair: src/flows/doctor-lint-flow.ts, persistent cache schema: src/plugins/tools.optional.test.ts, and 21 more. Confirm migration or upgrade compatibility proof before merge.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🐚 platinum hermit
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • Rebase or refresh the PR review surface so the GitHub file list matches the current-main three-file delta before landing.

Risk before merge

  • [P1] The PR's GitHub base is stale and the file list is misleading; maintainers should review the current-main diff or rebase before landing so already-landed main changes do not obscure the small media-core delta.

Maintainer options:

  1. Decide the mitigation before merge
    Land or adopt the media-core canonicalizer fix after final current-head checks, with review anchored to the current-main three-file diff rather than the stale-base file list.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • [P2] No repair lane is needed because the current-main diff is focused, proof is sufficient, and no actionable patch finding remains.

Security
Cleared: No security or supply-chain issue was found in the actual current-main merge delta; it changes validation logic and tests only.

Review details

Best possible solution:

Land or adopt the media-core canonicalizer fix after final current-head checks, with review anchored to the current-main three-file diff rather than the stale-base file list.

Do we have a high-confidence way to reproduce the issue?

Yes, from source with high confidence: current main rejects unpadded cleaned base64 before the inline-image sanitizer decodes it, and the branch adds coverage for the accepted unpadded case.

Is this the best way to solve the issue?

Yes. The shared canonicalizeBase64 helper is the narrowest maintainable boundary because inline-image URLs and other base64 media consumers already validate there before decoding.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against fa2379dbc883.

Label changes

Label justifications:

  • P2: This is a normal shared media-core bug fix with focused regression coverage and limited direct blast radius.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body includes after-fix terminal output for canonicalizeBase64 and sanitizeInlineImageDataUrl plus targeted test and formatting output.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes after-fix terminal output for canonicalizeBase64 and sanitizeInlineImageDataUrl plus targeted test and formatting output.
Evidence reviewed

What I checked:

Likely related people:

  • RomneyDa: GitHub commit metadata and blame point to bd43c36 as the commit that added the current packages/media-core base64 and inline-image files. (role: introduced current package copy; confidence: medium; commits: bd43c36bb132; files: packages/media-core/src/base64.ts, packages/media-core/src/inline-image-data-url.ts)
  • steipete: Older history shows repeated base64 size and image data URL hardening in the pre-package media path that feeds the current shared media-core behavior. (role: adjacent media/base64 hardening contributor; confidence: high; commits: 31791233d604, e578521ef493, 00a08908892d; files: src/media/base64.ts, src/media/input-files.ts, src/agents/tool-images.ts)
  • vincentkoc: Recent media input-image MIME validation work changed the same validation boundary that canonicalized base64 reaches before decoding and MIME sniffing. (role: adjacent input-image validation contributor; confidence: medium; commits: 084dfd2ecc0e; files: src/media/input-files.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P2 Normal backlog priority with limited blast radius. labels Jun 24, 2026
@vincentkoc vincentkoc self-assigned this Jun 24, 2026
@vincentkoc
vincentkoc requested a review from a team as a code owner June 24, 2026 15:19
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation channel: msteams Channel integration: msteams channel: telegram Channel integration: telegram app: web-ui App: web-ui gateway Gateway runtime extensions: memory-core Extension: memory-core scripts Repository scripts commands Command implementations docker Docker and sandbox tooling agents Agent runtime and tooling extensions: duckduckgo extensions: qa-lab extensions: lmstudio extensions: ollama extensions: parallel size: XL and removed size: XS labels Jun 24, 2026
@vincentkoc
vincentkoc merged commit 6b1755a into openclaw:main Jun 24, 2026
96 checks passed
@vincentkoc

Copy link
Copy Markdown
Member

Merged via squash.

Thanks @lin-hongkuan!

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jun 25, 2026
Merged via squash.

Prepared head SHA: dc4693b
Co-authored-by: lin-hongkuan <234943746+lin-hongkuan@users.noreply.github.com>
Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
Reviewed-by: @vincentkoc
QiuYuang pushed a commit to QiuYuang/openclaw that referenced this pull request Jul 1, 2026
Merged via squash.

Prepared head SHA: dc4693b
Co-authored-by: lin-hongkuan <234943746+lin-hongkuan@users.noreply.github.com>
Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
Reviewed-by: @vincentkoc
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling app: web-ui App: web-ui channel: msteams Channel integration: msteams channel: telegram Channel integration: telegram commands Command implementations docker Docker and sandbox tooling docs Improvements or additions to documentation extensions: duckduckgo extensions: lmstudio extensions: memory-core Extension: memory-core extensions: ollama extensions: parallel extensions: qa-lab gateway Gateway runtime P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. scripts Repository scripts size: XL status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants