Skip to content

Security: route look-alikes, invalid mode, stale sample bucket and prototype names closed on main; advisory draft - #123

Merged
Karanvir1729 merged 5 commits into
mainfrom
security/advisory-mode-raise
Oct 28, 2026
Merged

Karanvir1729 merged 5 commits into
mainfrom
security/advisory-mode-raise

Conversation

@Karanvir1729

Copy link
Copy Markdown
Collaborator

What

The adversarial review of the 0.2.3 backport found four more ways page-controlled or misconfigured input could widen GenClass's behaviour. They are fixed in 0.2.3 (tag pushed, release waiting for approval) and main had the same code paths, so this ports them, line for line where the code matched:

  1. Route rules. A routes[] rule now matches the raw location.pathname or its normalised form (percent-decoded in a try/catch, ; params stripped, dot segments and repeated slashes resolved, no trailing slash), so /se%61rch, /search/, //search and /search;x=1 can no longer step out of a rule that scopes /search to off or observe. scope.route stays raw. (util/match.ts normalizeRoute, runtime.ts computeScope.)
  2. Invalid mode. Anything but observe, guard or heal now runs as observe with one warning, in the constructor and in readWindowConfig; modeAllows fails closed for anything but guard or heal. Before, an unknown value ran as heal.
  3. Sample bucket. sample: 0 never reads sessionStorage; the bucket is stored as act@<sample> or observe@<sample> and reused only under the same sample, so a stored act cannot lift a sampled-out session.
  4. Aggressiveness names. aggressivenessLevel checks names with includes, so "constructor" and other prototype names never yield a function.

Also: the draft security advisory docs/security/2026-10-mode-raise.md (the mode-raise issue plus these four, with affected versions checked per tag, a CVSS 3.1 vector with its reasoning, workarounds and a timeline; marked DRAFT for the owner to publish through GitHub's advisory form) and an Advisories line in SECURITY.md. Docs: OPTIONS-SPEC, API.md, THREAT-MODEL T8, INTERCEPTION.md, the runtime README, RESULTS.md section 10, and a CHANGELOG entry under Unreleased (0.3.0-beta.5) with a Security section. No version bump.

Tests

29 new or extended tests fail on main's sources and pass here (status-scope 14, options 13, gates 1, policy 1; 2 more pin unchanged behaviour). Full suite: 1,242 passed, 14 skipped (model parity). review-perf 4 of 4. tsc clean, tsup builds. No model-visible text changed; no review test edited.

Needs the owner's OK

  • First-load limit. The fixes add 280 B gzip to /auto (108.63 KB), 33 B over bundle.test.ts's 108.6 KB ceiling, so the limit is raised to 108.7 KB in this PR, the way earlier branches did pending your OK. The alternative is trimming about 35 B of warning text, which would make 0.3.0's strings differ from 0.2.3's.
  • Still stale after this PR and left alone (binding or out of scope): CONTRACT.md line 540, the README's ?genclass-sample=1 plus debug recipe, INTERCEPTION.md line 205; listed in the advisory's owner notes.

Not run: the browser and Playwright suites, smoke, the install suite and the visual bench (CI or ask-first).

🤖 Generated with Claude Code

Karanvir1729 and others added 5 commits October 27, 2026 23:36
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…kes, invalid mode, stale sample bucket, level names)

Ports commit 8694211 (release/0.2.3, tag v0.2.3) onto main for 0.3.0-beta.5; the 0.3.0 code differs, so the port is
by hand. The version is not bumped.

- routes[]: a rule matches the raw location.pathname or its normalised form (percent-decoded in try/catch, no ";"
  parameters, dot segments and repeated slashes resolved, no trailing slash), so /se%61rch, /search/, //search and
  /search;x=1 cannot step out of a rule that scopes /search to off or observe; scope.route stays raw
  (util/match.ts normalizeRoute, runtime.ts computeScope).
- mode: any value but observe, guard or heal runs as observe with one warning (the runtime constructor, and
  cdn/config.ts readWindowConfig with a warn sink from auto-start.ts and global.ts); decide/policy.ts modeAllows fails
  closed for anything but guard or heal (an unknown value ran as heal).
- sample: 0 never reads sessionStorage; the bucket is stored as act@<sample> / observe@<sample> and reused only under
  the same sample (runtime.ts sampleBucket).
- aggressivenessLevel checks the level names with includes, so Object.prototype names such as "constructor" never
  yield a function (decide/policy.ts).
- Tests ported: status-scope (15 new), options (14 new), gates (1 new), policy (1 extended): 30 new tests, 29 of
  which fail on origin/main's sources (checked by checking out origin/main's src, running the four files: 14 + 13 + 1
  + 1 failures, then restoring).
- Docs: OPTIONS-SPEC §3 (mode validation; the URL-overrides bullet now says what 0.3.0-beta.2 left in the code), §4.2,
  §4.3; API.md; THREAT-MODEL T8 (mitigations aligned with the code, the new findings, the residual with devtools
  "local"); INTERCEPTION.md; README (data-devtools="local" hosts); types.ts JSDoc; CHANGELOG "Unreleased
  (0.3.0-beta.5)" with a Security section; RESULTS.md §10 first-load rows; SECURITY.md advisories bullet;
  docs/security/2026-10-mode-raise.md gains F1 to F4 with affected and patched versions, workarounds, timeline and the
  CVSS note (the score is unchanged).
- test/bundle.test.ts: the /auto first-load limit 108.6 -> 108.7 KB, pending the owner's OK as the earlier limits
  were: the fixes add 0.27 KB to /auto and 0.17 KB to the main entry (test/first-load-size.mjs against main 49c8c03's
  dist: 108.63 / 100.46 KB, under the 0.5 KB growth check; script tag 128.83 KB gzip, +0.28 KB).

Checked: tsc clean, tsup builds, vitest 1242 passed and 14 skipped (102 files), review-perf 4 of 4 when run alone (one earlier run
missed the 5,000-entity redux dispatch budget by 0.2-0.5 ms under load; store dispatch is untouched here). No
model-visible text changed (situation-v2.3); no review test edited.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…, 108.9 KB

This Mac's Node 25 gzips /auto to 108.63 KB; CI's Node 22 measured 108.83 KB, over the 108.7 KB set
here, so the limit is 108.9 KB. The growth from the four fixes is 0.28 KB either way, under the
0.5 KB first-load-size check against the release line.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Karanvir1729
Karanvir1729 merged commit 0ca84ee into main Oct 28, 2026
MeharPro pushed a commit that referenced this pull request Oct 28, 2026
…on Node 22), 100.6 KB

CI measured index.js at 100.55 KB after #123 (this Mac's Node 25: 100.46 KB), over the 100.48 KB
ceiling, for the same zlib reason as /auto. The growth from the four fixes is 0.15 KB, under the
0.5 KB first-load-size check against the release line, which passed on the same run.

Co-Authored-By: Claude Fable 5.1 <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