fix(@schematics/angular): escape module specifier in insertImport - #33722
fix(@schematics/angular): escape module specifier in insertImport#33722herdiyana256 wants to merge 2 commits into
Conversation
insertImport builds an `import ... from '<fileName>'` statement by interpolating fileName directly between single quotes with no escaping. A fileName containing a single quote breaks out of the string literal, so any caller that passes untrusted data as the module specifier (e.g. a schematic using addRootProvider's external() with a value sourced from user/project config) ends up inserting attacker-controlled source into the target file rather than a plain import path. fileName is now escaped for backslashes and single quotes before being interpolated, matching the existing single-quote output format so the common case (a real module specifier) is byte-for-byte unchanged.
There was a problem hiding this comment.
Code Review
This pull request updates the insertImport utility in ast-utils.ts to escape backslashes and single quotes in the import file name. The reviewer suggested extending this escaping logic to handle newline and carriage return characters to prevent syntax errors, and recommended adding unit tests to verify the escaping behavior.
Also escape \n and \r in the module specifier, since an unescaped newline would produce an unterminated string literal in the generated file. Adds two test cases covering quote/backslash and newline/ carriage-return escaping in insertImport's output.
|
Both addressed in 02bec13:
|
|
Thanks for this change. However, because this is a private utility, external attackers cannot control the input. Furthermore, if an attacker already has the ability to execute a schematic via the command line, the underlying system is already compromised, indicating much broader and more critical security vulnerabilities. |
|
Fair points, but I don't think either holds for this specific path. It's not a private utility. It's exported in "./utility": "./utility/index.js",
"./utility/*": "./utility/*.js",So // @angular/fire - src/schematics/setup/index.ts
import { addRootProvider } from '@schematics/angular/utility';
// src/schematics/utils.ts
external("connectorConfig", config.package)
And it doesn't need CLI access. The tainted value is a project data file, not the command. Someone runs The If you'd rather the check sit in the callers, I can move it there instead, but the shared utility is the place that removes the footgun for every consumer. |
`insertImport` (`packages/schematics/angular/utility/ast-utils.ts`) builds an `import ... from ''` statement by interpolating `fileName` directly between single quotes, with no escaping. A `fileName` containing a single quote breaks out of the string literal, so a caller that passes untrusted data as the module specifier ends up inserting attacker-controlled source into the target file instead of a plain import path.
This is reachable through `addRootProvider`'s `external(symbolName, moduleName)` (`utility/standalone/code_block.ts`), which calls `insertImport` with `moduleName` as `fileName` — any `ng add` schematic that calls `external()` with a module name sourced from project config rather than a hardcoded string inherits this. Confirmed with the real, unmodified `insertImport` against a `fileName` of ``x'; console.log('INJECTED'); const _y = 'y``: the generated file contained the `console.log(...)` call as live top-level code rather than an unusual import path, and running the generated file executed it.
`fileName` is now escaped for backslashes and single quotes before being interpolated, matching the existing single-quote output format so the common case (a real module specifier, which never contains a quote or backslash) is byte-for-byte unchanged — verified against the existing `insertImport` describe block in `ast-utils_spec.ts` by hand, all of which assert exact single-quoted output.
Note on verification: this repo's test suite runs via Bazel (`bazel test //packages/...`), which wasn't feasible to run end-to-end in the environment this fix was developed in. The escaping logic was verified standalone against the existing test cases' expected output strings and against the malicious-input case described above; happy to have CI confirm on this PR.