diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml new file mode 100644 index 000000000000..388cff16fe10 --- /dev/null +++ b/.github/workflows/security-scan.yml @@ -0,0 +1,41 @@ +name: OpenGrep Triage and Remediation Prod + +permissions: + contents: read + id-token: write + +on: + workflow_dispatch: + +env: + OPENGREP_VERSION: "v1.16.1" + +jobs: + opengrep-scan-and-process: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Download OpenGrep + run: | + curl -sL "https://github.com/opengrep/opengrep/releases/download/${OPENGREP_VERSION}/opengrep_manylinux_x86" -o opengrep + chmod +x opengrep + + - name: Run OpenGrep scan + run: | + ./opengrep scan --sarif --sarif-output=opengrep-results.sarif --config auto . || true + + - name: Upload SARIF as artifact + uses: actions/upload-artifact@v4 + with: + name: opengrep-sarif + path: opengrep-results.sarif + retention-days: 7 + + - name: AppSecAI Triage and Remediation + uses: AppSecureAI/automation-action@v1 + with: + file: opengrep-results.sarif diff --git a/packages/react-devtools-core/src/editor.js b/packages/react-devtools-core/src/editor.js index 50728c941cf4..f5784afc4587 100644 --- a/packages/react-devtools-core/src/editor.js +++ b/packages/react-devtools-core/src/editor.js @@ -12,6 +12,10 @@ import {basename, join, isAbsolute} from 'path'; import {execSync, spawn} from 'child_process'; import {parse} from 'shell-quote'; +// Characters that cmd.exe interprets as shell operators, enabling OS command +// injection (CWE-78) when user-controlled strings are passed via /C. +const SHELL_METACHARACTERS_RE = /[;&|`$<>()\n\r]/; + function isTerminalEditor(editor: string): boolean { switch (editor) { case 'vim': @@ -97,11 +101,13 @@ function guessEditor(): Array { } } - // Last resort, use old-school env vars + // Last resort, use old-school env vars. + // Parse via shell-quote (same as REACT_EDITOR) so the value is split on + // spaces but shell metacharacters are not expanded into a shell command. if (process.env.VISUAL) { - return [process.env.VISUAL]; + return parse(process.env.VISUAL); } else if (process.env.EDITOR) { - return [process.env.EDITOR]; + return parse(process.env.EDITOR); } return []; @@ -116,6 +122,14 @@ export function getValidFilePath( // We use relative paths at Facebook with deterministic builds. // This is why our internal tooling calls React DevTools with absoluteProjectRoots. // If the filename is absolute then we don't need to care about this. + + // Reject paths containing shell metacharacters to prevent CWE-78 OS Command + // Injection. On Windows, file paths are passed through cmd.exe which + // interprets characters like &, |, ; as shell operators. + if (SHELL_METACHARACTERS_RE.test(maybeRelativePath)) { + return null; + } + if (isAbsolute(maybeRelativePath)) { if (existsSync(maybeRelativePath)) { return maybeRelativePath; @@ -161,6 +175,13 @@ export function launchEditor( return; } + // Reject editor binaries containing shell metacharacters to prevent CWE-78. + // On Windows the editor string is passed to cmd.exe /C which would interpret + // operators like & and | as command separators. + if (SHELL_METACHARACTERS_RE.test(editor)) { + return; + } + let args = destructuredArgs; if (lineNumber) {