From 0c98acf3a935be15554064cee2c017cc1c072da0 Mon Sep 17 00:00:00 2001 From: Nicholas Ellul <15018469+NicholasEllul@users.noreply.github.com> Date: Tue, 6 Jan 2026 14:54:37 -0500 Subject: [PATCH 1/3] Add npx rule for YAML files --- rules/src/generic/npx-usage/npx-usage-yml.yml | 43 +++++++++ .../generic/npx-usage/npx-usage-yml.test.yml | 87 +++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 rules/src/generic/npx-usage/npx-usage-yml.yml create mode 100644 rules/test/generic/npx-usage/npx-usage-yml.test.yml diff --git a/rules/src/generic/npx-usage/npx-usage-yml.yml b/rules/src/generic/npx-usage/npx-usage-yml.yml new file mode 100644 index 0000000..e12ec45 --- /dev/null +++ b/rules/src/generic/npx-usage/npx-usage-yml.yml @@ -0,0 +1,43 @@ +rules: + - id: npx-usage-yml + languages: + - yaml + severity: WARNING + metadata: + tags: [security] + shortDescription: "npx usage introduces supply chain security risks" + confidence: HIGH + help: | + Using npx to install and run packages introduces significant supply chain security risks for the following reasons: + + 1. **Unpinned by default**: Running `npx ` fetches the latest release outside of your lockfile. If a malicious version of a package is published ([example])(https://socket.dev/blog/npm-author-qix-compromised-in-major-supply-chain-attack), `npx` will install and execute it the next time it is run. + + 2. **Bypasses lockfile guarantees**: Packages executed with npx are not added to your project's package.json or lockfile. As a result, their versions and lockfile integrity hashes are not captured for reproducibility, making builds non-deterministic and harder to audit + + ### Recommended practice + - Add packages as dependencies or devDependencies in `package.json`. + - Use your package manager to install and execute them (e.g., `yarn add --dev` followed by `yarn `). + + **Bad example (using npx):** + ```yaml + - name: Run tests + run: npx jest --coverage + ``` + + **Good example (proper dependency):** + ```yaml + - name: Run tests + run: yarn jest --coverage + ``` + + message: >- + Avoid using 'npx' to run packages due to supply chain security risks. Instead, install the package + as a dependency / devDependency and invoke it using your package manager to ensure version pinning + and reproducibility. + patterns: + - pattern: | + run: $CMD + - metavariable-pattern: + metavariable: $CMD + language: sh + pattern: npx ... diff --git a/rules/test/generic/npx-usage/npx-usage-yml.test.yml b/rules/test/generic/npx-usage/npx-usage-yml.test.yml new file mode 100644 index 0000000..9d187e6 --- /dev/null +++ b/rules/test/generic/npx-usage/npx-usage-yml.test.yml @@ -0,0 +1,87 @@ +name: Test Workflow + +on: + push: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # Test basic npx usage in GitHub Actions - should be flagged + - name: Run tests + # ruleid: npx-usage-yml + run: npx jest --coverage + + - name: Lint code + # ruleid: npx-usage-yml + run: npx eslint src/ + + - name: Format code + # ruleid: npx-usage-yml + run: npx prettier --write . + + - name: Create app + # ruleid: npx-usage-yml + run: npx create-react-app my-app + + - name: Setup tool with flag + # ruleid: npx-usage-yml + run: npx --yes setup-tool --config config.json + + - name: Run with env vars + # ruleid: npx-usage-yml + run: npx jest ${GITHUB_WORKSPACE} --coverage + + # Test scoped package with output redirection - should be flagged + - name: Generate fingerprint + # ruleid: npx-usage-yml + run: npx @expo/fingerprint ./ > fingerprint-pr.json + + - name: Another scoped package + # ruleid: npx-usage-yml + run: npx @typescript-eslint/parser --version + + # Test npx in middle of command strings - should be flagged + - name: Install and test + # ruleid: npx-usage-yml + run: yarn install && npx jest --coverage + + - name: Setup and lint + # ruleid: npx-usage-yml + run: echo "Setting up" && npx eslint src/ + + - name: Build and format + # ruleid: npx-usage-yml + run: npm run build && npx prettier --write . + + # Test good alternatives - should not be flagged + - name: Run tests with yarn + # ok: npx-usage-yml + run: yarn jest --coverage + + - name: Lint code with yarn + # ok: npx-usage-yml + run: yarn eslint src/ + + - name: Format code with yarn + # ok: npx-usage-yml + run: yarn prettier --write . + + - name: Create app with yarn dlx + # ok: npx-usage-yml + run: yarn dlx create-react-app my-app + + - name: Build with npm script + # ok: npx-usage-yml + run: npm run build + + - name: Description mentions npx but doesn't use it + # ok: npx-usage-yml + run: echo "This workflow mentions npx but doesn't execute it" + + - name: Just npm (not npx) + # ok: npx-usage-yml + run: npm install From 075f928cc3ff9a343074cc69a8da6518a669b20a Mon Sep 17 00:00:00 2001 From: Nicholas Ellul <15018469+NicholasEllul@users.noreply.github.com> Date: Tue, 6 Jan 2026 15:34:10 -0500 Subject: [PATCH 2/3] Add npx detection in JS/TS --- rules/src/generic/npx-usage/npx-usage-js.yml | 47 ++++++++ .../generic/npx-usage/npx-usage-js.test.js | 108 ++++++++++++++++++ .../generic/npx-usage/npx-usage-js.test.ts | 65 +++++++++++ 3 files changed, 220 insertions(+) create mode 100644 rules/src/generic/npx-usage/npx-usage-js.yml create mode 100644 rules/test/generic/npx-usage/npx-usage-js.test.js create mode 100644 rules/test/generic/npx-usage/npx-usage-js.test.ts diff --git a/rules/src/generic/npx-usage/npx-usage-js.yml b/rules/src/generic/npx-usage/npx-usage-js.yml new file mode 100644 index 0000000..84e9cb1 --- /dev/null +++ b/rules/src/generic/npx-usage/npx-usage-js.yml @@ -0,0 +1,47 @@ +rules: + - id: npx-usage-js + languages: + - javascript + - typescript + severity: WARNING + metadata: + tags: [security] + shortDescription: "npx usage introduces supply chain security risks" + confidence: HIGH + help: | + Using npx to install and run packages introduces significant supply chain security risks for the following reasons: + + 1. **Unpinned by default**: Running `npx ` fetches the latest release outside of your lockfile. If a malicious version of a package is published ([example])(https://socket.dev/blog/npm-author-qix-compromised-in-major-supply-chain-attack), `npx` will install and execute it the next time it is run. + + 2. **Bypasses lockfile guarantees**: Packages executed with npx are not added to your project's package.json or lockfile. As a result, their versions and lockfile integrity hashes are not captured for reproducibility, making builds non-deterministic and harder to audit + + ### Recommended practice + - Add packages as dependencies or devDependencies in `package.json`. + - Use your package manager to install and execute them (e.g., `yarn add [--dev]` followed by `yarn `). + + **Bad example (using npx):** + ```javascript + const cmd = `npx jest --coverage`; + execSync(cmd); + ``` + + **Good example (proper dependency):** + ```javascript + // Add jest as a dependency /devDependency in package.json + const cmd = `yarn jest --coverage`; + execSync(cmd); + ``` + + message: >- + Avoid using 'npx' to run packages due to supply chain security risks. Instead, install the package + as a dependency / devDependency and invoke it using your package manager to ensure version pinning + and reproducibility. + patterns: + - pattern-either: + - pattern: "$CMD" + - pattern: | + `$CMD` + - metavariable-pattern: + metavariable: $CMD + language: sh + pattern: npx ... diff --git a/rules/test/generic/npx-usage/npx-usage-js.test.js b/rules/test/generic/npx-usage/npx-usage-js.test.js new file mode 100644 index 0000000..bbd1f5b --- /dev/null +++ b/rules/test/generic/npx-usage/npx-usage-js.test.js @@ -0,0 +1,108 @@ +const { execSync, exec, spawn, spawnSync } = require('child_process'); + +// Test cases that should be flagged + +// Template literal with interpolation (like coverage-analysis.js:234) +function runTests() { + const testArgs = 'test/*.js'; + // ruleid: npx-usage-js + const cmd = `npx jest ${testArgs} --coverage --coverageReporters=lcov`; + execSync(cmd); +} + +// Template literal passed directly to exec +function lintCode() { + // ruleid: npx-usage-js + execSync(`npx eslint src/`); +} + +// String literal in error message (like global.setup.ts:72) +// This won't be caught because "Example: npx..." doesn't parse as shell command starting with npx +function throwError() { + throw new Error( + // ok: npx-usage-js + 'Please specify a project name with --project flag. Example: npx playwright test --project dummy-test-local' + ); +} + +// String literal with scoped package +function formatCode() { + // ruleid: npx-usage-js + const command = "npx @typescript-eslint/parser --version"; + exec(command); +} + +// Template literal with output redirection +function generateFingerprint() { + // ruleid: npx-usage-js + exec(`npx @expo/fingerprint ./ > fingerprint.json`); +} + +// Template literal with flags +function setupTool() { + // ruleid: npx-usage-js + const setupCmd = `npx --yes create-react-app my-app`; + execSync(setupCmd); +} + +// Template literal with environment variables +function runWithEnv() { + const workspace = process.env.GITHUB_WORKSPACE; + // ruleid: npx-usage-js + spawn(`npx jest ${workspace} --coverage`); +} + +// Template literal in command chain +function buildAndTest() { + // ruleid: npx-usage-js + execSync(`yarn build && npx jest --coverage`); +} + +// String literal assigned to variable +function assignCommand() { + // ruleid: npx-usage-js + let cmd = "npx prettier --write ."; + return cmd; +} + +// Test cases that should NOT be flagged + +// Using yarn instead +function goodYarnUsage() { + // ok: npx-usage-js + execSync(`yarn jest --coverage`); +} + +// Using npm scripts +function goodNpmUsage() { + // ok: npx-usage-js + execSync('npm run test'); +} + +// Using yarn dlx +function goodYarnDlx() { + // ok: npx-usage-js + const cmd = `yarn dlx create-react-app my-app`; + execSync(cmd); +} + +// Direct node execution +function goodNodeUsage() { + // ok: npx-usage-js + exec('node scripts/build.js'); +} + +// Comment mentioning npx - should be ignored automatically +// This comment talks about npx but isn't code execution +function withComment() { + // ok: npx-usage-js + execSync('yarn test'); +} + +// Variable name contains "npx" but not executing it +function variableName() { + // ok: npx-usage-js + const shouldUseNpx = false; + const npxWarning = "Dont use npx!"; + console.log(npxWarning); +} diff --git a/rules/test/generic/npx-usage/npx-usage-js.test.ts b/rules/test/generic/npx-usage/npx-usage-js.test.ts new file mode 100644 index 0000000..8caf039 --- /dev/null +++ b/rules/test/generic/npx-usage/npx-usage-js.test.ts @@ -0,0 +1,65 @@ +import { execSync, exec, spawn } from 'child_process'; + +// Test cases that should be flagged in TypeScript + +// Template literal with type annotation (like global.setup.ts scenario) +function runPlaywrightTests(): void { + const project: string = 'test-project'; + // ruleid: npx-usage-js + const command: string = `npx playwright test --project ${project}`; + execSync(command); +} + +// Error message with npx example +function throwConfigError(): never { + throw new Error( + // This may or may not be caught depending on shell parsing + 'Please specify a project name with --project flag. Example: npx playwright test --project dummy-test-local' + ); +} + +// Arrow function with template literal +const buildApp = (): void => { + // ruleid: npx-usage-js + execSync(`npx tsc --build`); +}; + +// Async function +async function deployApp(): Promise { + // ruleid: npx-usage-js + await exec(`npx vercel deploy`); +} + +// String literal with type assertion +function formatFiles(): void { + // ruleid: npx-usage-js + const cmd = "npx prettier --write ." as const; + execSync(cmd); +} + +// Template literal in class method +class TestRunner { + runTests(): void { + const coverage: boolean = true; + // ruleid: npx-usage-js + execSync(`npx jest ${coverage ? '--coverage' : ''}`); + } +} + +// Good examples that should NOT be flagged + +function useYarnProperly(): void { + // ok: npx-usage-js + execSync('yarn test --coverage'); +} + +function useNpmScript(): void { + // ok: npx-usage-js + exec('npm run build'); +} + +function useYarnDlx(): void { + // ok: npx-usage-js + const cmd: string = `yarn dlx create-next-app my-app`; + execSync(cmd); +} From 8f924fca977340ef4f527835e9a88b99daaff0ce Mon Sep 17 00:00:00 2001 From: Nicholas Ellul <15018469+NicholasEllul@users.noreply.github.com> Date: Tue, 6 Jan 2026 15:51:36 -0500 Subject: [PATCH 3/3] Match all strings in js/ts --- rules/src/generic/npx-usage/npx-usage-js.yml | 12 ++++-------- rules/test/generic/npx-usage/npx-usage-js.test.js | 4 ++-- rules/test/generic/npx-usage/npx-usage-js.test.ts | 2 +- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/rules/src/generic/npx-usage/npx-usage-js.yml b/rules/src/generic/npx-usage/npx-usage-js.yml index 84e9cb1..cb3e4a1 100644 --- a/rules/src/generic/npx-usage/npx-usage-js.yml +++ b/rules/src/generic/npx-usage/npx-usage-js.yml @@ -37,11 +37,7 @@ rules: as a dependency / devDependency and invoke it using your package manager to ensure version pinning and reproducibility. patterns: - - pattern-either: - - pattern: "$CMD" - - pattern: | - `$CMD` - - metavariable-pattern: - metavariable: $CMD - language: sh - pattern: npx ... + - pattern: "$STRING" + - metavariable-regex: + metavariable: $STRING + regex: '.*\bnpx\s' diff --git a/rules/test/generic/npx-usage/npx-usage-js.test.js b/rules/test/generic/npx-usage/npx-usage-js.test.js index bbd1f5b..cc8a050 100644 --- a/rules/test/generic/npx-usage/npx-usage-js.test.js +++ b/rules/test/generic/npx-usage/npx-usage-js.test.js @@ -17,10 +17,10 @@ function lintCode() { } // String literal in error message (like global.setup.ts:72) -// This won't be caught because "Example: npx..." doesn't parse as shell command starting with npx +// Now caught with regex - flags npx usage anywhere in strings including docs/examples function throwError() { throw new Error( - // ok: npx-usage-js + // ruleid: npx-usage-js 'Please specify a project name with --project flag. Example: npx playwright test --project dummy-test-local' ); } diff --git a/rules/test/generic/npx-usage/npx-usage-js.test.ts b/rules/test/generic/npx-usage/npx-usage-js.test.ts index 8caf039..fb2daa4 100644 --- a/rules/test/generic/npx-usage/npx-usage-js.test.ts +++ b/rules/test/generic/npx-usage/npx-usage-js.test.ts @@ -13,7 +13,7 @@ function runPlaywrightTests(): void { // Error message with npx example function throwConfigError(): never { throw new Error( - // This may or may not be caught depending on shell parsing + // ruleid: npx-usage-js 'Please specify a project name with --project flag. Example: npx playwright test --project dummy-test-local' ); }