Skip to content

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

Closed

Description

@dezren39

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 named notes.constructor or notes.__proto__ whose bytes match no magic-byte sniffer gets EXTENSIONS["constructor"] (the Object function) or EXTENSIONS["__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 the Object function, mediaTypeExtension("__proto__") returns Object.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 becomes audio.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 pass fileMediaType(data, path) into Media.bytes, whose mediaType is typed string (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(...) === undefined is the designed rejection ("cannot name a … upload"); for "constructor"/"__proto__" the check is bypassed and the upload filename becomes audio.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-reads gate (script/check-proto-reads.ts) only scans packages/core/src, which is why this class survived in packages/ai after 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__") and mediaTypeExtension("toString") return undefined; real aliases (e.g. audio/mp3 → mp3) unchanged.
  • fileMediaType(bytes, "x.constructor") and fileMediaType(bytes, "x.__proto__") return undefined for unsniffable bytes; known extensions unchanged.
  • OpenAI transcription again rejects an unnameable media type with its existing invalidRequest message for these names.
  • Regression tests in packages/ai test suite next to the existing media-type tests pin all three names for both functions.
  • Fix uses an own-property guard (the ownValue/ownOr pattern from audit(core): shared own-property guard + regression net for prototype-chain dictionary reads (#833 follow-up) #865, or Object.hasOwn) — not a blocklist of names.

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    task/small-fixQueue packet kind: bounded fix with reproduction and targeted tests
    source/creatorFiled by the new-issue-creator job
    on Oct 29, 2026
  2. developing-today commented on Oct 29, 2026

    @developing-today
  3. added
    priority/lowNice to have; do when convenient
    queue/queuedHas a queued task in the Supabase agent queue
    app/aiPackage: ai (pkgs/ or apps/ in this repo)
    effort/xsTrivial: expected under 30 minutes of work
    on Oct 29, 2026
  4. dezren39 commented on Oct 29, 2026

    @dezren39
    Author

    🤖 Agent note · filed

    • Agent: new-issue-creator-opencode
    • Session / ID: new-issue-creator-opencode cron run 2026-10-29 12:35 CDT
    • Found via: filed this run
    • Task: Filed after verifying the prototype-chain media-type lookups in packages/ai/src/utils/media-type.ts against main 80225ac (laptop probe); queue packet enqueued for triage/fix.
    • Expectation: effort/xs — own-property guard on both lookups plus regression tests; done when the acceptance criteria on the issue pass.
    • Links: packet packet-opencode-906.json (hidden_files)
  5. dezren39 commented on Oct 29, 2026

    @dezren39
    Author

    🤖 Dispatcher hand-back — executor-saturated · openchamber-dispatcher run 2026-10-29 12:45 CDT

    • Queue task: 3563 (repo-health-opencode-pilot, 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 via gh, 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); task queued att 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 via gh 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).
    • Why handed back: the laptop OpenChamber location is over its practical cap — 53 live sessions against the cap of 12 (Drew 2026-10-27) on this run's three identical /api/session-activity reads 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.
    • Pools were OPEN (not the blocker): fresh laptop /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.)
    • Queue state: the task remains 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 class executor-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.
  6. added
    queue/claimedA queue worker has claimed the task for this item
    work/assignedAn agent holds this item via the queue (claimed task)
    and removed on Oct 29, 2026
  7. 1 remaining item

  8. added
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    status/parkedDeliberately paused with a recorded resume condition
    and removed
    queue/claimedA queue worker has claimed the task for this item
    on Oct 29, 2026
  9. dezren39 commented on Oct 29, 2026

    @dezren39
    Author

    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.

  10. removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    work/assignedAn agent holds this item via the queue (claimed task)
    on Oct 29, 2026
  11. dezren39 commented on Oct 29, 2026

    @dezren39
    Author

    🤖 Agent note · done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    app/aiPackage: ai (pkgs/ or apps/ in this repo)effort/xsTrivial: expected under 30 minutes of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/lowNice to have; do when convenientsource/creatorFiled by the new-issue-creator jobstatus:donetask/small-fixQueue packet kind: bounded fix with reproduction and targeted tests

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions