Skip to content

pm-dispatch(validate): resolvePriority's plain-object lookup returns prototype members as priorities — 'constructor' becomes the Object constructor function and '__proto__' an object, instead of the documented 400 #2757

Closed

Description

@dezren39

Problem

pm-dispatch: resolvePriority resolves a named priority through a plain-object lookup with no own-key check, so two prototype spellings are accepted as priorities instead of failing with the documented 400 — and the value that reaches the queue is not a number at all.

pm-stack/dispatch/src/validate.ts:28-48 (main 58635f8d):

export const PRIORITY_NAMES: Record<string, number> = {
  critical: 10, high: 20, normal: 40, low: 70,
};
...
if (typeof input === "string") {
  const v = PRIORITY_NAMES[input.trim().toLowerCase()];
  if (v === undefined) { throw new ValidationError(`priority: unknown name ...`); }
  return v;
}

PRIORITY_NAMES["constructor"] is the Object constructor function and PRIORITY_NAMES["__proto__"] is Object.prototype — neither is undefined, so neither throws.

Verified (this run, real module)

Probe on the laptop (~/git/fleet/probe-2026-10-30-laneB-priorityproto/, validate.ts copied verbatim from main, md5 c1f61d5acf5c85da71de798f4c7056b8, run under bun 1.4.2):

input result
"high" / "normal" (controls) 20 / 40 (number) ✓
"bogus" (control) ValidationError 400 ✓
"constructor" returns the Object constructor function (typeof === "function")
"__proto__" returns Object.prototype (typeof === "object")
"toString" / "hasOwnProperty" 400 — they only escape because toLowerCase() changes their spelling; constructor/__proto__ are already lowercase

Downstream, through the real parseSessionRequest:

  • priority: "constructor" yields a session/task request whose priority field is a function. JSON.stringify({ p_priority: <that> }) produces {} — the field is silently dropped, so QueueClient.enqueue (src/queue.ts) sends the enqueue RPC with no p_priority at all instead of the request being rejected.
  • priority: "__proto__" serializes as p_priority: {}, an object where the RPC expects the design-scale integer.
  • buildPacket (src/packet.ts) interpolates the value into priority_reason ("resolved to function Object() { [native code] }…") and the /v1/tasks comment path interpolates it as [object Object].

So a documented-error case (unknown priority name → 400, validate.ts:36-38) instead becomes a dropped/mistyped queue priority and garbage provenance text — the request is never told its priority was invalid.

Why it matters

Acceptance criteria

  • resolvePriority("constructor") and resolvePriority("__proto__") (any case/whitespace variant that lowercases/trims to a prototype key) throw ValidationError (400, "unknown name"), like every other unknown name.
  • The fix uses an own-key check (e.g. Object.hasOwn / a Map / a null-prototype table) rather than a denylist of the two spellings, so no other inherited member can leak through.
  • Regression tests in pm-stack/dispatch/test/validate.test.ts: the two spellings above 400; the four real names and the numeric scale/snap behaviour are unchanged.
  • No non-number can reach Packet.priority / p_priority from a named priority: a test through parseSessionRequest/parseTaskRequest with priority: "constructor" 400s instead of producing a request object.

Dedup

Searched open + recently closed issues for resolvePriority, prototype/constructor priority, and dispatch priority validation. #2679 covers the composite actions sending a numeric priority as a JSON string (different mechanism, action side); #2734 is the same failure class in the aggregator's weights.ts (different module, already filed separately per app). No filing covers the dispatch lookup.

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    priority/normalOrdinary priority (the default)
    queue/queuedHas a queued task in the Supabase agent queue
    source/creatorFiled by the new-issue-creator job
    task/triageQueue packet kind: verify, reproduce, and route this item
    app/pm-stackpm-stack (Pad/Grist) in developing-today/code
    effort/xsTrivial: expected under 30 minutes of work
    on Oct 30, 2026
  2. dezren39 commented on Oct 30, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

    • Agent: new-issue-creator (lane B, new-issue-creator-code-b)
    • Session / ID: cron new-issue-creator-code-b, run 2026-10-30 08:50 CDT
    • Found via: filed this run
    • Task: Triage the resolvePriority prototype-lookup finding: pin the own-key fix shape, then route a small_fix packet.
    • Expectation: effort/xs — done when 'constructor'/'proto' 400 like other unknown names, with regression tests, and no non-number can reach Packet.priority.
    • Links: probe on laptop ~/git/fleet/probe-2026-10-30-laneB-priorityproto/ (real validate.ts, md5-matched to main 58635f8)
  3. dezren39 commented on Oct 30, 2026

    @dezren39
    MemberAuthor

    agent-note:v1 — design-architect pass 21 (2026-10-30): this issue is ADOPTED as Phase 3 of design #2792 ("own keys only — a plain object is not a map for untrusted keys", doc files/designs/code-own-key-lookups.md). It is linked as a sub-issue of #2792. Triage: do NOT re-file, re-scope, or split — record the phase structure instead. Phase decisions recorded on #2792: the fix is the own-key rule (Object.hasOwn at the read site — the #2787 embodiment, closed today in id/web); a prototype spelling must produce the documented 400, exactly like a genuinely unknown priority name. Phase 3 also carries the design's closing audit (inventory sweep for remaining plain-object tables indexed by dynamic keys, vendored assets excluded), which closes #2792 with its evidence — the audit belongs with this phase, not as a separate issue.

  4. removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Oct 31, 2026
  5. 4 remaining items

  6. added
    queue/queuedHas a queued task in the Supabase agent queue
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    queue/queuedHas a queued task in the Supabase agent queue
    on Oct 31, 2026
  7. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  8. added a commit that references this issue on Oct 31, 2026
  9. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

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

Metadata

Metadata

Assignees

Labels

app/pm-stackpm-stack (Pad/Grist) in developing-today/codeeffort/xsTrivial: expected under 30 minutes of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/normalOrdinary priority (the default)source/creatorFiled by the new-issue-creator jobsource/design-architectFiled by the repo design architect jobtask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions