Skip to content

fix: harden docs snippets, SVG sanitizer, lint regexes, GSAP writers and registry pins - #7512

Merged
michaelwang11394 merged 11 commits into
mainfrom
harden-docs-lint-registry
Nov 10, 2026
Merged

michaelwang11394 merged 11 commits into
mainfrom
harden-docs-lint-registry

Conversation

@michaelwang11394

@michaelwang11394 michaelwang11394 commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

What

Hardening across the docs site, the SVG sanitizer, lint, the GSAP writers and the registry:

  • Docs catalog preview: variable values are encoded for the inline script they land in. The install-command copy button quotes URL parameters for the shell.
  • SVG sanitizer: CR LF is folded before CSS escapes are decoded, so an escaped function name followed by a line break is still caught. The media-use bundle is regenerated.
  • Lint: the template-id rule no longer builds a backtracking regex from composition text, and the CSS-rule scanners run in linear time, including on a closed <style> holding many { with no }. The two gsap-rule scans use a small scanner that returns the same selector and body pairs as the old regex. Findings are identical on every tracked HTML file.
  • GSAP writers escape </script and <!-- in values they write into inline scripts, and refuse a tween method other than set/to/from/fromTo or a non-numeric motion-path value.
  • scripts/host-registry-assets.ts refuses paths outside the item and symlinks, with a test.
  • The five map blocks pin d3@7.9.0, us-atlas@3.0.1 and world-atlas@2.0.2 with subresource integrity, and a test rejects floating CDN versions.

Why

Each change keeps untrusted text (composition HTML, URL parameters, SVG input, registry paths) from becoming code or reaching files it should not, and keeps third-party scripts from changing under a floating version.

Related work

#3953 also edits the map blocks and will need a small rebase after this lands.

How

Local fixes in each module. d3 is pinned to 7.9.0, the long-standing release (7.9.1 and 7.9.2 were published days ago); the producer's d3 fixture already uses 7.9.0.

Test plan

  • Unit tests added/updated; each fails without its fix (the old template-id code hung past a 120 s timeout).
  • Lint findings are identical before and after the scanner change on all 1089 tracked HTML files (1633 findings). The scanner returns the same pairs as the old regex on 200,000 random inputs that include nested @media blocks. The full lint (1288) and parsers (1426) suites pass. scripts/registry-cdn-pins.test.ts passes.
  • The 7.9.0 integrity hash matches the CDN file. All five map blocks load in headless Chrome with d3 7.9.0 under that hash, draw their state or country paths, and log no errors.
  • The generated catalog pages and payloads are not committed; the catalog publish job regenerates them from the item sources.
  • Comment ratchet and pre-commit hooks pass.
  • fails without the fix: scans a closed style block of many unclosed rules in linear time (lint, 2870 ms against a 2000 ms limit with the previous gsap.ts), matches a many-placeholder template id against a long near-miss id in linear time (lint), drops: CSS-escaped image-set() followed by CR LF in a style element and ... in style= (core), the six escaping, tween-method and motion-path cases in gsapWriter.scriptContext.test.ts (parsers), a manifest path outside the item is refused before anything is staged or removed and an asset that is a symlink is refused before its target is read (scripts), shown by running each test file with the merge-base version of the source it covers. scripts/catalog-detail.test.ts does not load without the fix, because the helpers it extracts are absent. The form-feed sanitizer case already passes on the base; it is kept as a guard for the line-ending fold.
  • Not done here: the <script>/<style> lint regexes are still slow on very large unclosed input, server-side lint has no time budget yet, and the docs catalog preview is not moved to an opaque-origin sandbox (it would break the preview).

🤖 Generated with Claude Code

michaelwang11394 and others added 7 commits November 10, 2026 09:50
…vars argument

The catalog preview embeds variable values as JSON that cannot close a
script element or attribute, accepts value messages only from the page
that embeds it, and reads only declared ids with primitive values from
the link. The install command shell-quotes its --vars JSON and keeps
only variable-id keys with primitive values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…anitizer

A browser folds CR LF, CR and form feed into one newline before it reads
CSS escapes, so the whitespace after a hex escape can be a CR LF pair.
The sanitizer now does the same, so an escaped function name split by a
line ending is checked under its real name. The media-use bundle is
regenerated from the source.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A getElementById template literal is matched against document ids with
an in-order indexOf scan instead of a regex of joined wildcards, so the
time no longer grows with the number of placeholders. The CSS rule
scanners only start a match at the beginning of a brace-free run, which
finds the same rules without rescanning long brace-less text. Lint
results on every HTML file in the repository are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
String literals and quoted keys the GSAP writers emit now encode a
script closer or comment opener as \u003c, so a value can no longer end
the inline script that holds the timeline. Strings without those
sequences are written exactly as before. The writers also refuse a tween
method outside set, to, from and fromTo, and a motion path whose
coordinates or curviness are not numbers, since both are emitted as code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The asset hosting script now refuses a manifest path that leaves the
item directory or reaches its file through a symlink, before anything is
read, staged, uploaded or removed. A repository test fails when the
registry tracks a symlink or a manifest path that leaves its item.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The five map blocks load d3 7.9.2, the build the d3@7 range serves today,
with a subresource integrity hash, and fetch us-atlas 3.0.1 and
world-atlas 2.0.2, the files their ranges serve today. The bytes are
unchanged, so the blocks render the same. A repository test fails when a
registry item names an npm CDN package by range, tag or no version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7.9.0 has been the stable release since 2024; 7.9.1 and 7.9.2 were published days ago. Pin the
long-standing version, with its subresource integrity hash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mintlify

mintlify Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
hyperframes 🟢 Ready View Preview Nov 10, 2026, 6:24 PM

💡 Tip: Enable Automations to automatically generate PRs for you.

michaelwang11394 and others added 2 commits November 10, 2026 10:21
Restore the generated catalog pages and payloads, which the catalog
publish job regenerates from the item sources. Name the us-atlas URL
in us-map-flow so the call fits on one line and formatting is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 267 (base branch 267), smooth 90 of those

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (8)

Unstable (2)

  • seqrotatenudge-none-pct-r0-root-z100: tracking 0.03, pressJump 0, drop 0, reload 3.03, render 0.04, renderKey -, undo false, teleport true / tracking 0.03, pressJump 0, drop 0, reload 0, render 0.08, renderKey -, undo true, teleport true / tracking 0.03, pressJump 0, drop 0, reload 0, render 0.08, renderKey -, undo true, teleport true
  • seqplainkeys-none-px-r0-root-z100: tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: REQUEST CHANGES · 1 blocker · 0 important · 2 nits · head ae81778

  • blocker packages/lint/src/rules/gsap.ts:426 and :2354: both CSS-rule scans still use /(?<![^{}])([^{}]+)\{([^}]+)\}/g. The body [^}]+ can cross {, so the scan is quadratic. A closed <style> holding "x{".repeat(40000) (80 KB) takes about 4.7 s per regex, and lintHyperframeHtml takes about 10 s end to end (N=10k/20k: 271 → 1150 ms in plain node). This is a closed tag, not the "very large unclosed input" the body leaves out, and it contradicts "the CSS-rule scanners run in linear time". Fix: use a [^{}]+ body or an indexOf scan, rerun the lint-parity check (nested @media … { .a {…} attribution), and add an x{×N timing test.
  • nit sanitizeSvg.test.ts: the "CSS-escaped url() followed by a form feed" case passes on base, because the old [ \t\n\r\f]? already took a lone \f. Only the two CR LF cases are real regressions.
  • nit: the body has no fails without the fix: line naming tests.
Scope

I ran 29 SVG bypass probes (xlink/aliased javascript:, external <use>, foreignObject, animate/set, mixed case, escaped url()/@import/image-set, feImage, PI, entities, CDATA), and all were stripped or inert. Also checked: the GSAP writers (no bare JSON.stringify left), registry asset hosting (realpath + O_NOFOLLOW), and the d3@7.9.0 sha384, which matches jsDelivr. Docs filters drop no real registry entry. Tests pass: parsers 8/8, core sanitizeSvg 59/59, lint gsap 241/241, and the node --test scripts. CI is green.

— Genesis

michaelwang11394 and others added 2 commits November 10, 2026 11:33
The two CSS-rule scans in the gsap rules took a body that could cross
'{', so a closed style of many '{' with no '}' rescanned to its end from
every brace. A scanner now returns the same selector and body pairs and
remembers the next '}', so each scan is linear. Findings are identical
on every tracked HTML file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@michaelwang11394

Copy link
Copy Markdown
Contributor Author

Review follow-up:

  • blocker (gsap.ts CSS-rule scans quadratic on "x{".repeat(40000)): fixed in bcb4e00. Both scans now use a linear scanner that returns the same selector and body pairs as the old regex (checked on 200,000 random inputs with nested @media). Lint findings are identical on all 1089 tracked HTML files. New test scans a closed style block of many unclosed rules in linear time fails with the previous gsap.ts (2870 ms against a 2000 ms limit).
  • nit (form-feed sanitizer case passes on base): relabelled as a guard for the line-ending fold in 006da3d. Only the two CR LF cases are claimed as regressions.
  • nit (no fails without the fix: line): added to the PR body.

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: APPROVE — 2 resolved, 0 open, 0 new · ae81778 → 006da3d. styleRuleBodies is linear: 0 mismatches against the old regex on 300k random inputs of my own, and about 1 ms on x{/a{b{/}{/a{}×40k. The new timing test fails on the previous gsap.ts (9.0 s). lint gsap 242/242, sanitizeSvg 59/59. CI is green.

— Genesis

@michaelwang11394
michaelwang11394 added this pull request to the merge queue Nov 10, 2026
Merged via the queue into main with commit 24508ce Nov 10, 2026
148 checks passed
@michaelwang11394
michaelwang11394 deleted the harden-docs-lint-registry branch November 10, 2026 20:37
@miguel-heygen miguel-heygen mentioned this pull request Nov 10, 2026
3 of 4 tasks
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