Skip to content

Slice 2: recipe core (parse, lint, render, verify) - #6

Open
rvegajr wants to merge 1 commit into
slice-1/resetfrom
slice-2/recipe-core
Open

rvegajr wants to merge 1 commit into
slice-1/resetfrom
slice-2/recipe-core

Conversation

@rvegajr

@rvegajr rvegajr commented Sep 30, 2026

Copy link
Copy Markdown
Member

Stacked on #5. Spec sentences from docs/IMPLEMENTATION.md §4 Slice 2.

What

All in packages/core/src/recipe/ (pure, no I/O):

  • parseRecipe: structural validation. Covers kebab-case name, version: 1, draft/approved status, auth, the origins list (https://host[:port]), typed params (string/int/enum, with checked defaults), steps as argv arrays ending in ["eval", code], verify and regression.
  • lintRecipe: the security rules.
    • Command allowlist: cookies, storage, network, download, upload, auth, state, snapshot and screenshot are rejected.
    • No {{…}} inside eval code.
    • No @eN snapshot refs.
    • open URLs must stay inside origins.
    • Read-only eval heuristics: fetch, XHR, sendBeacon, WebSocket, import(, document.cookie, local/session storage, location assignment/assign/replace, window.open, .submit(.
    • No fills into password/token/OTP/card fields.
    • No :nth-of-type built from a param.
  • renderRecipe:
    • resolves defaults and types;
    • applies the lower/underscore/path/url filters;
    • rejects unknown or missing params, values that would start an argv element with -, and URLs outside origins (including github.com.evil.test);
    • wraps eval code as (function(params){ return eval(code) })(JSON.parse(...)) and base64-encodes it, so values are never pasted into code.
  • verifyResult: required fields, regex match with params regex-escaped, non-string values compared JSON-style, and a miss when the result isn't a JSON object.

The eval-wrapping test runs the rendered code in node:vm with a param containing quotes, backslashes, backticks, ${} and </script>, and checks the round trip.

Tests

  • npm run check: 4 files, 35 tests (22 new), import boundary clean, secret scan clean, typecheck clean.

🤖 Generated with Claude Code

A recipe is a list of agent-browser argv steps ending in an eval that
returns a JSON object, with typed params, an origins allowlist, and verify
rules. All of it is pure and lives in @tyto/core.

- parseRecipe validates structure (kebab name, origins, typed params,
  last step eval, verify, regression).
- lintRecipe enforces the security rules: command allowlist, no {{…}}
  inside eval code, no @en snapshot refs, open URLs inside origins,
  read-only eval heuristics, no password/token/OTP/card fills, no
  :nth-of-type built from a param.
- renderRecipe resolves defaults and types, applies lower/underscore/
  path/url filters, refuses values that would become flags or leave the
  origins, and wraps eval code so it reads params from a JSON literal
  (proven in a sandbox against quotes, backslashes, and </script>).
- verifyResult checks required fields and regex matches with params
  escaped, comparing non-strings JSON-style.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant