diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ff583f7f..8829c2be 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,9 +9,18 @@ on: jobs: verify: runs-on: ubuntu-latest + permissions: + contents: read + # Required so github/codeql-action/upload-sarif can publish the fallow + # report to GitHub code scanning. Report-only for now (see #15). + security-events: write steps: # Bumped 2026-06-01 (#4): Node 20 actions deprecated, runner forces Node 24 by 2026-06-02. - uses: actions/checkout@v5 + # Full history so `fallow audit` can resolve a merge-base against + # origin/main (its diff gate needs the base commit, not a shallow clone). + with: + fetch-depth: 0 - uses: actions/setup-node@v5 with: node-version: '20' @@ -21,3 +30,23 @@ jobs: - run: npm run lint - run: npm run test - run: npm run build + # Report-only static-analysis gate (#15). `fallow audit` is a diff gate, + # so it's scoped to changes vs origin/main; non-blocking for now (a + # follow-up flips it to a hard gate once the baseline is clean). The + # guard guarantees a syntactically valid SARIF even if fallow exits + # non-zero, so the upload step never fails on an empty file. + - name: fallow static analysis + run: | + npx fallow audit --format sarif --base origin/main > fallow.sarif \ + || echo "fallow audit exited non-zero (report-only, ignored)" + if [ ! -s fallow.sarif ]; then + echo '{"$schema":"https://json.schemastore.org/sarif-2.1.0.json","version":"2.1.0","runs":[{"tool":{"driver":{"name":"fallow"}},"results":[]}]}' > fallow.sarif + fi + # fallow emits one SARIF run per sub-analysis (dead-code/health/dupes) + # with an empty category; CodeQL rejects multiple runs sharing a + # category, so stamp each run with a distinct automationDetails.id. + node -e 'const fs=require("fs");const f="fallow.sarif";const j=JSON.parse(fs.readFileSync(f,"utf8"));j.runs.forEach((r,i)=>{r.automationDetails={...(r.automationDetails||{}),id:`fallow/${i}/`}});fs.writeFileSync(f,JSON.stringify(j))' + - name: upload fallow SARIF + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: fallow.sarif diff --git a/package-lock.json b/package-lock.json index adbba93f..a20067fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "autoprefixer": "^10.4.21", "eslint": "^9.29.0", "eslint-plugin-react": "^7.37.5", + "fallow": "^2.96.0", "globals": "^16.2.0", "postcss": "^8.5.6", "tailwindcss": "^3.4.17", @@ -930,6 +931,118 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@fallow-cli/darwin-arm64": { + "version": "2.96.0", + "resolved": "https://registry.npmjs.org/@fallow-cli/darwin-arm64/-/darwin-arm64-2.96.0.tgz", + "integrity": "sha512-y5zqGloIyeDnLXtrmCDSaANK+jrEXIR9an3UokkIhtCvztq2/Oi4ha2kWgBuNDQhrT8dk9tXTYX/+lBV9a7Yhg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@fallow-cli/darwin-x64": { + "version": "2.96.0", + "resolved": "https://registry.npmjs.org/@fallow-cli/darwin-x64/-/darwin-x64-2.96.0.tgz", + "integrity": "sha512-ofN1Y4MoGV2bO19j0p7REZBdflZJ7oSAAel8DOQkrcoAqa5rEu2iJAFO0u9I0gSDxQG/g++GawojPK2jWsGz7Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@fallow-cli/linux-arm64-gnu": { + "version": "2.96.0", + "resolved": "https://registry.npmjs.org/@fallow-cli/linux-arm64-gnu/-/linux-arm64-gnu-2.96.0.tgz", + "integrity": "sha512-GlHpuXQhrK/BpDLV2GahGHSL+LawzufDLGfFujdrLCsOsCHggqWr0T3NR6fuzHgapel/nhtjBRzWJSYAMz0PLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@fallow-cli/linux-arm64-musl": { + "version": "2.96.0", + "resolved": "https://registry.npmjs.org/@fallow-cli/linux-arm64-musl/-/linux-arm64-musl-2.96.0.tgz", + "integrity": "sha512-EAKnmAxvALNiSomznrT/UwGOSI3B/V2ISFoJFaIOcd+K2tVr9rbxNyv40gPWgzECWukiDn+u+vWMO+ul7fpd4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@fallow-cli/linux-x64-gnu": { + "version": "2.96.0", + "resolved": "https://registry.npmjs.org/@fallow-cli/linux-x64-gnu/-/linux-x64-gnu-2.96.0.tgz", + "integrity": "sha512-gobVZ9J1rhPanZyPkVHT0T9SmPHgC+C46IPPcjdjC0C655X7pAGV6CysVjvIBAo+pAIcUWVvkD5DiDa1OFYwgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@fallow-cli/linux-x64-musl": { + "version": "2.96.0", + "resolved": "https://registry.npmjs.org/@fallow-cli/linux-x64-musl/-/linux-x64-musl-2.96.0.tgz", + "integrity": "sha512-SjeU14V5SYUhXzYjKOVo2oijpxYSbMxKoiKzeiKNK+hmMGC7SoCPruP9jD8IdsTr1NmgR+MdkKafxuGmWBvWKw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@fallow-cli/win32-arm64-msvc": { + "version": "2.96.0", + "resolved": "https://registry.npmjs.org/@fallow-cli/win32-arm64-msvc/-/win32-arm64-msvc-2.96.0.tgz", + "integrity": "sha512-KL6Xf/J/XPOhK0VfIURFYCyJ11tfa4pn6+HzKc8hKa4DucwJlvpO+Pk4937hEQgCkLC/HmCzZWPn3rE7WiIvNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@fallow-cli/win32-x64-msvc": { + "version": "2.96.0", + "resolved": "https://registry.npmjs.org/@fallow-cli/win32-x64-msvc/-/win32-x64-msvc-2.96.0.tgz", + "integrity": "sha512-WXrNyYxqW4A1jMAZuMLInGuzL4E35kQlaDfLtGqkbC3vbbq36eMPmdoXuK8trtllqg0NA8Cd+KYQwN8xA8zzNA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@fontsource/poppins": { "version": "5.2.7", "resolved": "https://registry.npmjs.org/@fontsource/poppins/-/poppins-5.2.7.tgz", @@ -3424,6 +3537,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -3977,6 +4100,34 @@ "node": ">=12.0.0" } }, + "node_modules/fallow": { + "version": "2.96.0", + "resolved": "https://registry.npmjs.org/fallow/-/fallow-2.96.0.tgz", + "integrity": "sha512-fNEj+b/LUVXwfyxZPnr+SJjdQHCKjUbWgluW/wC7Q5kcJENHNEJtmFDgpHEqKY6gY257xRCgyg1AlSC8YQgROA==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-libc": "2.1.2" + }, + "bin": { + "fallow": "bin/fallow", + "fallow-lsp": "bin/fallow-lsp", + "fallow-mcp": "bin/fallow-mcp" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@fallow-cli/darwin-arm64": "2.96.0", + "@fallow-cli/darwin-x64": "2.96.0", + "@fallow-cli/linux-arm64-gnu": "2.96.0", + "@fallow-cli/linux-arm64-musl": "2.96.0", + "@fallow-cli/linux-x64-gnu": "2.96.0", + "@fallow-cli/linux-x64-musl": "2.96.0", + "@fallow-cli/win32-arm64-msvc": "2.96.0", + "@fallow-cli/win32-x64-msvc": "2.96.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", diff --git a/package.json b/package.json index b098e5ee..3b28018d 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "autoprefixer": "^10.4.21", "eslint": "^9.29.0", "eslint-plugin-react": "^7.37.5", + "fallow": "^2.96.0", "globals": "^16.2.0", "postcss": "^8.5.6", "tailwindcss": "^3.4.17",