Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .changeset/real-quickjs-action-sandbox-harness.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
'hotcrm': patch
---

Run action and hook bodies through the REAL QuickJS sandbox in tests. The
action side had no sandbox harness at all — `test/global-actions.test.ts` ran
each `body.source` through `new Function`, which proves what a body computes
and is structurally blind to everything the runtime imposes on it: no module
scope, a JSON-only boundary, a capability gate, and a `ctx.api` the engine
builds rather than the author. `test/helpers/action-sandbox.ts` now hands the
same bodies to the runtime's own `QuickJSScriptRunner` +
`actionBodyRunnerFactory` / `hookBodyRunnerFactory`, over an ObjectQL-shaped
recording engine whose update contract is pinned against a real kernel on the
in-memory driver. `test/action-sandbox.test.ts` executes all seven script
action bodies, asserts the capability gate denies an undeclared capability, and
turns `src/objects/_line-item-price-fill.ts`'s comment-only constraint — the
shared factory is safe only while the handler body never reads a factory
parameter — into an executable guard, with a negative control that fails when
it is violated. Refs #575.
8 changes: 8 additions & 0 deletions src/objects/_line-item-price-fill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ import type { HookApi } from './_hook-api';
* plain metadata on the returned Hook, and the handler body below closes over
* NOTHING but its own `ctx`. Never move a value the body reads into this
* factory's parameters; it would resolve here and be undefined in the sandbox.
*
* That rule is enforced, not just documented: `test/action-sandbox.test.ts`
* lowers this handler with the CLI build's own extraction pass (which rejects a
* body referencing anything out of scope) and runs the result under the real
* QuickJS runner, with a negative control that shows the guard still bites.
* Worth knowing why a test has to: when extraction fails the build does not,
* it silently keeps the handler in its bundled form and the hook stops being
* deployable as pure metadata.
*/
export function createLineItemPriceFill(objectName: string, hookName: string): Hook {
return {
Expand Down
Loading
Loading