Skip to content

fix(cli): prevent code injection via filenames in loader/section codegen - #461

Open
0xcucumbersalad wants to merge 1 commit into
mainfrom
fix/codegen-filename-code-injection
Open

fix(cli): prevent code injection via filenames in loader/section codegen#461
0xcucumbersalad wants to merge 1 commit into
mainfrom
fix/codegen-filename-code-injection

Conversation

@0xcucumbersalad

@0xcucumbersalad 0xcucumbersalad commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Summary

generate-loaders.ts and generate-sections.ts build the emitted .gen.ts by interpolating filename-derived values (entry.key, importPath, rel) directly into string literals:

`  "${entry.key}": createLoaderEntry("${entry.key}", () => import("${entry.importPath}")),`

Those values were pasted raw — no escaping.

Impact

A repo file whose name contains ", ) or ; breaks out of the string literal into executable generated code, which runs on the next dev/build. Feeding an untrusted repo (or a merged PR adding such a file) through codegen = code execution on the dev/CI machine.

Example file committed to a site:

src/loaders/x");import('node:child_process').then(c=>c.execSync('id'));(.ts

lands in .deco/loaders.gen.ts as live TypeScript. (POSIX filenames can't contain / or NUL, but " ) ; ( are all legal — enough to break out.)

Fix

Emit every filename-derived value through JSON.stringify (correct string quoting + escaping) in both generators:

  • loaders: loader/action keys, the .ts alias key, and every import(...) specifier;
  • sections: sectionMeta keys + meta values, syncComponents/loadingFallbacks keys, the static import * as _syncN / import { LoadingFallback as _fbN } specifiers, and the --registry sectionImports map (./sections/<rel> + import(...)).

For ordinary paths JSON.stringify("a/b") === "a/b", so output is byte-identical for normal inputs — the 17 existing shape tests pass unchanged.

Tests

New codegen-injection.test.ts (2 tests): writes a loader and a section whose filename carries a zz");PWN;( breakout payload, runs each real generator, and asserts the emitted quote is escaped (zz\");PWN present) and the raw breakout never appears (zz");PWN absent). Full codegen suite:

Test Files  3 passed (3)
     Tests  19 passed (19)   # 2 new + 17 existing shape tests

Scope

Finding F6 from the source audit. Companion PRs: #459 (F5 git-clone command injection), #460 (F4 Supabase SQLi). Same class — untrusted input reaching a raw sink (shell / SQL / generated code) without escaping; each fixed by encoding at the boundary (argv array / validation+quote-escape / JSON.stringify).

🤖 Generated with Claude Code


Summary by cubic

Fixes code injection in CLI codegen by escaping all filename-derived values in generate-loaders.ts and generate-sections.ts. Malicious filenames could previously break out of string literals and execute code during dev/build.

  • Bug Fixes
    • Loaders: use JSON.stringify for loader/action keys, .ts alias keys, and all dynamic import(...) specifiers.
    • Sections: use JSON.stringify for sectionMeta keys/values, static import specifiers, syncComponents/loadingFallbacks keys, and --registry import map keys/specifiers.
    • Added regression tests that create hostile filenames and verify proper escaping; output for normal paths is unchanged and existing shape tests still pass.

Written for commit bd44575. Summary will update on new commits.

Review in cubic

generate-loaders.ts and generate-sections.ts built the emitted `.gen.ts`
by interpolating filename-derived values (entry.key, importPath, rel)
directly into string literals:

  `  "${entry.key}": createLoaderEntry("${entry.key}", () => import("${entry.importPath}")),`

Those values were pasted raw, so a repo file whose NAME contained `"`,
`)` or `;` broke out of the string literal into executable generated
code — run on the next `dev`/`build` (RCE on the dev/CI machine that
builds an untrusted repo).

Fix: emit every filename-derived value via JSON.stringify (proper string
quoting + escaping) in both generators — loader/action keys and import
specifiers, and section meta keys, static import specifiers, and the
--registry sectionImports map. For ordinary paths the output is
byte-identical to the previous double-quoted form, so existing shape
tests are unchanged.

Adds codegen-injection.test.ts: writes a loader and a section whose
FILENAME carries a quote/paren breakout payload, runs each real
generator, and asserts the emitted quote is escaped and the raw
breakout never appears.

Co-Authored-By: Claude Opus 4.8 (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