Repository navigation
Closed
Labels
app/aiPackage: ai (pkgs/ or apps/ in this repo)Package: ai (pkgs/ or apps/ in this repo)effort/xsTrivial: expected under 30 minutes of workTrivial: expected under 30 minutes of workkind/bugSomething isn't working (prefixed form of the classic bug label)Something isn't working (prefixed form of the classic bug label)priority/lowNice to have; do when convenientNice to have; do when convenientsource/creatorFiled by the new-issue-creator jobFiled by the new-issue-creator jobstatus:donetask/small-fixQueue packet kind: bounded fix with reproduction and targeted testsQueue packet kind: bounded fix with reproduction and targeted tests
Description
Bug
mediaTypeExtension and the file-extension lookup in packages/ai/src/utils/media-type.ts read plain-object tables through the prototype chain, so an Object.prototype member name is returned where a media type / extension string is expected:
packages/ai/src/utils/media-type.ts:52—extensionMediaType:EXTENSIONS[path.slice(path.lastIndexOf(".") + 1).toLowerCase()]. The extension is lower-cased, so the lower-case prototype names hit exactly: a file namednotes.constructorornotes.__proto__whose bytes match no magic-byte sniffer getsEXTENSIONS["constructor"](theObjectfunction) orEXTENSIONS["__proto__"](Object.prototype) as its "media type".packages/ai/src/utils/media-type.ts:69—mediaTypeExtension:EXTENSION_ALIASES[type] ?? …. The??fallback never fires for prototype names because the inherited member is not nullish:mediaTypeExtension("constructor")returns theObjectfunction,mediaTypeExtension("__proto__")returnsObject.prototype.
Evidence (verified this run, main 80225ac023)
- Laptop probe of the exact tables/lookups:
EXTENSIONS["constructor"]→function,EXTENSIONS["__proto__"]→object,EXTENSION_ALIASES["constructor"]→function; a template-built filename becomesaudio.function Object() { [native code] }. - Callers that trust the result:
packages/ai/src/media.ts:313/packages/ai/src/promise.ts:116—Media.file/ file reads passfileMediaType(data, path)intoMedia.bytes, whosemediaTypeis typedstring(packages/ai/src/media.ts:274); a function/object is stored as the asset's media type and propagates to providers.packages/ai/src/protocols/openai-transcription.ts:167-175—mediaTypeExtension(...) === undefinedis the designed rejection ("cannot name a … upload"); for"constructor"/"__proto__"the check is bypassed and the upload filename becomesaudio.function Object() { [native code] }instead of the clear local error.packages/ai/src/protocols/elevenlabs-transcription.ts:111-117— same filename construction, same garbage name.
- The
check:proto-readsgate (script/check-proto-reads.ts) only scanspackages/core/src, which is why this class survived inpackages/aiafter the audit(core): prototype-chain dictionary lookups — systematic sweep, shared guard, regression net #833/audit(core): shared own-property guard + regression net for prototype-chain dictionary reads (#833 follow-up) #865 sweep.
Why it matters
Both inputs are external: file paths/extensions come from user files, and audio media types come from callers/providers. A mistyped or hostile value produces a function/object where every consumer assumes a string — bypassing a designed validation error in OpenAI transcription and corrupting asset metadata elsewhere — instead of the intended undefined fallback.
Acceptance criteria
-
mediaTypeExtension("constructor"),mediaTypeExtension("__proto__")andmediaTypeExtension("toString")returnundefined; real aliases (e.g.audio/mp3→mp3) unchanged. -
fileMediaType(bytes, "x.constructor")andfileMediaType(bytes, "x.__proto__")returnundefinedfor unsniffable bytes; known extensions unchanged. - OpenAI transcription again rejects an unnameable media type with its existing
invalidRequestmessage for these names. - Regression tests in
packages/aitest suite next to the existing media-type tests pin all three names for both functions. - Fix uses an own-property guard (the
ownValue/ownOrpattern from audit(core): shared own-property guard + regression net for prototype-chain dictionary reads (#833 follow-up) #865, orObject.hasOwn) — not a blocklist of names.
Metadata
Metadata
Assignees
Labels
app/aiPackage: ai (pkgs/ or apps/ in this repo)Package: ai (pkgs/ or apps/ in this repo)effort/xsTrivial: expected under 30 minutes of workTrivial: expected under 30 minutes of workkind/bugSomething isn't working (prefixed form of the classic bug label)Something isn't working (prefixed form of the classic bug label)priority/lowNice to have; do when convenientNice to have; do when convenientsource/creatorFiled by the new-issue-creator jobFiled by the new-issue-creator jobstatus:donetask/small-fixQueue packet kind: bounded fix with reproduction and targeted testsQueue packet kind: bounded fix with reproduction and targeted tests
Activity
developing-today commentedon Oct 29, 2026
OPENCODE-226
dezren39 commentedon Oct 29, 2026
🤖 Agent note · filed
dezren39 commentedon Oct 29, 2026
🤖 Dispatcher hand-back — executor-saturated · openchamber-dispatcher run 2026-10-29 12:45 CDT
small_fix, low risk, no flags, priority/low per issue label, effort/xs, timebox 20m) ↔ this issue (fix(ai): media-type lookups read through the prototype chain — a ".constructor" file or "constructor" media type returns the Object function instead of undefined #906). Packet verified this run: issue OPEN viagh, labels clean (kind/bug, task/small-fix, status/needs-triage, source/creator, priority/low, queue/queued, app/ai, effort/xs — no held / user/owned / do-not-merge / do-not-auto-edit, no work/*, no queue/claimed); taskqueuedatt 0/2 on multiple direct Supabase reads this run (enqueued 12:47:08 CDT this run by the new-issue-creator); no competing PR for fix(ai): media-type lookups read through the prototype chain — a ".constructor" file or "constructor" media type returns the Object function instead of undefined #906 (checked viagh search prs— no results); no prior executor-saturated hand-back on this issue (its only comments are the creator's filing note and a tracker link)./api/session-activityreads over the laptop (~12:47–12:50 CDT, 51 idle / 2 busy, identical session-id sets). 0 free slots, so no session was launched for this task. Note: capacity reads this run also exposed a dual-instance anomaly — a second OpenChamber web process (started 12:38 CDT) now shares port 3000 with the standing process (started 05:22 CDT), so some reads answer from the old process (1 session in its activity map); the union is ≥53 distinct sessions and the saturation verdict is the same on either process. The duplicate-instance problem is being filed separately this run. Idle completed sessions still hold their slots — harvest by the owning lanes remains the unblock./api/quota/*read ~12:47 CDT (raw:dispatch-packets/quota-2026-10-29-1247.json.txt, all 14 sections present) — opencode-go weekly 59.37% (reset Sun Nov 1 6:00 PM) / monthly 92.17% (reset Fri Nov 6 11:55 PM; monthly ≥85%, named; pool stays open under the used-to-100% rule; the 5h window had just rolled at its 12:47 PM reset and returned no 5h entry; per the steward guard Go is Flash finishing-only with minimal new placements at/over the ~90% monthly guard line); cline-pass 5h 5% (reset 1:00 PM) / weekly 84% / monthly 98% (monthly ≥85%, named; HARD STOP per the steward guard, not used); codex weekly 24% on the active credential (label "OpenAI", reset Wed Nov 4 12:35 AM; final reserve, not used); openai.alternate 5h 0% / weekly 0%; google per-model daily — models at 57% and 0%; github-copilot premium interactions "Unlimited"; command-code 5h 1.83% / weekly 26.99%. Saturation is slots only. (Meta read FAILED twice this run — ok:false, "No quota data in response", on the batch read and an independent re-read — so the Meta pool is CLOSED this run per the per-pool gate and blocked only itself; OpenRouter monthly remains walled at a known 100%; claude read failed "Rate limited. Retrying soon." and antigravity/opendesign are not configured — those pools blocked only themselves.)queued, attempts 0, visible to the owning lane — there is no targeted claim/park verb for a dispatcher hand-back (the known binding gap, fix(script): stats.ts fetches have no deadline and buffer unbounded .json() bodies — PostHog, npm downloads, and the GitHub releases pager #1337/fix(changelog): read upstream changelog through a 1 MiB capped reader #1272), so no park verb was misfired at the wrong task. This comment is the hand-back record with reason classexecutor-saturated; the next dispatcher run with a free slot should dispatch it (Fast →opencode-route/speed— route id only, per Drew's 2026-10-29 route-only rule; the fix is mechanical and well-specified: prototype-safe media-type lookups in packages/ai/src/utils/media-type.ts with regression tests), or a lane may claim it if urgent.1 remaining item
dezren39 commentedon Oct 29, 2026
PR #909 opened (head ec0c891): both media-type lookups now guard with Object.hasOwn; regression tests pin constructor/proto/toString for mediaTypeExtension and fileMediaType. Laptop: bun test test/media.test.ts 10 pass / 0 fail. Task 3563 parked awaiting CI; merge on green + Codex clean.
dezren39 commentedon Oct 29, 2026
🤖 Agent note · done
packages/ai/src/utils/media-type.tsnow readEXTENSIONS/EXTENSION_ALIASESby own property (Object.hasOwn), soconstructor/__proto__/toStringresolve toundefinedand OpenAI transcription's existinginvalidRequestrejection applies again.ec0c891830, Codex review clean (has_any_findings=false); PR diff limited tomedia-type.ts+media.test.ts; laptopcd packages/ai && bun test test/media.test.ts→ 10 pass, 0 fail (103 expects). PR description rewritten into the repo template after the compliance bot's missing-sections warning.385248f8efd015fe604af357f7168ba0ad497c29(2026-10-29T19:41:42Z, squash); issue closed by the merge; branchqueue/3563-906-media-type-protodeleted. Removedqueue/parked,work/assigned,status/parked.385248f8fix(ai): guard bedrock-media MIME tables against prototype-chain reads (
fix(ai): guard speech finish-path tables against prototype-chain reads (