From a85820c277b7ee70de3769487f2446cf6c8b3386 Mon Sep 17 00:00:00 2001 From: Ian Hou <45278651+iankhou@users.noreply.github.com> Date: Wed, 26 Aug 2026 17:26:36 +0000 Subject: [PATCH 1/3] chore: ban direct child_process and shell:true outside the subprocess tool Locks in the subprocess consolidation (#1763, #1849) so new code can't regress to spawning outside the shared tool. - no-restricted-imports: ban `child_process` / `node:child_process` - no-restricted-syntax: ban `shell: true` in spawn options Only `run`/`runSync`/`runUserCommandLine` from the subprocess tool remain as spawn paths. The tool itself carries a localized eslint-disable at the two sanctioned lines. Test files are exempt (they mock/spy on child_process). integ-runner and cli-integ have temporary, tracked per-file overrides pending their migration onto the tool. cdk-build-tools is not managed by the root projen config, so the rule does not reach it yet; tracked as a follow-up. --- .eslintrc.json | 32 +++++++++++ .projenrc.ts | 34 ++++++++++++ .../@aws-cdk-testing/cli-integ/.eslintrc.json | 54 ++++++++++++++++++- .../@aws-cdk/cdk-assets-lib/.eslintrc.json | 35 +++++++++++- packages/@aws-cdk/cdk-explorer/.eslintrc.json | 35 +++++++++++- .../cli-plugin-contract/.eslintrc.json | 35 +++++++++++- .../cloud-assembly-api/.eslintrc.json | 35 +++++++++++- .../cloud-assembly-schema/.eslintrc.json | 35 +++++++++++- .../cloudformation-diff/.eslintrc.json | 35 +++++++++++- packages/@aws-cdk/integ-runner/.eslintrc.json | 48 ++++++++++++++++- .../@aws-cdk/private-tools/.eslintrc.json | 35 +++++++++++- .../private-tools/lib/subprocess/index.ts | 2 + packages/@aws-cdk/toolkit-lib/.eslintrc.json | 32 +++++++++++ .../@aws-cdk/user-input-gen/.eslintrc.json | 35 +++++++++++- packages/@aws-cdk/yarn-cling/.eslintrc.json | 35 +++++++++++- packages/aws-cdk/.eslintrc.json | 32 +++++++++++ packages/cdk-assets/.eslintrc.json | 35 +++++++++++- packages/cdk/.eslintrc.json | 35 +++++++++++- projenrc/eslint/imports.ts | 11 ++++ projenrc/eslint/index.ts | 26 +++++++++ projenrc/eslint/team.ts | 8 +++ 21 files changed, 651 insertions(+), 13 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index e26cd5d07..b17cc2b18 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -222,6 +222,10 @@ { "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", "message": "Use the md5hash() function from the core library if you want md5" + }, + { + "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", + "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ @@ -252,6 +256,14 @@ { "name": "punycode", "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" + }, + { + "name": "child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." + }, + { + "name": "node:child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." } ], "patterns": [ @@ -320,6 +332,26 @@ ] }, "overrides": [ + { + "files": [ + "**/test/**", + "**/tests/**", + "**/*.test.ts", + "**/*.integtest.ts", + "projenrc/**", + "**/projenrc/**", + ".projenrc.ts", + "**/.projenrc.ts" + ], + "rules": { + "no-restricted-imports": [ + "off" + ], + "no-restricted-syntax": [ + "off" + ] + } + }, { "files": [ ".projenrc.ts" diff --git a/.projenrc.ts b/.projenrc.ts index 5ed4f49d1..2f877916e 100644 --- a/.projenrc.ts +++ b/.projenrc.ts @@ -1565,6 +1565,19 @@ integRunner.preCompileTask.prependExec('./build-tools/generate.sh'); new TypecheckTests(integRunner); fixupTestTask(integRunner); +// integ-runner is integration-test tooling, and its `exec()` already spawns an +// argv array with no shell (so it carries no injection risk — the ban only +// flags it for importing child_process directly). Exempt it for now; migrating +// it onto the shared subprocess tool is deferred follow-up work. Scoped to the +// one sink so any NEW file still hits the ban. +integRunner.eslint?.addOverride({ + files: ['lib/utils.ts'], + rules: { + 'no-restricted-imports': ['off'], + 'no-restricted-syntax': ['off'], + }, +}); + new BundleCli(integRunner, { externals: { dependencies: [ @@ -1688,6 +1701,27 @@ const cliInteg = configureProject( ); cliInteg.eslint?.addIgnorePattern('resources/**/*.ts'); +// cli-integ is a test harness that deliberately spawns through a shell (and a +// pty) to exercise the CLI the way a user would at a terminal — that is the +// point of these helpers, not something to migrate away. Permanently exempt, +// like test code. Scoped to the current sinks so NEW files still hit the ban +// and get a conscious decision rather than a silent pass. +cliInteg.eslint?.addOverride({ + files: [ + 'lib/cli/stage-distribution.ts', + 'lib/npm.ts', + 'lib/package-sources/repo-tools/npm.ts', + 'lib/process.ts', + 'lib/shell.ts', + 'lib/with-cdk-app.ts', + 'lib/with-sam.ts', + ], + rules: { + 'no-restricted-imports': ['off'], + 'no-restricted-syntax': ['off'], + }, +}); + cliInteg.deps.addDependency('@aws-cdk/toolkit-lib', pj.DependencyType.OPTIONAL); const compiledDirs = ['tests', 'test', 'lib']; diff --git a/packages/@aws-cdk-testing/cli-integ/.eslintrc.json b/packages/@aws-cdk-testing/cli-integ/.eslintrc.json index 12cf1184f..cbb18f88f 100644 --- a/packages/@aws-cdk-testing/cli-integ/.eslintrc.json +++ b/packages/@aws-cdk-testing/cli-integ/.eslintrc.json @@ -123,6 +123,10 @@ { "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", "message": "Use the md5hash() function from the core library if you want md5" + }, + { + "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", + "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ @@ -153,6 +157,14 @@ { "name": "punycode", "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" + }, + { + "name": "child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." + }, + { + "name": "node:child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." } ], "patterns": [ @@ -318,5 +330,45 @@ "off" ] }, - "overrides": [] + "overrides": [ + { + "files": [ + "**/test/**", + "**/tests/**", + "**/*.test.ts", + "**/*.integtest.ts", + "projenrc/**", + "**/projenrc/**", + ".projenrc.ts", + "**/.projenrc.ts" + ], + "rules": { + "no-restricted-imports": [ + "off" + ], + "no-restricted-syntax": [ + "off" + ] + } + }, + { + "files": [ + "lib/cli/stage-distribution.ts", + "lib/npm.ts", + "lib/package-sources/repo-tools/npm.ts", + "lib/process.ts", + "lib/shell.ts", + "lib/with-cdk-app.ts", + "lib/with-sam.ts" + ], + "rules": { + "no-restricted-imports": [ + "off" + ], + "no-restricted-syntax": [ + "off" + ] + } + } + ] } diff --git a/packages/@aws-cdk/cdk-assets-lib/.eslintrc.json b/packages/@aws-cdk/cdk-assets-lib/.eslintrc.json index a7966375f..c832c334d 100644 --- a/packages/@aws-cdk/cdk-assets-lib/.eslintrc.json +++ b/packages/@aws-cdk/cdk-assets-lib/.eslintrc.json @@ -122,6 +122,10 @@ { "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", "message": "Use the md5hash() function from the core library if you want md5" + }, + { + "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", + "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ @@ -153,6 +157,14 @@ "name": "punycode", "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" }, + { + "name": "child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." + }, + { + "name": "node:child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." + }, { "name": "@aws-cdk/private-tools", "message": "Import shared tools from './private/tools' (the generated shim), not '@aws-cdk/private-tools' directly." @@ -336,5 +348,26 @@ "off" ] }, - "overrides": [] + "overrides": [ + { + "files": [ + "**/test/**", + "**/tests/**", + "**/*.test.ts", + "**/*.integtest.ts", + "projenrc/**", + "**/projenrc/**", + ".projenrc.ts", + "**/.projenrc.ts" + ], + "rules": { + "no-restricted-imports": [ + "off" + ], + "no-restricted-syntax": [ + "off" + ] + } + } + ] } diff --git a/packages/@aws-cdk/cdk-explorer/.eslintrc.json b/packages/@aws-cdk/cdk-explorer/.eslintrc.json index e0c610e49..d80ffb927 100644 --- a/packages/@aws-cdk/cdk-explorer/.eslintrc.json +++ b/packages/@aws-cdk/cdk-explorer/.eslintrc.json @@ -121,6 +121,10 @@ { "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", "message": "Use the md5hash() function from the core library if you want md5" + }, + { + "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", + "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ @@ -151,6 +155,14 @@ { "name": "punycode", "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" + }, + { + "name": "child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." + }, + { + "name": "node:child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." } ], "patterns": [ @@ -316,5 +328,26 @@ "off" ] }, - "overrides": [] + "overrides": [ + { + "files": [ + "**/test/**", + "**/tests/**", + "**/*.test.ts", + "**/*.integtest.ts", + "projenrc/**", + "**/projenrc/**", + ".projenrc.ts", + "**/.projenrc.ts" + ], + "rules": { + "no-restricted-imports": [ + "off" + ], + "no-restricted-syntax": [ + "off" + ] + } + } + ] } diff --git a/packages/@aws-cdk/cli-plugin-contract/.eslintrc.json b/packages/@aws-cdk/cli-plugin-contract/.eslintrc.json index 625959c6e..f568fb6b0 100644 --- a/packages/@aws-cdk/cli-plugin-contract/.eslintrc.json +++ b/packages/@aws-cdk/cli-plugin-contract/.eslintrc.json @@ -122,6 +122,10 @@ { "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", "message": "Use the md5hash() function from the core library if you want md5" + }, + { + "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", + "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ @@ -152,6 +156,14 @@ { "name": "punycode", "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" + }, + { + "name": "child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." + }, + { + "name": "node:child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." } ], "patterns": [ @@ -317,5 +329,26 @@ "off" ] }, - "overrides": [] + "overrides": [ + { + "files": [ + "**/test/**", + "**/tests/**", + "**/*.test.ts", + "**/*.integtest.ts", + "projenrc/**", + "**/projenrc/**", + ".projenrc.ts", + "**/.projenrc.ts" + ], + "rules": { + "no-restricted-imports": [ + "off" + ], + "no-restricted-syntax": [ + "off" + ] + } + } + ] } diff --git a/packages/@aws-cdk/cloud-assembly-api/.eslintrc.json b/packages/@aws-cdk/cloud-assembly-api/.eslintrc.json index 625959c6e..f568fb6b0 100644 --- a/packages/@aws-cdk/cloud-assembly-api/.eslintrc.json +++ b/packages/@aws-cdk/cloud-assembly-api/.eslintrc.json @@ -122,6 +122,10 @@ { "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", "message": "Use the md5hash() function from the core library if you want md5" + }, + { + "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", + "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ @@ -152,6 +156,14 @@ { "name": "punycode", "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" + }, + { + "name": "child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." + }, + { + "name": "node:child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." } ], "patterns": [ @@ -317,5 +329,26 @@ "off" ] }, - "overrides": [] + "overrides": [ + { + "files": [ + "**/test/**", + "**/tests/**", + "**/*.test.ts", + "**/*.integtest.ts", + "projenrc/**", + "**/projenrc/**", + ".projenrc.ts", + "**/.projenrc.ts" + ], + "rules": { + "no-restricted-imports": [ + "off" + ], + "no-restricted-syntax": [ + "off" + ] + } + } + ] } diff --git a/packages/@aws-cdk/cloud-assembly-schema/.eslintrc.json b/packages/@aws-cdk/cloud-assembly-schema/.eslintrc.json index 625959c6e..f568fb6b0 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/.eslintrc.json +++ b/packages/@aws-cdk/cloud-assembly-schema/.eslintrc.json @@ -122,6 +122,10 @@ { "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", "message": "Use the md5hash() function from the core library if you want md5" + }, + { + "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", + "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ @@ -152,6 +156,14 @@ { "name": "punycode", "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" + }, + { + "name": "child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." + }, + { + "name": "node:child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." } ], "patterns": [ @@ -317,5 +329,26 @@ "off" ] }, - "overrides": [] + "overrides": [ + { + "files": [ + "**/test/**", + "**/tests/**", + "**/*.test.ts", + "**/*.integtest.ts", + "projenrc/**", + "**/projenrc/**", + ".projenrc.ts", + "**/.projenrc.ts" + ], + "rules": { + "no-restricted-imports": [ + "off" + ], + "no-restricted-syntax": [ + "off" + ] + } + } + ] } diff --git a/packages/@aws-cdk/cloudformation-diff/.eslintrc.json b/packages/@aws-cdk/cloudformation-diff/.eslintrc.json index 625959c6e..f568fb6b0 100644 --- a/packages/@aws-cdk/cloudformation-diff/.eslintrc.json +++ b/packages/@aws-cdk/cloudformation-diff/.eslintrc.json @@ -122,6 +122,10 @@ { "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", "message": "Use the md5hash() function from the core library if you want md5" + }, + { + "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", + "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ @@ -152,6 +156,14 @@ { "name": "punycode", "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" + }, + { + "name": "child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." + }, + { + "name": "node:child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." } ], "patterns": [ @@ -317,5 +329,26 @@ "off" ] }, - "overrides": [] + "overrides": [ + { + "files": [ + "**/test/**", + "**/tests/**", + "**/*.test.ts", + "**/*.integtest.ts", + "projenrc/**", + "**/projenrc/**", + ".projenrc.ts", + "**/.projenrc.ts" + ], + "rules": { + "no-restricted-imports": [ + "off" + ], + "no-restricted-syntax": [ + "off" + ] + } + } + ] } diff --git a/packages/@aws-cdk/integ-runner/.eslintrc.json b/packages/@aws-cdk/integ-runner/.eslintrc.json index 625959c6e..244a000de 100644 --- a/packages/@aws-cdk/integ-runner/.eslintrc.json +++ b/packages/@aws-cdk/integ-runner/.eslintrc.json @@ -122,6 +122,10 @@ { "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", "message": "Use the md5hash() function from the core library if you want md5" + }, + { + "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", + "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ @@ -152,6 +156,14 @@ { "name": "punycode", "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" + }, + { + "name": "child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." + }, + { + "name": "node:child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." } ], "patterns": [ @@ -317,5 +329,39 @@ "off" ] }, - "overrides": [] + "overrides": [ + { + "files": [ + "**/test/**", + "**/tests/**", + "**/*.test.ts", + "**/*.integtest.ts", + "projenrc/**", + "**/projenrc/**", + ".projenrc.ts", + "**/.projenrc.ts" + ], + "rules": { + "no-restricted-imports": [ + "off" + ], + "no-restricted-syntax": [ + "off" + ] + } + }, + { + "files": [ + "lib/utils.ts" + ], + "rules": { + "no-restricted-imports": [ + "off" + ], + "no-restricted-syntax": [ + "off" + ] + } + } + ] } diff --git a/packages/@aws-cdk/private-tools/.eslintrc.json b/packages/@aws-cdk/private-tools/.eslintrc.json index e0c610e49..d80ffb927 100644 --- a/packages/@aws-cdk/private-tools/.eslintrc.json +++ b/packages/@aws-cdk/private-tools/.eslintrc.json @@ -121,6 +121,10 @@ { "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", "message": "Use the md5hash() function from the core library if you want md5" + }, + { + "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", + "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ @@ -151,6 +155,14 @@ { "name": "punycode", "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" + }, + { + "name": "child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." + }, + { + "name": "node:child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." } ], "patterns": [ @@ -316,5 +328,26 @@ "off" ] }, - "overrides": [] + "overrides": [ + { + "files": [ + "**/test/**", + "**/tests/**", + "**/*.test.ts", + "**/*.integtest.ts", + "projenrc/**", + "**/projenrc/**", + ".projenrc.ts", + "**/.projenrc.ts" + ], + "rules": { + "no-restricted-imports": [ + "off" + ], + "no-restricted-syntax": [ + "off" + ] + } + } + ] } diff --git a/packages/@aws-cdk/private-tools/lib/subprocess/index.ts b/packages/@aws-cdk/private-tools/lib/subprocess/index.ts index fe827d0ef..fbf5bb070 100644 --- a/packages/@aws-cdk/private-tools/lib/subprocess/index.ts +++ b/packages/@aws-cdk/private-tools/lib/subprocess/index.ts @@ -16,6 +16,7 @@ * the only path to a shell and takes no argv form, so command lines can * never be assembled from parts by this codebase. */ +// eslint-disable-next-line no-restricted-imports -- this module IS the sanctioned wrapper around child_process import * as child_process from 'child_process'; import { StringDecoder } from 'string_decoder'; import spawn from 'cross-spawn'; @@ -279,6 +280,7 @@ export function runSync(argv: readonly string[], options: RunSyncOptions = {}): export async function runUserCommandLine(commandLine: string, options: RunOptions = {}): Promise { const child = child_process.spawn(commandLine, { ...spawnOptions(options), + // eslint-disable-next-line no-restricted-syntax -- this is the single sanctioned shell entry point (see the module header) shell: true, }); return monitor(child, commandLine, options); diff --git a/packages/@aws-cdk/toolkit-lib/.eslintrc.json b/packages/@aws-cdk/toolkit-lib/.eslintrc.json index 6a8e078ea..0af775717 100644 --- a/packages/@aws-cdk/toolkit-lib/.eslintrc.json +++ b/packages/@aws-cdk/toolkit-lib/.eslintrc.json @@ -119,6 +119,10 @@ { "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", "message": "Use the md5hash() function from the core library if you want md5" + }, + { + "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", + "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ @@ -150,6 +154,14 @@ "name": "punycode", "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" }, + { + "name": "child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." + }, + { + "name": "node:child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." + }, { "name": "@aws-cdk/private-tools", "message": "Import shared tools from './private/tools' (the generated shim), not '@aws-cdk/private-tools' directly." @@ -332,6 +344,26 @@ "@cdklabs/no-throw-default-error": "error" }, "overrides": [ + { + "files": [ + "**/test/**", + "**/tests/**", + "**/*.test.ts", + "**/*.integtest.ts", + "projenrc/**", + "**/projenrc/**", + ".projenrc.ts", + "**/.projenrc.ts" + ], + "rules": { + "no-restricted-imports": [ + "off" + ], + "no-restricted-syntax": [ + "off" + ] + } + }, { "files": [ "./test/**" diff --git a/packages/@aws-cdk/user-input-gen/.eslintrc.json b/packages/@aws-cdk/user-input-gen/.eslintrc.json index e0c610e49..d80ffb927 100644 --- a/packages/@aws-cdk/user-input-gen/.eslintrc.json +++ b/packages/@aws-cdk/user-input-gen/.eslintrc.json @@ -121,6 +121,10 @@ { "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", "message": "Use the md5hash() function from the core library if you want md5" + }, + { + "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", + "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ @@ -151,6 +155,14 @@ { "name": "punycode", "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" + }, + { + "name": "child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." + }, + { + "name": "node:child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." } ], "patterns": [ @@ -316,5 +328,26 @@ "off" ] }, - "overrides": [] + "overrides": [ + { + "files": [ + "**/test/**", + "**/tests/**", + "**/*.test.ts", + "**/*.integtest.ts", + "projenrc/**", + "**/projenrc/**", + ".projenrc.ts", + "**/.projenrc.ts" + ], + "rules": { + "no-restricted-imports": [ + "off" + ], + "no-restricted-syntax": [ + "off" + ] + } + } + ] } diff --git a/packages/@aws-cdk/yarn-cling/.eslintrc.json b/packages/@aws-cdk/yarn-cling/.eslintrc.json index e0c610e49..d80ffb927 100644 --- a/packages/@aws-cdk/yarn-cling/.eslintrc.json +++ b/packages/@aws-cdk/yarn-cling/.eslintrc.json @@ -121,6 +121,10 @@ { "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", "message": "Use the md5hash() function from the core library if you want md5" + }, + { + "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", + "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ @@ -151,6 +155,14 @@ { "name": "punycode", "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" + }, + { + "name": "child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." + }, + { + "name": "node:child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." } ], "patterns": [ @@ -316,5 +328,26 @@ "off" ] }, - "overrides": [] + "overrides": [ + { + "files": [ + "**/test/**", + "**/tests/**", + "**/*.test.ts", + "**/*.integtest.ts", + "projenrc/**", + "**/projenrc/**", + ".projenrc.ts", + "**/.projenrc.ts" + ], + "rules": { + "no-restricted-imports": [ + "off" + ], + "no-restricted-syntax": [ + "off" + ] + } + } + ] } diff --git a/packages/aws-cdk/.eslintrc.json b/packages/aws-cdk/.eslintrc.json index b28fc0ca0..a67d2bb88 100644 --- a/packages/aws-cdk/.eslintrc.json +++ b/packages/aws-cdk/.eslintrc.json @@ -119,6 +119,10 @@ { "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", "message": "Use the md5hash() function from the core library if you want md5" + }, + { + "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", + "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ @@ -150,6 +154,14 @@ "name": "punycode", "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" }, + { + "name": "child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." + }, + { + "name": "node:child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." + }, { "name": "@aws-cdk/private-tools", "message": "Import shared tools from './private/tools' (the generated shim), not '@aws-cdk/private-tools' directly." @@ -328,6 +340,26 @@ "@cdklabs/no-throw-default-error": "error" }, "overrides": [ + { + "files": [ + "**/test/**", + "**/tests/**", + "**/*.test.ts", + "**/*.integtest.ts", + "projenrc/**", + "**/projenrc/**", + ".projenrc.ts", + "**/.projenrc.ts" + ], + "rules": { + "no-restricted-imports": [ + "off" + ], + "no-restricted-syntax": [ + "off" + ] + } + }, { "files": [ "./test/**" diff --git a/packages/cdk-assets/.eslintrc.json b/packages/cdk-assets/.eslintrc.json index 625959c6e..f568fb6b0 100644 --- a/packages/cdk-assets/.eslintrc.json +++ b/packages/cdk-assets/.eslintrc.json @@ -122,6 +122,10 @@ { "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", "message": "Use the md5hash() function from the core library if you want md5" + }, + { + "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", + "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ @@ -152,6 +156,14 @@ { "name": "punycode", "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" + }, + { + "name": "child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." + }, + { + "name": "node:child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." } ], "patterns": [ @@ -317,5 +329,26 @@ "off" ] }, - "overrides": [] + "overrides": [ + { + "files": [ + "**/test/**", + "**/tests/**", + "**/*.test.ts", + "**/*.integtest.ts", + "projenrc/**", + "**/projenrc/**", + ".projenrc.ts", + "**/.projenrc.ts" + ], + "rules": { + "no-restricted-imports": [ + "off" + ], + "no-restricted-syntax": [ + "off" + ] + } + } + ] } diff --git a/packages/cdk/.eslintrc.json b/packages/cdk/.eslintrc.json index 625959c6e..f568fb6b0 100644 --- a/packages/cdk/.eslintrc.json +++ b/packages/cdk/.eslintrc.json @@ -122,6 +122,10 @@ { "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", "message": "Use the md5hash() function from the core library if you want md5" + }, + { + "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", + "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ @@ -152,6 +156,14 @@ { "name": "punycode", "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" + }, + { + "name": "child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." + }, + { + "name": "node:child_process", + "message": "Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool ('./private/tools')." } ], "patterns": [ @@ -317,5 +329,26 @@ "off" ] }, - "overrides": [] + "overrides": [ + { + "files": [ + "**/test/**", + "**/tests/**", + "**/*.test.ts", + "**/*.integtest.ts", + "projenrc/**", + "**/projenrc/**", + ".projenrc.ts", + "**/.projenrc.ts" + ], + "rules": { + "no-restricted-imports": [ + "off" + ], + "no-restricted-syntax": [ + "off" + ] + } + } + ] } diff --git a/projenrc/eslint/imports.ts b/projenrc/eslint/imports.ts index 1d4c4b902..c504f4552 100644 --- a/projenrc/eslint/imports.ts +++ b/projenrc/eslint/imports.ts @@ -19,6 +19,17 @@ export default { name: 'punycode', message: 'Package \'punycode\' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation', }, + // All child process spawning goes through the shared subprocess tool, so + // that no code path both escapes and executes. The tool itself (and the + // not-yet-migrated packages) carry a local eslint-disable / override. + { + name: 'child_process', + message: 'Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool (\'./private/tools\').', + }, + { + name: 'node:child_process', + message: 'Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool (\'./private/tools\').', + }, ], patterns: ['!punycode/'], }, diff --git a/projenrc/eslint/index.ts b/projenrc/eslint/index.ts index 45c78eeda..ee34fa628 100644 --- a/projenrc/eslint/index.ts +++ b/projenrc/eslint/index.ts @@ -61,6 +61,32 @@ export function configureEslint(x: typescript.TypeScriptProject) { ); x.eslint?.addRules(ESLINT_RULES); + // The `child_process` / `shell: true` bans protect SHIPPED code from spawning + // outside the subprocess tool. Two categories are outside that trust boundary + // and are exempt: + // - test code, which legitimately imports `child_process` to mock/spy and + // may spawn with `shell: true` against throwaway fixtures; + // - build-time tooling (projen config, build tasks), which runs on + // developer/CI machines rather than a user's machine. + // (This also relaxes the md5/punycode rules for these files, which is harmless + // for non-shipped code.) + x.eslint?.addOverride({ + files: [ + '**/test/**', + '**/tests/**', + '**/*.test.ts', + '**/*.integtest.ts', + 'projenrc/**', + '**/projenrc/**', + '.projenrc.ts', + '**/.projenrc.ts', + ], + rules: { + 'no-restricted-imports': ['off'], + 'no-restricted-syntax': ['off'], + }, + }); + // For our published packages, we need all type imports to be from a public dependency if (!isRoot && !isPrivate && x.eslint) { x.eslint.rules['import/no-extraneous-dependencies'][1].includeTypes = true; diff --git a/projenrc/eslint/team.ts b/projenrc/eslint/team.ts index 7005209af..e9f4bb8d5 100644 --- a/projenrc/eslint/team.ts +++ b/projenrc/eslint/team.ts @@ -12,5 +12,13 @@ export default { selector: "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", message: 'Use the md5hash() function from the core library if you want md5', }, + { + // Spawning through a shell is the one place command injection can happen. + // The only sanctioned shell entry point is `runUserCommandLine` in the + // subprocess tool (which carries its own eslint-disable); everything else + // must spawn an argv array via `run`/`runSync`, which never touch a shell. + selector: "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", + message: 'Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line.', + }, ], }; From 74e76ae5be38588fff5e96379c1b009954b6882e Mon Sep 17 00:00:00 2001 From: Ian Hou <45278651+iankhou@users.noreply.github.com> Date: Thu, 27 Aug 2026 14:14:06 +0000 Subject: [PATCH 2/3] chore: address review feedback on the subprocess lint rule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Broaden the shell-spawn ban: reject any `shell` property that is not statically `false`, instead of only the literal `true`. `{ shell }`, `shell: cond`, `shell: someVar` and `shell: '/bin/sh'` are now caught (per review: matching only `true` left obvious bypasses). - Replace the projen `addOverride` exemptions with in-file eslint-disable comments that carry a reason at the spawn site: - integ-runner/lib/utils.ts: exempt only the child_process import; the shell rule stays active (exec() is argv-based today, so a future shell:true there should still be caught). - cli-integ: it is a test harness that intentionally spawns through a shell to exercise the CLI as a user would — permanent, like test code. --- .eslintrc.json | 4 +-- .projenrc.ts | 34 ------------------- .../@aws-cdk-testing/cli-integ/.eslintrc.json | 23 ++----------- .../cli-integ/lib/cli/stage-distribution.ts | 1 + .../@aws-cdk-testing/cli-integ/lib/npm.ts | 1 + .../lib/package-sources/repo-tools/npm.ts | 1 + .../@aws-cdk-testing/cli-integ/lib/process.ts | 2 ++ .../@aws-cdk-testing/cli-integ/lib/shell.ts | 1 + .../cli-integ/lib/with-cdk-app.ts | 1 + .../cli-integ/lib/with-sam.ts | 2 ++ .../@aws-cdk/cdk-assets-lib/.eslintrc.json | 4 +-- packages/@aws-cdk/cdk-explorer/.eslintrc.json | 4 +-- .../cli-plugin-contract/.eslintrc.json | 4 +-- .../cloud-assembly-api/.eslintrc.json | 4 +-- .../cloud-assembly-schema/.eslintrc.json | 4 +-- .../cloudformation-diff/.eslintrc.json | 4 +-- packages/@aws-cdk/integ-runner/.eslintrc.json | 17 ++-------- packages/@aws-cdk/integ-runner/lib/utils.ts | 1 + .../@aws-cdk/private-tools/.eslintrc.json | 4 +-- packages/@aws-cdk/toolkit-lib/.eslintrc.json | 4 +-- .../@aws-cdk/user-input-gen/.eslintrc.json | 4 +-- packages/@aws-cdk/yarn-cling/.eslintrc.json | 4 +-- packages/aws-cdk/.eslintrc.json | 4 +-- packages/cdk-assets/.eslintrc.json | 4 +-- packages/cdk/.eslintrc.json | 4 +-- projenrc/eslint/imports.ts | 4 +-- projenrc/eslint/team.ts | 6 ++-- 27 files changed, 47 insertions(+), 103 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index b17cc2b18..7e9a434d6 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -224,8 +224,8 @@ "message": "Use the md5hash() function from the core library if you want md5" }, { - "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", - "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." + "selector": "Property:matches([key.name='shell'], [key.value='shell']):not([value.type='Literal'][value.value=false])", + "message": "Do not enable the `shell` spawn option. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ diff --git a/.projenrc.ts b/.projenrc.ts index 2f877916e..5ed4f49d1 100644 --- a/.projenrc.ts +++ b/.projenrc.ts @@ -1565,19 +1565,6 @@ integRunner.preCompileTask.prependExec('./build-tools/generate.sh'); new TypecheckTests(integRunner); fixupTestTask(integRunner); -// integ-runner is integration-test tooling, and its `exec()` already spawns an -// argv array with no shell (so it carries no injection risk — the ban only -// flags it for importing child_process directly). Exempt it for now; migrating -// it onto the shared subprocess tool is deferred follow-up work. Scoped to the -// one sink so any NEW file still hits the ban. -integRunner.eslint?.addOverride({ - files: ['lib/utils.ts'], - rules: { - 'no-restricted-imports': ['off'], - 'no-restricted-syntax': ['off'], - }, -}); - new BundleCli(integRunner, { externals: { dependencies: [ @@ -1701,27 +1688,6 @@ const cliInteg = configureProject( ); cliInteg.eslint?.addIgnorePattern('resources/**/*.ts'); -// cli-integ is a test harness that deliberately spawns through a shell (and a -// pty) to exercise the CLI the way a user would at a terminal — that is the -// point of these helpers, not something to migrate away. Permanently exempt, -// like test code. Scoped to the current sinks so NEW files still hit the ban -// and get a conscious decision rather than a silent pass. -cliInteg.eslint?.addOverride({ - files: [ - 'lib/cli/stage-distribution.ts', - 'lib/npm.ts', - 'lib/package-sources/repo-tools/npm.ts', - 'lib/process.ts', - 'lib/shell.ts', - 'lib/with-cdk-app.ts', - 'lib/with-sam.ts', - ], - rules: { - 'no-restricted-imports': ['off'], - 'no-restricted-syntax': ['off'], - }, -}); - cliInteg.deps.addDependency('@aws-cdk/toolkit-lib', pj.DependencyType.OPTIONAL); const compiledDirs = ['tests', 'test', 'lib']; diff --git a/packages/@aws-cdk-testing/cli-integ/.eslintrc.json b/packages/@aws-cdk-testing/cli-integ/.eslintrc.json index cbb18f88f..9fa854e9f 100644 --- a/packages/@aws-cdk-testing/cli-integ/.eslintrc.json +++ b/packages/@aws-cdk-testing/cli-integ/.eslintrc.json @@ -125,8 +125,8 @@ "message": "Use the md5hash() function from the core library if you want md5" }, { - "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", - "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." + "selector": "Property:matches([key.name='shell'], [key.value='shell']):not([value.type='Literal'][value.value=false])", + "message": "Do not enable the `shell` spawn option. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ @@ -350,25 +350,6 @@ "off" ] } - }, - { - "files": [ - "lib/cli/stage-distribution.ts", - "lib/npm.ts", - "lib/package-sources/repo-tools/npm.ts", - "lib/process.ts", - "lib/shell.ts", - "lib/with-cdk-app.ts", - "lib/with-sam.ts" - ], - "rules": { - "no-restricted-imports": [ - "off" - ], - "no-restricted-syntax": [ - "off" - ] - } } ] } diff --git a/packages/@aws-cdk-testing/cli-integ/lib/cli/stage-distribution.ts b/packages/@aws-cdk-testing/cli-integ/lib/cli/stage-distribution.ts index 2376f5b40..8dd0365ec 100644 --- a/packages/@aws-cdk-testing/cli-integ/lib/cli/stage-distribution.ts +++ b/packages/@aws-cdk-testing/cli-integ/lib/cli/stage-distribution.ts @@ -108,6 +108,7 @@ async function main() { await usageDir.activateInCurrentProcess(); await shell(args.COMMAND ?? [], { + // eslint-disable-next-line no-restricted-syntax -- cli-integ deliberately runs commands through a shell to mimic real terminal invocation in integ tests. shell: true, show: 'always', }); diff --git a/packages/@aws-cdk-testing/cli-integ/lib/npm.ts b/packages/@aws-cdk-testing/cli-integ/lib/npm.ts index 82c96a5f8..09aa05f0c 100644 --- a/packages/@aws-cdk-testing/cli-integ/lib/npm.ts +++ b/packages/@aws-cdk-testing/cli-integ/lib/npm.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line no-restricted-imports -- cli-integ is a test harness that spawns processes to exercise the CLI as a user would; it is test infrastructure, not shipped runtime. import { spawnSync } from 'child_process'; import * as semver from 'semver'; import { shell } from './shell'; diff --git a/packages/@aws-cdk-testing/cli-integ/lib/package-sources/repo-tools/npm.ts b/packages/@aws-cdk-testing/cli-integ/lib/package-sources/repo-tools/npm.ts index 38e0e1e87..def67e48c 100644 --- a/packages/@aws-cdk-testing/cli-integ/lib/package-sources/repo-tools/npm.ts +++ b/packages/@aws-cdk-testing/cli-integ/lib/package-sources/repo-tools/npm.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line no-restricted-imports -- cli-integ is a test harness that spawns processes to exercise the CLI as a user would; it is test infrastructure, not shipped runtime. import * as child_process from 'child_process'; import * as fs from 'fs-extra'; diff --git a/packages/@aws-cdk-testing/cli-integ/lib/process.ts b/packages/@aws-cdk-testing/cli-integ/lib/process.ts index 9b64ee585..7532ece64 100644 --- a/packages/@aws-cdk-testing/cli-integ/lib/process.ts +++ b/packages/@aws-cdk-testing/cli-integ/lib/process.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line no-restricted-imports -- cli-integ is a test harness that spawns processes to exercise the CLI as a user would; it is test infrastructure, not shipped runtime. import * as child from 'child_process'; import type { Readable, Writable } from 'stream'; import * as pty from 'node-pty'; @@ -63,6 +64,7 @@ export class Process { // (passing args with shell: true is deprecated because they are not escaped). const fullCommand = [command, ...args].join(' '); const process = child.spawn(fullCommand, [], { + // eslint-disable-next-line no-restricted-syntax -- cli-integ deliberately runs commands through a shell to mimic real terminal invocation in integ tests. shell: true, stdio: ['ignore', 'pipe', 'pipe'], ...options, diff --git a/packages/@aws-cdk-testing/cli-integ/lib/shell.ts b/packages/@aws-cdk-testing/cli-integ/lib/shell.ts index 436ee7633..1acd22efe 100644 --- a/packages/@aws-cdk-testing/cli-integ/lib/shell.ts +++ b/packages/@aws-cdk-testing/cli-integ/lib/shell.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line no-restricted-imports -- type-only import in cli-integ, a test harness that spawns processes to exercise the CLI; test infrastructure, not shipped runtime. import type * as child_process from 'child_process'; import * as fs from 'fs'; import * as os from 'os'; diff --git a/packages/@aws-cdk-testing/cli-integ/lib/with-cdk-app.ts b/packages/@aws-cdk-testing/cli-integ/lib/with-cdk-app.ts index 5923a445e..61d862662 100644 --- a/packages/@aws-cdk-testing/cli-integ/lib/with-cdk-app.ts +++ b/packages/@aws-cdk-testing/cli-integ/lib/with-cdk-app.ts @@ -518,6 +518,7 @@ export class TestFixture extends ShellHelper { '--username', username, '--password', '${ECR_PASSWORD}', 'public.ecr.aws'], { + // eslint-disable-next-line no-restricted-syntax -- cli-integ deliberately runs commands through a shell to mimic real terminal invocation in integ tests. shell: true, modEnv: { ECR_PASSWORD: password, diff --git a/packages/@aws-cdk-testing/cli-integ/lib/with-sam.ts b/packages/@aws-cdk-testing/cli-integ/lib/with-sam.ts index e3350a84c..7ae5eaecd 100644 --- a/packages/@aws-cdk-testing/cli-integ/lib/with-sam.ts +++ b/packages/@aws-cdk-testing/cli-integ/lib/with-sam.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line no-restricted-imports -- cli-integ is a test harness that spawns processes to exercise the CLI as a user would; it is test infrastructure, not shipped runtime. import * as child_process from 'child_process'; import * as os from 'os'; import * as path from 'path'; @@ -192,6 +193,7 @@ export async function shellWithAction( ...options, env, // Need this for Windows where we want .cmd and .bat to be found as well. + // eslint-disable-next-line no-restricted-syntax -- cli-integ deliberately runs commands through a shell to mimic real terminal invocation in integ tests. shell: true, stdio: ['ignore', 'pipe', 'pipe'], }); diff --git a/packages/@aws-cdk/cdk-assets-lib/.eslintrc.json b/packages/@aws-cdk/cdk-assets-lib/.eslintrc.json index c832c334d..a11f0bc31 100644 --- a/packages/@aws-cdk/cdk-assets-lib/.eslintrc.json +++ b/packages/@aws-cdk/cdk-assets-lib/.eslintrc.json @@ -124,8 +124,8 @@ "message": "Use the md5hash() function from the core library if you want md5" }, { - "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", - "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." + "selector": "Property:matches([key.name='shell'], [key.value='shell']):not([value.type='Literal'][value.value=false])", + "message": "Do not enable the `shell` spawn option. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ diff --git a/packages/@aws-cdk/cdk-explorer/.eslintrc.json b/packages/@aws-cdk/cdk-explorer/.eslintrc.json index d80ffb927..117ce8067 100644 --- a/packages/@aws-cdk/cdk-explorer/.eslintrc.json +++ b/packages/@aws-cdk/cdk-explorer/.eslintrc.json @@ -123,8 +123,8 @@ "message": "Use the md5hash() function from the core library if you want md5" }, { - "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", - "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." + "selector": "Property:matches([key.name='shell'], [key.value='shell']):not([value.type='Literal'][value.value=false])", + "message": "Do not enable the `shell` spawn option. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ diff --git a/packages/@aws-cdk/cli-plugin-contract/.eslintrc.json b/packages/@aws-cdk/cli-plugin-contract/.eslintrc.json index f568fb6b0..0e3165c02 100644 --- a/packages/@aws-cdk/cli-plugin-contract/.eslintrc.json +++ b/packages/@aws-cdk/cli-plugin-contract/.eslintrc.json @@ -124,8 +124,8 @@ "message": "Use the md5hash() function from the core library if you want md5" }, { - "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", - "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." + "selector": "Property:matches([key.name='shell'], [key.value='shell']):not([value.type='Literal'][value.value=false])", + "message": "Do not enable the `shell` spawn option. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ diff --git a/packages/@aws-cdk/cloud-assembly-api/.eslintrc.json b/packages/@aws-cdk/cloud-assembly-api/.eslintrc.json index f568fb6b0..0e3165c02 100644 --- a/packages/@aws-cdk/cloud-assembly-api/.eslintrc.json +++ b/packages/@aws-cdk/cloud-assembly-api/.eslintrc.json @@ -124,8 +124,8 @@ "message": "Use the md5hash() function from the core library if you want md5" }, { - "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", - "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." + "selector": "Property:matches([key.name='shell'], [key.value='shell']):not([value.type='Literal'][value.value=false])", + "message": "Do not enable the `shell` spawn option. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ diff --git a/packages/@aws-cdk/cloud-assembly-schema/.eslintrc.json b/packages/@aws-cdk/cloud-assembly-schema/.eslintrc.json index f568fb6b0..0e3165c02 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/.eslintrc.json +++ b/packages/@aws-cdk/cloud-assembly-schema/.eslintrc.json @@ -124,8 +124,8 @@ "message": "Use the md5hash() function from the core library if you want md5" }, { - "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", - "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." + "selector": "Property:matches([key.name='shell'], [key.value='shell']):not([value.type='Literal'][value.value=false])", + "message": "Do not enable the `shell` spawn option. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ diff --git a/packages/@aws-cdk/cloudformation-diff/.eslintrc.json b/packages/@aws-cdk/cloudformation-diff/.eslintrc.json index f568fb6b0..0e3165c02 100644 --- a/packages/@aws-cdk/cloudformation-diff/.eslintrc.json +++ b/packages/@aws-cdk/cloudformation-diff/.eslintrc.json @@ -124,8 +124,8 @@ "message": "Use the md5hash() function from the core library if you want md5" }, { - "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", - "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." + "selector": "Property:matches([key.name='shell'], [key.value='shell']):not([value.type='Literal'][value.value=false])", + "message": "Do not enable the `shell` spawn option. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ diff --git a/packages/@aws-cdk/integ-runner/.eslintrc.json b/packages/@aws-cdk/integ-runner/.eslintrc.json index 244a000de..0e3165c02 100644 --- a/packages/@aws-cdk/integ-runner/.eslintrc.json +++ b/packages/@aws-cdk/integ-runner/.eslintrc.json @@ -124,8 +124,8 @@ "message": "Use the md5hash() function from the core library if you want md5" }, { - "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", - "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." + "selector": "Property:matches([key.name='shell'], [key.value='shell']):not([value.type='Literal'][value.value=false])", + "message": "Do not enable the `shell` spawn option. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ @@ -349,19 +349,6 @@ "off" ] } - }, - { - "files": [ - "lib/utils.ts" - ], - "rules": { - "no-restricted-imports": [ - "off" - ], - "no-restricted-syntax": [ - "off" - ] - } } ] } diff --git a/packages/@aws-cdk/integ-runner/lib/utils.ts b/packages/@aws-cdk/integ-runner/lib/utils.ts index e1a1b3bf6..f0329a464 100644 --- a/packages/@aws-cdk/integ-runner/lib/utils.ts +++ b/packages/@aws-cdk/integ-runner/lib/utils.ts @@ -1,4 +1,5 @@ // Helper functions for CDK Exec +// eslint-disable-next-line no-restricted-imports -- integ-runner is test tooling; exec() spawns an argv array with no shell, so this is safe. Migrating onto the shared subprocess tool is deferred follow-up work. import { spawnSync } from 'child_process'; /** diff --git a/packages/@aws-cdk/private-tools/.eslintrc.json b/packages/@aws-cdk/private-tools/.eslintrc.json index d80ffb927..117ce8067 100644 --- a/packages/@aws-cdk/private-tools/.eslintrc.json +++ b/packages/@aws-cdk/private-tools/.eslintrc.json @@ -123,8 +123,8 @@ "message": "Use the md5hash() function from the core library if you want md5" }, { - "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", - "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." + "selector": "Property:matches([key.name='shell'], [key.value='shell']):not([value.type='Literal'][value.value=false])", + "message": "Do not enable the `shell` spawn option. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ diff --git a/packages/@aws-cdk/toolkit-lib/.eslintrc.json b/packages/@aws-cdk/toolkit-lib/.eslintrc.json index 0af775717..3c8dbe97e 100644 --- a/packages/@aws-cdk/toolkit-lib/.eslintrc.json +++ b/packages/@aws-cdk/toolkit-lib/.eslintrc.json @@ -121,8 +121,8 @@ "message": "Use the md5hash() function from the core library if you want md5" }, { - "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", - "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." + "selector": "Property:matches([key.name='shell'], [key.value='shell']):not([value.type='Literal'][value.value=false])", + "message": "Do not enable the `shell` spawn option. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ diff --git a/packages/@aws-cdk/user-input-gen/.eslintrc.json b/packages/@aws-cdk/user-input-gen/.eslintrc.json index d80ffb927..117ce8067 100644 --- a/packages/@aws-cdk/user-input-gen/.eslintrc.json +++ b/packages/@aws-cdk/user-input-gen/.eslintrc.json @@ -123,8 +123,8 @@ "message": "Use the md5hash() function from the core library if you want md5" }, { - "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", - "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." + "selector": "Property:matches([key.name='shell'], [key.value='shell']):not([value.type='Literal'][value.value=false])", + "message": "Do not enable the `shell` spawn option. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ diff --git a/packages/@aws-cdk/yarn-cling/.eslintrc.json b/packages/@aws-cdk/yarn-cling/.eslintrc.json index d80ffb927..117ce8067 100644 --- a/packages/@aws-cdk/yarn-cling/.eslintrc.json +++ b/packages/@aws-cdk/yarn-cling/.eslintrc.json @@ -123,8 +123,8 @@ "message": "Use the md5hash() function from the core library if you want md5" }, { - "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", - "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." + "selector": "Property:matches([key.name='shell'], [key.value='shell']):not([value.type='Literal'][value.value=false])", + "message": "Do not enable the `shell` spawn option. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ diff --git a/packages/aws-cdk/.eslintrc.json b/packages/aws-cdk/.eslintrc.json index a67d2bb88..0ea33b4b4 100644 --- a/packages/aws-cdk/.eslintrc.json +++ b/packages/aws-cdk/.eslintrc.json @@ -121,8 +121,8 @@ "message": "Use the md5hash() function from the core library if you want md5" }, { - "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", - "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." + "selector": "Property:matches([key.name='shell'], [key.value='shell']):not([value.type='Literal'][value.value=false])", + "message": "Do not enable the `shell` spawn option. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ diff --git a/packages/cdk-assets/.eslintrc.json b/packages/cdk-assets/.eslintrc.json index f568fb6b0..0e3165c02 100644 --- a/packages/cdk-assets/.eslintrc.json +++ b/packages/cdk-assets/.eslintrc.json @@ -124,8 +124,8 @@ "message": "Use the md5hash() function from the core library if you want md5" }, { - "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", - "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." + "selector": "Property:matches([key.name='shell'], [key.value='shell']):not([value.type='Literal'][value.value=false])", + "message": "Do not enable the `shell` spawn option. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ diff --git a/packages/cdk/.eslintrc.json b/packages/cdk/.eslintrc.json index f568fb6b0..0e3165c02 100644 --- a/packages/cdk/.eslintrc.json +++ b/packages/cdk/.eslintrc.json @@ -124,8 +124,8 @@ "message": "Use the md5hash() function from the core library if you want md5" }, { - "selector": "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", - "message": "Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." + "selector": "Property:matches([key.name='shell'], [key.value='shell']):not([value.type='Literal'][value.value=false])", + "message": "Do not enable the `shell` spawn option. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line." } ], "no-throw-literal": [ diff --git a/projenrc/eslint/imports.ts b/projenrc/eslint/imports.ts index c504f4552..5a674e2b9 100644 --- a/projenrc/eslint/imports.ts +++ b/projenrc/eslint/imports.ts @@ -19,9 +19,7 @@ export default { name: 'punycode', message: 'Package \'punycode\' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation', }, - // All child process spawning goes through the shared subprocess tool, so - // that no code path both escapes and executes. The tool itself (and the - // not-yet-migrated packages) carry a local eslint-disable / override. + // Route child process spawning through the shared subprocess tool. { name: 'child_process', message: 'Do not use `child_process` directly. Use `run`/`runSync`/`runUserCommandLine` from the subprocess tool (\'./private/tools\').', diff --git a/projenrc/eslint/team.ts b/projenrc/eslint/team.ts index e9f4bb8d5..5c24e95b2 100644 --- a/projenrc/eslint/team.ts +++ b/projenrc/eslint/team.ts @@ -17,8 +17,10 @@ export default { // The only sanctioned shell entry point is `runUserCommandLine` in the // subprocess tool (which carries its own eslint-disable); everything else // must spawn an argv array via `run`/`runSync`, which never touch a shell. - selector: "Property:matches([key.name='shell'], [key.value='shell'])[value.value=true]", - message: 'Do not spawn with `shell: true`. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line.', + // + // Reject any `shell` property that is not statically `false`. + selector: "Property:matches([key.name='shell'], [key.value='shell']):not([value.type='Literal'][value.value=false])", + message: 'Do not enable the `shell` spawn option. Use `run`/`runSync` (argv, no shell) from the subprocess tool, or `runUserCommandLine` for a user-authored command line.', }, ], }; From b0ff6a75e71c719eec7691b7f2c1589a17a765bf Mon Sep 17 00:00:00 2001 From: Ian Hou <45278651+iankhou@users.noreply.github.com> Date: Thu, 27 Aug 2026 15:22:14 +0000 Subject: [PATCH 3/3] chore: preserve punycode/MD5 team rules in the test/build-tooling exemption The test/build-tooling exemption previously turned no-restricted-imports and no-restricted-syntax fully off, which also dropped the pre-existing punycode and MD5 team rules there. Re-specify those two rules in the exemption so they keep applying, and drop ONLY the newer subprocess restrictions (child_process import + shell option). Extract PUNYCODE_IMPORT_RESTRICTION / MD5_SYNTAX_RESTRICTION as shared constants so the exemption reuses them without duplication. --- .eslintrc.json | 19 +++++++++++++++++-- .../@aws-cdk-testing/cli-integ/.eslintrc.json | 19 +++++++++++++++++-- .../@aws-cdk/cdk-assets-lib/.eslintrc.json | 19 +++++++++++++++++-- packages/@aws-cdk/cdk-explorer/.eslintrc.json | 19 +++++++++++++++++-- .../cli-plugin-contract/.eslintrc.json | 19 +++++++++++++++++-- .../cloud-assembly-api/.eslintrc.json | 19 +++++++++++++++++-- .../cloud-assembly-schema/.eslintrc.json | 19 +++++++++++++++++-- .../cloudformation-diff/.eslintrc.json | 19 +++++++++++++++++-- packages/@aws-cdk/integ-runner/.eslintrc.json | 19 +++++++++++++++++-- .../@aws-cdk/private-tools/.eslintrc.json | 19 +++++++++++++++++-- packages/@aws-cdk/toolkit-lib/.eslintrc.json | 19 +++++++++++++++++-- .../@aws-cdk/user-input-gen/.eslintrc.json | 19 +++++++++++++++++-- packages/@aws-cdk/yarn-cling/.eslintrc.json | 19 +++++++++++++++++-- packages/aws-cdk/.eslintrc.json | 19 +++++++++++++++++-- packages/cdk-assets/.eslintrc.json | 19 +++++++++++++++++-- packages/cdk/.eslintrc.json | 19 +++++++++++++++++-- projenrc/eslint/imports.ts | 10 ++++++---- projenrc/eslint/index.ts | 18 ++++-------------- projenrc/eslint/team.ts | 15 +++++++++------ 19 files changed, 291 insertions(+), 56 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 7e9a434d6..26e1beefd 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -345,10 +345,25 @@ ], "rules": { "no-restricted-imports": [ - "off" + "error", + { + "paths": [ + { + "name": "punycode", + "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" + } + ], + "patterns": [ + "!punycode/" + ] + } ], "no-restricted-syntax": [ - "off" + "error", + { + "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", + "message": "Use the md5hash() function from the core library if you want md5" + } ] } }, diff --git a/packages/@aws-cdk-testing/cli-integ/.eslintrc.json b/packages/@aws-cdk-testing/cli-integ/.eslintrc.json index 9fa854e9f..b96a4a843 100644 --- a/packages/@aws-cdk-testing/cli-integ/.eslintrc.json +++ b/packages/@aws-cdk-testing/cli-integ/.eslintrc.json @@ -344,10 +344,25 @@ ], "rules": { "no-restricted-imports": [ - "off" + "error", + { + "paths": [ + { + "name": "punycode", + "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" + } + ], + "patterns": [ + "!punycode/" + ] + } ], "no-restricted-syntax": [ - "off" + "error", + { + "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", + "message": "Use the md5hash() function from the core library if you want md5" + } ] } } diff --git a/packages/@aws-cdk/cdk-assets-lib/.eslintrc.json b/packages/@aws-cdk/cdk-assets-lib/.eslintrc.json index a11f0bc31..4ed46c0ff 100644 --- a/packages/@aws-cdk/cdk-assets-lib/.eslintrc.json +++ b/packages/@aws-cdk/cdk-assets-lib/.eslintrc.json @@ -362,10 +362,25 @@ ], "rules": { "no-restricted-imports": [ - "off" + "error", + { + "paths": [ + { + "name": "punycode", + "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" + } + ], + "patterns": [ + "!punycode/" + ] + } ], "no-restricted-syntax": [ - "off" + "error", + { + "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", + "message": "Use the md5hash() function from the core library if you want md5" + } ] } } diff --git a/packages/@aws-cdk/cdk-explorer/.eslintrc.json b/packages/@aws-cdk/cdk-explorer/.eslintrc.json index 117ce8067..41efb7f7f 100644 --- a/packages/@aws-cdk/cdk-explorer/.eslintrc.json +++ b/packages/@aws-cdk/cdk-explorer/.eslintrc.json @@ -342,10 +342,25 @@ ], "rules": { "no-restricted-imports": [ - "off" + "error", + { + "paths": [ + { + "name": "punycode", + "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" + } + ], + "patterns": [ + "!punycode/" + ] + } ], "no-restricted-syntax": [ - "off" + "error", + { + "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", + "message": "Use the md5hash() function from the core library if you want md5" + } ] } } diff --git a/packages/@aws-cdk/cli-plugin-contract/.eslintrc.json b/packages/@aws-cdk/cli-plugin-contract/.eslintrc.json index 0e3165c02..a2b63dee4 100644 --- a/packages/@aws-cdk/cli-plugin-contract/.eslintrc.json +++ b/packages/@aws-cdk/cli-plugin-contract/.eslintrc.json @@ -343,10 +343,25 @@ ], "rules": { "no-restricted-imports": [ - "off" + "error", + { + "paths": [ + { + "name": "punycode", + "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" + } + ], + "patterns": [ + "!punycode/" + ] + } ], "no-restricted-syntax": [ - "off" + "error", + { + "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", + "message": "Use the md5hash() function from the core library if you want md5" + } ] } } diff --git a/packages/@aws-cdk/cloud-assembly-api/.eslintrc.json b/packages/@aws-cdk/cloud-assembly-api/.eslintrc.json index 0e3165c02..a2b63dee4 100644 --- a/packages/@aws-cdk/cloud-assembly-api/.eslintrc.json +++ b/packages/@aws-cdk/cloud-assembly-api/.eslintrc.json @@ -343,10 +343,25 @@ ], "rules": { "no-restricted-imports": [ - "off" + "error", + { + "paths": [ + { + "name": "punycode", + "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" + } + ], + "patterns": [ + "!punycode/" + ] + } ], "no-restricted-syntax": [ - "off" + "error", + { + "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", + "message": "Use the md5hash() function from the core library if you want md5" + } ] } } diff --git a/packages/@aws-cdk/cloud-assembly-schema/.eslintrc.json b/packages/@aws-cdk/cloud-assembly-schema/.eslintrc.json index 0e3165c02..a2b63dee4 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/.eslintrc.json +++ b/packages/@aws-cdk/cloud-assembly-schema/.eslintrc.json @@ -343,10 +343,25 @@ ], "rules": { "no-restricted-imports": [ - "off" + "error", + { + "paths": [ + { + "name": "punycode", + "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" + } + ], + "patterns": [ + "!punycode/" + ] + } ], "no-restricted-syntax": [ - "off" + "error", + { + "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", + "message": "Use the md5hash() function from the core library if you want md5" + } ] } } diff --git a/packages/@aws-cdk/cloudformation-diff/.eslintrc.json b/packages/@aws-cdk/cloudformation-diff/.eslintrc.json index 0e3165c02..a2b63dee4 100644 --- a/packages/@aws-cdk/cloudformation-diff/.eslintrc.json +++ b/packages/@aws-cdk/cloudformation-diff/.eslintrc.json @@ -343,10 +343,25 @@ ], "rules": { "no-restricted-imports": [ - "off" + "error", + { + "paths": [ + { + "name": "punycode", + "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" + } + ], + "patterns": [ + "!punycode/" + ] + } ], "no-restricted-syntax": [ - "off" + "error", + { + "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", + "message": "Use the md5hash() function from the core library if you want md5" + } ] } } diff --git a/packages/@aws-cdk/integ-runner/.eslintrc.json b/packages/@aws-cdk/integ-runner/.eslintrc.json index 0e3165c02..a2b63dee4 100644 --- a/packages/@aws-cdk/integ-runner/.eslintrc.json +++ b/packages/@aws-cdk/integ-runner/.eslintrc.json @@ -343,10 +343,25 @@ ], "rules": { "no-restricted-imports": [ - "off" + "error", + { + "paths": [ + { + "name": "punycode", + "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" + } + ], + "patterns": [ + "!punycode/" + ] + } ], "no-restricted-syntax": [ - "off" + "error", + { + "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", + "message": "Use the md5hash() function from the core library if you want md5" + } ] } } diff --git a/packages/@aws-cdk/private-tools/.eslintrc.json b/packages/@aws-cdk/private-tools/.eslintrc.json index 117ce8067..41efb7f7f 100644 --- a/packages/@aws-cdk/private-tools/.eslintrc.json +++ b/packages/@aws-cdk/private-tools/.eslintrc.json @@ -342,10 +342,25 @@ ], "rules": { "no-restricted-imports": [ - "off" + "error", + { + "paths": [ + { + "name": "punycode", + "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" + } + ], + "patterns": [ + "!punycode/" + ] + } ], "no-restricted-syntax": [ - "off" + "error", + { + "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", + "message": "Use the md5hash() function from the core library if you want md5" + } ] } } diff --git a/packages/@aws-cdk/toolkit-lib/.eslintrc.json b/packages/@aws-cdk/toolkit-lib/.eslintrc.json index 3c8dbe97e..8c3e061a1 100644 --- a/packages/@aws-cdk/toolkit-lib/.eslintrc.json +++ b/packages/@aws-cdk/toolkit-lib/.eslintrc.json @@ -357,10 +357,25 @@ ], "rules": { "no-restricted-imports": [ - "off" + "error", + { + "paths": [ + { + "name": "punycode", + "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" + } + ], + "patterns": [ + "!punycode/" + ] + } ], "no-restricted-syntax": [ - "off" + "error", + { + "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", + "message": "Use the md5hash() function from the core library if you want md5" + } ] } }, diff --git a/packages/@aws-cdk/user-input-gen/.eslintrc.json b/packages/@aws-cdk/user-input-gen/.eslintrc.json index 117ce8067..41efb7f7f 100644 --- a/packages/@aws-cdk/user-input-gen/.eslintrc.json +++ b/packages/@aws-cdk/user-input-gen/.eslintrc.json @@ -342,10 +342,25 @@ ], "rules": { "no-restricted-imports": [ - "off" + "error", + { + "paths": [ + { + "name": "punycode", + "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" + } + ], + "patterns": [ + "!punycode/" + ] + } ], "no-restricted-syntax": [ - "off" + "error", + { + "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", + "message": "Use the md5hash() function from the core library if you want md5" + } ] } } diff --git a/packages/@aws-cdk/yarn-cling/.eslintrc.json b/packages/@aws-cdk/yarn-cling/.eslintrc.json index 117ce8067..41efb7f7f 100644 --- a/packages/@aws-cdk/yarn-cling/.eslintrc.json +++ b/packages/@aws-cdk/yarn-cling/.eslintrc.json @@ -342,10 +342,25 @@ ], "rules": { "no-restricted-imports": [ - "off" + "error", + { + "paths": [ + { + "name": "punycode", + "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" + } + ], + "patterns": [ + "!punycode/" + ] + } ], "no-restricted-syntax": [ - "off" + "error", + { + "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", + "message": "Use the md5hash() function from the core library if you want md5" + } ] } } diff --git a/packages/aws-cdk/.eslintrc.json b/packages/aws-cdk/.eslintrc.json index 0ea33b4b4..069e325d1 100644 --- a/packages/aws-cdk/.eslintrc.json +++ b/packages/aws-cdk/.eslintrc.json @@ -353,10 +353,25 @@ ], "rules": { "no-restricted-imports": [ - "off" + "error", + { + "paths": [ + { + "name": "punycode", + "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" + } + ], + "patterns": [ + "!punycode/" + ] + } ], "no-restricted-syntax": [ - "off" + "error", + { + "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", + "message": "Use the md5hash() function from the core library if you want md5" + } ] } }, diff --git a/packages/cdk-assets/.eslintrc.json b/packages/cdk-assets/.eslintrc.json index 0e3165c02..a2b63dee4 100644 --- a/packages/cdk-assets/.eslintrc.json +++ b/packages/cdk-assets/.eslintrc.json @@ -343,10 +343,25 @@ ], "rules": { "no-restricted-imports": [ - "off" + "error", + { + "paths": [ + { + "name": "punycode", + "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" + } + ], + "patterns": [ + "!punycode/" + ] + } ], "no-restricted-syntax": [ - "off" + "error", + { + "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", + "message": "Use the md5hash() function from the core library if you want md5" + } ] } } diff --git a/packages/cdk/.eslintrc.json b/packages/cdk/.eslintrc.json index 0e3165c02..a2b63dee4 100644 --- a/packages/cdk/.eslintrc.json +++ b/packages/cdk/.eslintrc.json @@ -343,10 +343,25 @@ ], "rules": { "no-restricted-imports": [ - "off" + "error", + { + "paths": [ + { + "name": "punycode", + "message": "Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation" + } + ], + "patterns": [ + "!punycode/" + ] + } ], "no-restricted-syntax": [ - "off" + "error", + { + "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", + "message": "Use the md5hash() function from the core library if you want md5" + } ] } } diff --git a/projenrc/eslint/imports.ts b/projenrc/eslint/imports.ts index 5a674e2b9..82cab159a 100644 --- a/projenrc/eslint/imports.ts +++ b/projenrc/eslint/imports.ts @@ -1,3 +1,8 @@ +export const PUNYCODE_IMPORT_RESTRICTION = { + name: 'punycode', + message: 'Package \'punycode\' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation', +}; + export default { 'import/no-unresolved': ['error'], // Require all imported libraries actually resolve (!!required for import/no-extraneous-dependencies to work!!) 'import/no-duplicates': 'error', // Cannot import from the same module twice (we prefer `import/no-duplicate` over `no-duplicate-imports` since the former can handle type imports) @@ -15,10 +20,7 @@ export default { 'no-restricted-imports': [ 'error', { paths: [ - { - name: 'punycode', - message: 'Package \'punycode\' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation', - }, + PUNYCODE_IMPORT_RESTRICTION, // Route child process spawning through the shared subprocess tool. { name: 'child_process', diff --git a/projenrc/eslint/index.ts b/projenrc/eslint/index.ts index ee34fa628..b939d6da9 100644 --- a/projenrc/eslint/index.ts +++ b/projenrc/eslint/index.ts @@ -3,10 +3,10 @@ import type { typescript } from 'projen'; import bestPractices from './best-practices'; import constructs from './constructs'; import formatting from './formatting'; -import imports from './imports'; +import imports, { PUNYCODE_IMPORT_RESTRICTION } from './imports'; import jest from './jest'; import jsdoc from './jsdoc'; -import team from './team'; +import team, { MD5_SYNTAX_RESTRICTION } from './team'; const ESLINT_RULES = { ...team, @@ -60,16 +60,6 @@ export function configureEslint(x: typescript.TypeScriptProject) { 'plugin:jest/recommended', ); x.eslint?.addRules(ESLINT_RULES); - - // The `child_process` / `shell: true` bans protect SHIPPED code from spawning - // outside the subprocess tool. Two categories are outside that trust boundary - // and are exempt: - // - test code, which legitimately imports `child_process` to mock/spy and - // may spawn with `shell: true` against throwaway fixtures; - // - build-time tooling (projen config, build tasks), which runs on - // developer/CI machines rather than a user's machine. - // (This also relaxes the md5/punycode rules for these files, which is harmless - // for non-shipped code.) x.eslint?.addOverride({ files: [ '**/test/**', @@ -82,8 +72,8 @@ export function configureEslint(x: typescript.TypeScriptProject) { '**/.projenrc.ts', ], rules: { - 'no-restricted-imports': ['off'], - 'no-restricted-syntax': ['off'], + 'no-restricted-imports': ['error', { paths: [PUNYCODE_IMPORT_RESTRICTION], patterns: ['!punycode/'] }], + 'no-restricted-syntax': ['error', MD5_SYNTAX_RESTRICTION], }, }); diff --git a/projenrc/eslint/team.ts b/projenrc/eslint/team.ts index 5c24e95b2..f648d4db3 100644 --- a/projenrc/eslint/team.ts +++ b/projenrc/eslint/team.ts @@ -1,17 +1,20 @@ // CDK team specific rules // These are typically informed by past operational events + +// No more md5, will break in FIPS environments +export const MD5_SYNTAX_RESTRICTION = { + // Both qualified and unqualified calls + selector: "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", + message: 'Use the md5hash() function from the core library if you want md5', +}; + export default { // This can cause huge I/O performance hits '@cdklabs/promiseall-no-unbounded-parallelism': ['error'], - // No more md5, will break in FIPS environments 'no-restricted-syntax': [ 'error', - { - // Both qualified and unqualified calls - selector: "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", - message: 'Use the md5hash() function from the core library if you want md5', - }, + MD5_SYNTAX_RESTRICTION, { // Spawning through a shell is the one place command injection can happen. // The only sanctioned shell entry point is `runUserCommandLine` in the