diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml new file mode 100644 index 0000000..cf5574d --- /dev/null +++ b/.github/codeql/codeql-config.yml @@ -0,0 +1,20 @@ +name: TariffShield CodeQL configuration + +queries: + - uses: security-and-quality + +paths: + - apps/api/src + - apps/web + - packages/sdk/src + - scripts + +paths-ignore: + - "**/node_modules/**" + - "**/dist/**" + - "**/build/**" + - "**/.next/**" + - "**/coverage/**" + - "**/generated/**" + - "**/*.generated.ts" + - "**/*.generated.tsx" diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 0415106..22c45f7 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -6,31 +6,94 @@ on: pull_request: branches: [main, master] schedule: - - cron: '0 8 * * 1' + - cron: "0 8 * * 1" + workflow_dispatch: + +permissions: + actions: read + contents: read + security-events: write jobs: analyze: - name: analyze + name: Analyze JavaScript and TypeScript runs-on: ubuntu-latest - permissions: - security-events: write - actions: read - contents: read - - strategy: - fail-fast: false - matrix: - language: [javascript-typescript] + timeout-minutes: 20 steps: - - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci + - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: - languages: ${{ matrix.language }} + languages: javascript-typescript + config-file: ./.github/codeql/codeql-config.yml + - name: Autobuild uses: github/codeql-action/autobuild@v3 + - name: Perform CodeQL analysis + id: analyze uses: github/codeql-action/analyze@v3 with: - category: /language:${{ matrix.language }} + category: "/language:javascript-typescript" + output: codeql-results + + - name: Fail on blocking CodeQL findings + shell: bash + run: | + node <<'NODE' + const fs = require('fs'); + const path = require('path'); + + const sarifDir = process.env.SARIF_DIR || 'codeql-results'; + if (!fs.existsSync(sarifDir)) { + console.log(`No SARIF output directory found at ${sarifDir}; skipping severity gate.`); + process.exit(0); + } + + const sarifFiles = fs.readdirSync(sarifDir) + .filter((file) => file.endsWith('.sarif')) + .map((file) => path.join(sarifDir, file)); + + let blocking = 0; + let warnings = 0; + + for (const sarifFile of sarifFiles) { + const sarif = JSON.parse(fs.readFileSync(sarifFile, 'utf8')); + for (const run of sarif.runs || []) { + const rules = run.tool?.driver?.rules || []; + for (const result of run.results || []) { + const rule = Number.isInteger(result.ruleIndex) + ? rules[result.ruleIndex] + : rules.find((candidate) => candidate.id === result.ruleId); + const securitySeverity = Number(rule?.properties?.['security-severity'] || 0); + if (result.level === 'error' || securitySeverity >= 9) { + blocking += 1; + } else if (result.level === 'warning') { + warnings += 1; + } + } + } + } + + console.log(`CodeQL warnings: ${warnings}`); + console.log(`CodeQL blocking findings: ${blocking}`); + + if (blocking > 0) { + console.error('CodeQL found error-level or critical-severity results. Review the code scanning alerts before merging.'); + process.exit(1); + } + NODE + env: + SARIF_DIR: ${{ steps.analyze.outputs.sarif-output }} diff --git a/docs/security.md b/docs/security.md new file mode 100644 index 0000000..0dcb4ff --- /dev/null +++ b/docs/security.md @@ -0,0 +1,32 @@ +# Security scanning + +TariffShield uses GitHub CodeQL to scan the JavaScript and TypeScript codebase for security and code-quality issues. + +## CodeQL coverage + +The CodeQL workflow runs for pull requests targeting `main`, on a weekly schedule at `03:00 UTC` every Monday, and on manual dispatch. It analyzes JavaScript and TypeScript with the `security-and-quality` query suite. + +The scan covers the API, web app, SDK, and TypeScript scripts: + +- `apps/api/src` +- `apps/web` +- `packages/sdk/src` +- `scripts` + +Generated and build outputs are excluded through `.github/codeql/codeql-config.yml`, including `node_modules`, `dist`, `build`, `.next`, coverage output, and generated TypeScript files. + +## Triage policy + +CodeQL findings with `error` level or critical security severity are release blockers. The workflow reads the generated SARIF report after analysis and fails the pull request check when those blocking findings are present. Maintainers should also require the CodeQL check before merging protected branches and should fix or explicitly dismiss those alerts in GitHub code scanning before a pull request is merged. + +Warning-level findings should be reviewed from the pull request annotations and the repository Security tab. They do not block merge by default, but they should be marked as fixed, false positive, or accepted risk during triage. + +## Reviewing alerts + +1. Open the repository Security tab. +2. Select Code scanning alerts. +3. Filter by tool `CodeQL` and sort by severity. +4. Assign each alert to the relevant owner. +5. Close the alert only after the fix is merged or after the maintainer records why the finding is not exploitable. + +If a CodeQL alert identifies a credential exposure, authentication bypass, injection path, or unsafe cryptographic use, treat it as a security incident and follow `docs/security/incident-response-playbook.md`.