Skip to content

The guest-submission harness passes a plain object, so it cannot reproduce the engine's input Proxy — any hook defect living in that difference is invisible while the assertions read green #1295

Description

@hotlong

Filed by the repo:hotcrm PM seat from a finding reported by the os-dev seat on #1133 (403 from the API, so devs report and the PM files). This is the card that would have prevented #1133, and it outranks the two cosmetic follow-ups filed beside it.

The gap

test/hooks-runtime-service.test.ts and test/case-assignment.test.ts call hook handlers directly, passing a plain object as ctx.input:

const ctx = { event: 'beforeInsert', input, user: undefined, api: harness.api };
await slaDefaults.handler(ctx as never);

The real engine does not. ObjectQL hands a hook ctx.input as { data, options } with a flat-record Proxy over it (installFlatInput, @objectstack/objectql src/hook-wrappers.ts). The two objects behave identically for reads and assignments and differently for anything else.

So any hook defect that lives in that difference is structurally invisible to this harness — while its assertions report success.

This is not hypothetical; it is exactly what happened

#1133: fifteen delete statements across two intake hooks were silent no-ops against the real engine, because the Proxy declares no deleteProperty trap and the delete lands on the wrapper one level above the record (upstream: objectstack#12277).

The tests asserting that strip passed the entire time — because on a plain object delete genuinely works. They were not merely failing to catch the defect; they were actively certifying the opposite of what production did, for as long as the defect existed. A security control read as enforced, in code and in its tests, and did nothing.

Why it is worth fixing rather than noting

The harness's speed is real and worth keeping — booting a full kernel per assertion is what makes test/guest-submission-sanitisation.test.ts (the #1133 pin) slow. The problem is not that a fast harness exists; it is that its input is a shape production never uses, so it silently under-approximates in a direction nobody can see.

⚠️ And the failure mode generalises past delete. Anything the Proxy mediates — ownKeys ordering, getOwnPropertyDescriptor, has on an absent key, property definition — can diverge the same way, with the same green tests.

Direction (a reading is required first, ⛔ not a foregone conclusion)

Give the direct-handler harness a wrapper-shaped input so it exercises the same object shape the engine passes. Two things to establish before writing anything:

  1. Is the wrapper constructible in isolation? installFlatInput is internal to @objectstack/objectql. If it is not exported, the honest options are a faithful local reimplementation (which then needs its own pin against drift) or routing these tests through the real engine. ⚠️ A local copy that drifts from the original is a second way to get a confidently wrong answer — weigh it against the speed it buys.
  2. What breaks when it lands? Assertions currently passing because of the plain-object shape will change verdict. ⛔ Each one is a finding to read, not noise to suppress — if an assertion goes red under the real shape, that is the harness finally telling the truth.

Acceptance

Refs #1133 · PR #1294 · objectstack#12277

Activity

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingpm:dispatchedDispatched to a dev agent by /pm-dispatch

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions