diff --git a/packages/semgrep-action/rules/src/generic/npx-usage/npx-usage-js.yml b/packages/semgrep-action/rules/src/generic/npx-usage/npx-usage-js.yml new file mode 100644 index 0000000..cb3e4a1 --- /dev/null +++ b/packages/semgrep-action/rules/src/generic/npx-usage/npx-usage-js.yml @@ -0,0 +1,43 @@ +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: "$STRING" + - metavariable-regex: + metavariable: $STRING + regex: '.*\bnpx\s' diff --git a/packages/semgrep-action/rules/src/generic/npx-usage/npx-usage-json.yml b/packages/semgrep-action/rules/src/generic/npx-usage/npx-usage-json.yml index 0d75a8b..0a90ecf 100644 --- a/packages/semgrep-action/rules/src/generic/npx-usage/npx-usage-json.yml +++ b/packages/semgrep-action/rules/src/generic/npx-usage/npx-usage-json.yml @@ -5,7 +5,7 @@ rules: severity: WARNING metadata: tags: [security] - shortDescription: 'npx usage introduces supply chain security risks' + 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: @@ -17,7 +17,7 @@ rules: ### 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):** ```json { @@ -26,7 +26,7 @@ rules: } } ``` - + **Good example (proper dependency):** ```json { @@ -38,9 +38,9 @@ rules: } } ``` - + 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. - pattern-regex: '"[^"]*":\s*"(\s*npx\s|npx\s)[^"]*"' + pattern-regex: '"[^"]*":\s*"(\s*npx\s|npx\s)[^"]*"' \ No newline at end of file diff --git a/packages/semgrep-action/rules/src/generic/npx-usage/npx-usage-shell.yml b/packages/semgrep-action/rules/src/generic/npx-usage/npx-usage-shell.yml index f233230..8d59213 100644 --- a/packages/semgrep-action/rules/src/generic/npx-usage/npx-usage-shell.yml +++ b/packages/semgrep-action/rules/src/generic/npx-usage/npx-usage-shell.yml @@ -6,7 +6,7 @@ rules: severity: WARNING metadata: tags: [security] - shortDescription: 'npx usage introduces supply chain security risks' + 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: @@ -18,8 +18,8 @@ rules: ### 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 `). - + - Use your package manager to install and execute them (e.g., `yarn add --dev` followed by `yarn `). + 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 diff --git a/packages/semgrep-action/rules/src/generic/npx-usage/npx-usage-yml.yml b/packages/semgrep-action/rules/src/generic/npx-usage/npx-usage-yml.yml new file mode 100644 index 0000000..e12ec45 --- /dev/null +++ b/packages/semgrep-action/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/packages/semgrep-action/rules/test/generic/npx-usage/npx-usage-js.test.js b/packages/semgrep-action/rules/test/generic/npx-usage/npx-usage-js.test.js new file mode 100644 index 0000000..cc8a050 --- /dev/null +++ b/packages/semgrep-action/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) +// Now caught with regex - flags npx usage anywhere in strings including docs/examples +function throwError() { + throw new Error( + // ruleid: 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/packages/semgrep-action/rules/test/generic/npx-usage/npx-usage-js.test.ts b/packages/semgrep-action/rules/test/generic/npx-usage/npx-usage-js.test.ts new file mode 100644 index 0000000..fb2daa4 --- /dev/null +++ b/packages/semgrep-action/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( + // ruleid: npx-usage-js + '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); +} diff --git a/packages/semgrep-action/rules/test/generic/npx-usage/npx-usage-yml.test.yml b/packages/semgrep-action/rules/test/generic/npx-usage/npx-usage-yml.test.yml new file mode 100644 index 0000000..9d187e6 --- /dev/null +++ b/packages/semgrep-action/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