Repository navigation
Description
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 whosepriorityfield is a function.JSON.stringify({ p_priority: <that> })produces{}— the field is silently dropped, soQueueClient.enqueue(src/queue.ts) sends the enqueue RPC with nop_priorityat all instead of the request being rejected.priority: "__proto__"serializes asp_priority: {}, an object where the RPC expects the design-scale integer.buildPacket(src/packet.ts) interpolates the value intopriority_reason("resolved tofunction Object() { [native code] }…") and the/v1/taskscomment 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
- The priority is the queue's claim-ordering input (design scale 10–90,
validate.ts:11-16). Silently dropping it or sending{}means a caller who typo'd — or probed — a name gets a task at whatever the RPC does with a missing/object priority, with no error anywhere. - This is the same own-key-lookup failure class as the aggregator weights lookup (aggregator(weights): effortWeight's plain-object lookup returns prototype members as weights — 'effort/constructor' counts as sized and string-poisons every weighted summary #2734), in the dispatcher's own request boundary — the one place whose whole job is validating requests before they become queue tasks / launched sessions (cf. design(pm-dispatch): launch pipeline, staged — validate the whole request at the boundary, one commit point, nothing after it can fail the launch #2668's boundary-validation design).
Acceptance criteria
-
resolvePriority("constructor")andresolvePriority("__proto__")(any case/whitespace variant that lowercases/trims to a prototype key) throwValidationError(400, "unknown name"), like every other unknown name. - The fix uses an own-key check (e.g.
Object.hasOwn/ aMap/ 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_priorityfrom a named priority: a test throughparseSessionRequest/parseTaskRequestwithpriority: "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
dezren39 commentedon Oct 30, 2026
🤖 Agent note · filed
dezren39 commentedon Oct 30, 2026
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.hasOwnat 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 remaining items
dezren39 commentedon Oct 31, 2026
🤖 Agent note · picked up
Merge pull request #3105 from developing-today/queue/4373-2757-resolv…
dezren39 commentedon Oct 31, 2026
🤖 Agent note · done