Skip to content

fix(cli): confine user paths, close DoS vectors, and repair broken paths - #4637

Merged
josephfarina merged 4 commits into
mainfrom
fix/security-path-confinement
Aug 3, 2026
Merged

fix(cli): confine user paths, close DoS vectors, and repair broken paths#4637
josephfarina merged 4 commits into
mainfrom
fix/security-path-confinement

Conversation

@josephfarina

@josephfarina josephfarina commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes the critical and high-priority defects found by fuzz/chaos testing the
CLI after the authoring consolidation. Three commits, grouped by theme.

Security — confine user-controlled file paths (commit 1)

  • theme build --out and theme build <file> were unconfined — they could
    write or read outside the project root. Now guarded with assertWithin
    (parity with theme add).
  • validate-integration manifest roots (components / templates /
    codemods): unconfined — a manifest could point codemods at ../somewhere
    and the validator would import and execute files outside the package (remote
    code execution). Now guarded; an escaping path reports a validation issue
    instead of executing.
  • layout --file accepted any path and read it unbounded (e.g. /dev/zero
    → out of memory). Now confined, size-capped at 5 MB, and rejects non-files.

Denial-of-service / resource exhaustion (commit 2)

  • Fuzzy-match (Levenshtein): a very long search query caused multi-second
    CPU spins. Added an early-exit when the length difference is large, dropping a
    10k-char search from ~17s to under 1s while preserving real typo suggestions.
  • Layout value parser: added a recursion-depth cap so a deeply nested
    attribute value can't blow the stack.
  • Layout expander: capped the repeat count so an expression like
    Box*999999999 can't exhaust the heap.

Broken paths + prototype pollution (commit 3)

  • Docs topic lookup: switched to a null-prototype map so __proto__ /
    constructor as a topic name can't bypass the unknown-topic guard (same
    pattern as a prior fix).
  • Sandbox registry generator: it pointed at a directory removed in the
    reorg, so every command was written out as "(failed to load)". Repointed to
    the current CLI source location.
  • Shipped getting-started docs: corrected the consumer bin path that
    consumers copy into their package.json scripts (the old path no longer
    exists after the reorg).

Stack

This is the bottom of a 3-PR stack:

  1. fix(cli): confine user paths, close DoS vectors, and repair broken paths #4637 (this PR) — security + DoS + broken paths
  2. fix(cli): make the API tolerate null options and context arguments #4638 — API robustness against null arguments
  3. fix(cli): flag collision, stale bundled themes, and codemod edge cases #4639 — flag collision, stale bundled themes, codemod edge cases

Test plan

  • Modified files syntax-check clean
  • CI green (build / lint / test / docsite)

Close four path-confinement gaps found by chaos testing:

- theme build --out: options.out was passed to path.resolve with no guard,
  allowing writes outside the project root (../ESCAPE.css). Now uses
  assertWithin (parity with theme add).
- theme build <file>: the input file path was similarly unconfined, allowing
  reads of arbitrary files. Now confined.
- validate-integration manifest roots: components/templates/codemods root
  values from an integration manifest were resolved against the package dir
  with no confinement — a manifest could point codemods at '../OUTCM' and
  the validator would import+execute files outside the package (RCE).
  Now guarded with assertWithin; escapes report a 'root_outside_package'
  validation issue instead of executing.
- Same fix in foundation/integrations/integrations.mjs (the load path used
  by upgrade --integration).
- layout --file: readFileSync accepted any path (including /dev/zero →
  OOM). Now confined + size-capped at 5 MB, rejects non-files.

assertWithin already existed and was used by theme-add, swizzle, template,
init, and upgrade --path — these were the unguarded outliers.
- levenshtein: early-exit when |len(a)-len(b)| > 3 (all callers use
  thresholds ≤5, so the sentinel 999 is safe). Drops search@10k from ~17s
  to <1s while preserving real typo suggestions. Closes the DoS where a
  3k+ char query caused multi-second CPU spins (8,860 lev calls per search).

- XLE parseValue: add depth counter (MAX_VALUE_DEPTH=64) to prevent stack
  overflow on deeply nested attribute values ({a:{a:{a:...}}}). The existing
  MAX_COMPACT_DEPTH guard covers tree nesting but missed parseValue recursion.

- XLE expand: cap repeat count at MAX_REPEAT=10000 to prevent heap OOM on
  B*999999999. The parser accepts any *N but the expander now clamps it.

- layout --file: size-cap (5MB) and reject non-files before readFileSync,
  preventing OOM from /dev/zero (already confined by PR1's assertWithin).
@vercel

vercel Bot commented Aug 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
astryx Ready Ready Preview Aug 3, 2026 5:34pm

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Aug 1, 2026
@josephfarina
josephfarina force-pushed the fix/security-path-confinement branch from 6067a86 to e1cb823 Compare August 1, 2026 17:11
@josephfarina
josephfarina force-pushed the fix/security-path-confinement branch from e1cb823 to b2a7ec1 Compare August 1, 2026 17:23
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

🧪 Sandbox Preview

View Sandbox for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

No new or modified components detected.

Bundle Size Summary

No component packages changed.

Accessibility Audit

Status: No accessibility violations detected.


Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

…ed docs (F4/F7/F12)

- docs _adapter: use Object.create(null) for the topic lookup map so
  __proto__/constructor don't bypass the unknown-topic guard and leak
  ERR_INVALID_ARG_TYPE. Same pattern as #912.

- sandbox generate-cli-registry: CLI_SRC repointed from the deleted
  packages/cli/src to packages/cli/clients/cli. The script was silently
  writing a degenerate cliRegistry.ts with all commands '(failed to load)'.

- getting-started + working-with-ai docs: fix the consumer bin path
  from bin/astryx.mjs (deleted) to clients/cli/bin/astryx.mjs (real).
  Consumers copy this into their package.json scripts.
@josephfarina
josephfarina force-pushed the fix/security-path-confinement branch from c09b908 to ea91397 Compare August 1, 2026 17:47
github-actions Bot added a commit that referenced this pull request Aug 1, 2026
github-actions Bot added a commit that referenced this pull request Aug 1, 2026
@josephfarina josephfarina changed the title fix(cli): security + robustness fixes from chaos testing (F4/F5/F7/F8/F9/F12/F13/F14/F15) fix(cli): confine user paths, close DoS vectors, and repair broken paths Aug 2, 2026
… changeset

The DoS early-exit used |m-n| > 3, but the hook suggester keeps matches at
distance <= 5, so a hook name 4-5 edits away was dropped from suggestions.
Widen the cutoff to > 5 (still O(1), same DoS protection) and pin it with
tests. Add the missing changeset for this consumer-facing CLI fix.
@josephfarina
josephfarina merged commit ddda8bf into main Aug 3, 2026
19 checks passed
github-actions Bot added a commit that referenced this pull request Aug 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant