Media: reject spoofed input_image MIME payloads#38289
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ffeeba6938
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| (detectedMime && HEIC_INPUT_IMAGE_MIMES.has(detectedMime)) || | ||
| (HEIC_INPUT_IMAGE_MIMES.has(declaredMime) && !detectedMime) | ||
| ? (detectedMime ?? declaredMime) |
There was a problem hiding this comment.
Honor detected non-HEIC image types before conversion
The new sourceMime selection ignores detectedMime whenever it is an image type other than HEIC/HEIF, so a payload declared as image/heic but sniffed as image/png/image/jpeg is still treated as HEIC and routed through convertHeicToJpeg. In practice (for both base64 and URL inputs with incorrect mediaType/Content-Type), this can recompress or even fail valid non-HEIC images, which regresses the prior behavior and contradicts the intent to scope HEIC normalization to actual HEIC bytes.
Useful? React with 👍 / 👎.
Greptile SummaryThis PR hardens However, there is a logic regression in the The spoofing-rejection guard is correct and new tests verify the intended behaviour for spoofed base64 and URL payloads. The Confidence Score: 2/5
Last reviewed commit: ffeeba6 |
| const sourceMime = | ||
| (detectedMime && HEIC_INPUT_IMAGE_MIMES.has(detectedMime)) || | ||
| (HEIC_INPUT_IMAGE_MIMES.has(declaredMime) && !detectedMime) | ||
| ? (detectedMime ?? declaredMime) | ||
| : declaredMime; |
There was a problem hiding this comment.
HEIC conversion triggered on non-HEIC image bytes when declared MIME is HEIC
When declaredMime is "image/heic" (or "image/heif") but detectedMime is a non-HEIC image type like "image/jpeg", the current logic sets sourceMime = declaredMime = "image/heic" and proceeds to call convertHeicToJpeg on non-HEIC bytes. This is a regression from the previous behavior.
Trace for declaredMime = "image/heic", detectedMime = "image/jpeg":
- Guard check (line 241): both are
image/*→ no rejection (correct) - Part 1:
"image/jpeg" && HEIC_INPUT_IMAGE_MIMES.has("image/jpeg")→false - Part 2:
HEIC_INPUT_IMAGE_MIMES.has("image/heic") && !"image/jpeg"→false - Overall condition →
false, takes the else branch sourceMime = declaredMime = "image/heic"- Line 253: tries to convert JPEG bytes as HEIC
The old code handled this gracefully by using detectedMime ?? declaredMime for all HEIC-declared inputs, so a JPEG claimed as HEIC would be returned directly without conversion.
Fix: also use detectedMime when declaredMime is HEIC but a non-HEIC image was actually detected:
| const sourceMime = | |
| (detectedMime && HEIC_INPUT_IMAGE_MIMES.has(detectedMime)) || | |
| (HEIC_INPUT_IMAGE_MIMES.has(declaredMime) && !detectedMime) | |
| ? (detectedMime ?? declaredMime) | |
| : declaredMime; | |
| const sourceMime = | |
| (detectedMime && HEIC_INPUT_IMAGE_MIMES.has(detectedMime)) || | |
| (HEIC_INPUT_IMAGE_MIMES.has(declaredMime) && !detectedMime) | |
| ? (detectedMime ?? declaredMime) | |
| : HEIC_INPUT_IMAGE_MIMES.has(declaredMime) && detectedMime | |
| ? detectedMime | |
| : declaredMime; |
This restores the old behaviour of using the detected MIME for HEIC-declared inputs while preserving all the new spoofing-rejection logic. A corresponding test case for "declared HEIC + detected non-HEIC image" is also missing.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/media/input-files.ts
Line: 244-248
Comment:
**HEIC conversion triggered on non-HEIC image bytes when declared MIME is HEIC**
When `declaredMime` is `"image/heic"` (or `"image/heif"`) but `detectedMime` is a non-HEIC image type like `"image/jpeg"`, the current logic sets `sourceMime = declaredMime = "image/heic"` and proceeds to call `convertHeicToJpeg` on non-HEIC bytes. This is a regression from the previous behavior.
Trace for `declaredMime = "image/heic"`, `detectedMime = "image/jpeg"`:
- Guard check (line 241): both are `image/*` → no rejection (correct)
- Part 1: `"image/jpeg" && HEIC_INPUT_IMAGE_MIMES.has("image/jpeg")` → `false`
- Part 2: `HEIC_INPUT_IMAGE_MIMES.has("image/heic") && !"image/jpeg"` → `false`
- Overall condition → **`false`**, takes the else branch
- **`sourceMime = declaredMime = "image/heic"`**
- Line 253: tries to convert JPEG bytes as HEIC
The old code handled this gracefully by using `detectedMime ?? declaredMime` for all HEIC-declared inputs, so a JPEG claimed as HEIC would be returned directly without conversion.
Fix: also use `detectedMime` when `declaredMime` is HEIC but a non-HEIC image was actually detected:
```suggestion
const sourceMime =
(detectedMime && HEIC_INPUT_IMAGE_MIMES.has(detectedMime)) ||
(HEIC_INPUT_IMAGE_MIMES.has(declaredMime) && !detectedMime)
? (detectedMime ?? declaredMime)
: HEIC_INPUT_IMAGE_MIMES.has(declaredMime) && detectedMime
? detectedMime
: declaredMime;
```
This restores the old behaviour of using the detected MIME for HEIC-declared inputs while preserving all the new spoofing-rejection logic. A corresponding test case for "declared HEIC + detected non-HEIC image" is also missing.
How can I resolve this? If you propose a fix, please make it concise.* main: (37 commits) feat(gateway): add channel-backed readiness probes (openclaw#38285) CI: enable report-only Knip deadcode job Tooling: wire deadcode scripts to Knip Tooling: add Knip workspace config CI: skip detect-secrets on main temporarily Install Smoke: fetch docs base on demand CI: fetch base history on demand CI: add base-commit fetch helper Docs: clarify main secret scan behavior CI: keep full secret scans on main Docs: update secret scan reproduction steps CI: scope secret scans to changed files Media: reject spoofed input_image MIME payloads (openclaw#38289) chore: code/dead tests cleanup (openclaw#38286) Install Smoke: cache docker smoke builds Install Smoke: allow reusing prebuilt test images Install Smoke: shallow docs-scope checkout CI: shallow scope checkouts feat(onboarding): add web search to onboarding flow (openclaw#34009) chore: disable contributor labels ...
* Media: reject spoofed input image MIME types * Media: cover spoofed input image MIME regressions * Changelog: note input image MIME hardening
* Media: reject spoofed input image MIME types * Media: cover spoofed input image MIME regressions * Changelog: note input image MIME hardening
* Media: reject spoofed input image MIME types * Media: cover spoofed input image MIME regressions * Changelog: note input image MIME hardening
* Media: reject spoofed input image MIME types * Media: cover spoofed input image MIME regressions * Changelog: note input image MIME hardening
* Media: reject spoofed input image MIME types * Media: cover spoofed input image MIME regressions * Changelog: note input image MIME hardening
* Media: reject spoofed input image MIME types * Media: cover spoofed input image MIME regressions * Changelog: note input image MIME hardening
* Media: reject spoofed input image MIME types * Media: cover spoofed input image MIME regressions * Changelog: note input image MIME hardening (cherry picked from commit 084dfd2)
* Media: reject spoofed input image MIME types * Media: cover spoofed input image MIME regressions * Changelog: note input image MIME hardening (cherry picked from commit 084dfd2)
Summary
normalizeInputImagetrusted caller-declared non-HEIC image MIME types before the allowlist check, so a request could claimimage/pngwhile supplying concrete non-image bytes.input_imagepayloads bypass the intended image MIME validation boundary on Gateway HTTP APIs.image/*payloads, and still keep HEIC/HEIF normalization scoped to actual HEIC inputs.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
User-visible / Behavior Changes
input_imagerequests now reject spoofed non-image payloads even when the request declares an allowed image MIME type.Security Impact (required)
Yes/No) NoYes/No) NoYes/No) NoYes/No) NoYes/No) NoYes, explain risk + mitigation:Repro + Verification
Environment
Steps
input_imagebase64 or URL request that declaresimage/png.application/pdfinstead of an image.Expected
Actual
Evidence
Attach at least one:
Human Verification (required)
What you personally verified (not just CI), and how:
Compatibility / Migration
Yes/No) YesYes/No) NoYes/No) NoFailure Recovery (if this breaks)
src/media/input-files.ts,src/media/input-files.fetch-guard.test.ts,CHANGELOG.mdRisks and Mitigations
AI Assistance
Verification:
pnpm vitest run src/media/input-files.fetch-guard.test.ts src/gateway/openai-http.test.ts src/gateway/openresponses-parity.test.ts src/gateway/openai-http.image-budget.test.tspnpm exec oxfmt --check src/media/input-files.ts src/media/input-files.fetch-guard.test.ts CHANGELOG.md