diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cc479dc..9d37c4c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -155,3 +155,25 @@ jobs: if: always() working-directory: infra/compose run: docker compose down -v + + dependency-audit: + runs-on: ubuntu-latest + # E10: fail on high/critical production advisories only. Policy: + # docs/ops/dependency-audit.md + steps: + - uses: actions/checkout@v7 + + - uses: pnpm/action-setup@v6 + with: + version: 9.15.0 + + - uses: actions/setup-node@v7 + with: + node-version: "20" + cache: "pnpm" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: pnpm audit (prod, high+) + run: pnpm audit:ci diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..dae629d --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,51 @@ +name: CodeQL + +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + # Weekly Monday 06:30 UTC + - cron: "30 6 * * 1" + +concurrency: + group: codeql-${{ github.ref }} + cancel-in-progress: true + +jobs: + analyze: + name: Analyze JavaScript / TypeScript + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + # Required once GitHub code scanning is enabled on the repo (Settings → + # Code security). Until then we keep upload:never so analysis still gates CI. + security-events: write + + steps: + - uses: actions/checkout@v7 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: javascript-typescript + build-mode: none + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 + with: + category: "/language:javascript-typescript" + # Private repos without Code Scanning / GHAS cannot upload SARIF to + # the Security tab. Analysis still runs and fails the job on query + # errors. Enable code scanning in repo settings to switch to upload. + upload: never + + - name: Upload SARIF artifact + uses: actions/upload-artifact@v4 + with: + name: codeql-javascript-typescript + path: ${{ runner.temp }}/codeql/javascript/results/*.sarif + if-no-files-found: ignore + retention-days: 14 diff --git a/docs/ops/codeql.md b/docs/ops/codeql.md new file mode 100644 index 0000000..a1f9b9a --- /dev/null +++ b/docs/ops/codeql.md @@ -0,0 +1,20 @@ +# CodeQL (E10) + +Workflow: `.github/workflows/codeql.yml` — runs on `main` PRs/pushes + weekly. + +## Behavior + +- Analyzes JavaScript/TypeScript (`build-mode: none`). +- **Upload to GitHub Code Scanning** is `never` until code scanning is enabled on this private repository (Settings → Code security and analysis → Code scanning). Without that, SARIF upload fails the job even when analysis succeeds. +- SARIF is retained as a workflow artifact (`codeql-javascript-typescript`) for 14 days. + +## Enabling Security-tab upload + +1. Repo **Settings → Code security** → enable **Code scanning**. +2. Change `upload: never` → `upload: always` (or remove the `upload` key) in `.github/workflows/codeql.yml`. +3. Keep `permissions.security-events: write`. + +## Related + +- Dependency audit gate: [`dependency-audit.md`](./dependency-audit.md) +- Dependabot: `.github/dependabot.yml` diff --git a/docs/ops/dependency-audit.md b/docs/ops/dependency-audit.md new file mode 100644 index 0000000..dfca636 --- /dev/null +++ b/docs/ops/dependency-audit.md @@ -0,0 +1,45 @@ +# Dependency audit policy (E10) + +**Goal:** catch high/critical production advisories in CI without papering over real risk. + +## CI gate + +| Command | When | Fail on | +|---------|------|---------| +| `pnpm audit:ci` → `pnpm audit --prod --audit-level=high` | Every PR (`ci.yml` job `dependency-audit`) | **high** and **critical** only | + +Moderate / low findings are **visible** in local `pnpm audit --prod` but do not fail the gate. Prefer fixing them via Dependabot (already weekly) rather than ignoring. + +## Allowlist (carefully) + +If a high/critical advisory cannot be fixed immediately (no patch, or only via a breaking major): + +1. Prefer `pnpm.overrides` / dependency bump that removes the vulnerable path. +2. Only if blocked: add the **CVE / GHSA id** under `pnpm.auditConfig.ignoreCves` in the **root** `package.json`, with an inline comment in `docs/ops/dependency-audit.md` stating: + - advisory id + package + - why production risk is accepted (or not reachable) + - owner + review-by date +3. Never ignore an advisory “to make CI green” without that note. +4. Revisit allowlisted entries every Dependabot cycle; delete when fixed. + +Empty allowlist is the default. Do **not** copy npm’s `--force` / blanket ignore. + +Example shape (comments belong in this doc, not in `package.json`): + +```json +{ + "pnpm": { + "auditConfig": { + "ignoreCves": ["CVE-YYYY-NNNNN"] + } + } +} +``` + +Document each ignored id in this file with package, rationale, owner, and review-by date. + +## Related + +- Dependabot: `.github/dependabot.yml` (weekly npm + Actions minors/patches) +- CodeQL: `.github/workflows/codeql.yml` (SAST on default branch + PRs) — see [`codeql.md`](./codeql.md) +- Local: `pnpm audit --prod` (all severities) diff --git a/infra/ci/README.md b/infra/ci/README.md index 525ac17..b0173f8 100644 --- a/infra/ci/README.md +++ b/infra/ci/README.md @@ -17,8 +17,11 @@ This folder is reserved for reusable CI helper scripts if they outgrow the workf | `compose-smoke-test` | Infra startup | Compose Postgres + Redis + Qdrant healthy | | `integration-test` | Product path (D5.4 e2e) | `pnpm build` → migrate → `pnpm test:integration` | | | Golden eval (D5.2) | `pnpm test:eval` (hit@3 ≥80%, citation 100%) | +| `dependency-audit` | E10 supply-chain | `pnpm audit:ci` (prod, fail on high/critical) — see [`docs/ops/dependency-audit.md`](../../docs/ops/dependency-audit.md) | -All of the above use the checked-in sample-repo fixture + fake OpenAI-compatible provider — **no** GitHub/Ollama/cloud network on the required path (₹0 / offline CI). +**Also on PRs / main (separate workflow):** CodeQL JavaScript/TypeScript — `.github/workflows/codeql.yml`. + +All product-path jobs above use the checked-in sample-repo fixture + fake OpenAI-compatible provider — **no** GitHub/Ollama/cloud network on that path (₹0 / offline CI). Dependency audit and CodeQL need network for the advisory DB / CodeQL pack download. **Not in CI:** `codeoracle replay` against a product/dogfood index (needs a ready repo + real embeddings). That is an ops gate — see [`test/replay/README.md`](../../test/replay/README.md) and [`infra/dogfood/README.md`](../dogfood/README.md). diff --git a/package.json b/package.json index 1eebfe1..435e93e 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "test:golden": "vitest run --config test/golden-queries/vitest.config.ts", "test:eval": "INTEGRATION_TEST=1 vitest run --config test/golden-queries/eval.vitest.config.ts", "test:retrieval-gates": "pnpm --filter @codeoracle/core-domain test -- test/absolute-score-floor.test.ts test/secret-path-class.test.ts && pnpm --filter @codeoracle/retrieval exec vitest run test/search-codebase.test.ts test/find-decision.test.ts test/explain-file.test.ts && pnpm --filter @codeoracle/cli test -- src/replay/score.test.ts", + "audit:ci": "pnpm audit --prod --audit-level=high", "typecheck:e2e": "tsc -p test/e2e/tsconfig.json", "typecheck:golden": "tsc -p test/golden-queries/tsconfig.json", "fixture:sample-repo:tree": "pnpm --filter @codeoracle/worker exec tsx ../../test/fixtures/sample-repo/write-tree-cli.ts",