|
| 1 | +# CodeInspectus CI — automatic checks on every pull request and push to master (CG-77). |
| 2 | +# |
| 3 | +# Rationale (CG-71 Claim 5): the repo had NO automatic PR/push CI — only the |
| 4 | +# manual pin-engines.yml. This adds it, in two tiers that match the tool's own |
| 5 | +# engine-gated design (evals E16/E17 self-skip when the security engines are absent): |
| 6 | +# |
| 7 | +# always-run (no engines) — MUST stay green on every PR regardless of engines. |
| 8 | +# Pure-TS surface only: typecheck, build, unit tests, the MCP stdio transport |
| 9 | +# smoke, the eval harness (E01-E15 run; E16 Opengrep + E17 Trivy self-skip), |
| 10 | +# and an npm-pack contents gate (runtime-only payload; no src/fixtures leak). |
| 11 | +# Redaction is covered here by eval E03; the gitleaks-backed redaction-e2e is |
| 12 | +# engine-dependent and runs in the engine job below. |
| 13 | +# |
| 14 | +# engine (linux/amd64) — fetches + cosign/checksum-verifies + SHA-pins the three |
| 15 | +# engine binaries and pulls the Trivy vuln DB, then runs the FULL eval (E16 + |
| 16 | +# E17 now execute -> 17/17), the gitleaks-backed redaction-e2e, and a real |
| 17 | +# fixture scan. Proven feasible in CG-77 recon: every engine has a non-null |
| 18 | +# linux-x64 pin and a fresh verified install reproduces the committed SHAs. |
| 19 | +# |
| 20 | +# Network egress used by the engine job (GitHub-hosted runners have UNRESTRICTED |
| 21 | +# egress, so no allow-list is enforced here; listed for the record / future |
| 22 | +# hardening — a too-tight allow-list would fail-close the job it protects): |
| 23 | +# github.com, objects.githubusercontent.com engine release assets + .sig/.cert/ |
| 24 | +# .sigstore.json/checksums; action code |
| 25 | +# *.sigstore.dev (fulcio/rekor/tuf-repo-cdn) cosign keyless verify-blob (tlog + TUF root) |
| 26 | +# mirror.gcr.io, ghcr.io Trivy vuln-DB OCI pull (install-time only) |
| 27 | +# registry.npmjs.org npm ci |
| 28 | +# |
| 29 | +# Third-party actions are pinned to a commit SHA (mirrors the project's engine |
| 30 | +# SHA-pin ethos). No run: step consumes untrusted github.event.* input. |
| 31 | +name: ci |
| 32 | + |
| 33 | +on: |
| 34 | + push: |
| 35 | + branches: [master] |
| 36 | + pull_request: |
| 37 | + |
| 38 | +permissions: |
| 39 | + contents: read |
| 40 | + |
| 41 | +concurrency: |
| 42 | + group: ci-${{ github.ref }} |
| 43 | + cancel-in-progress: true |
| 44 | + |
| 45 | +jobs: |
| 46 | + always: |
| 47 | + name: always-run (no engines) |
| 48 | + runs-on: ubuntu-latest |
| 49 | + steps: |
| 50 | + # audit mode — logs egress for a future block-mode allowlist (CG-78); does not restrict. |
| 51 | + - uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 |
| 52 | + with: |
| 53 | + egress-policy: audit |
| 54 | + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 |
| 55 | + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 |
| 56 | + with: |
| 57 | + node-version: 20 |
| 58 | + cache: npm |
| 59 | + - run: npm ci |
| 60 | + - name: Typecheck (tsc --noEmit) |
| 61 | + run: npm run typecheck |
| 62 | + - name: Build (tsc --noEmit && tsup) |
| 63 | + run: npm run build |
| 64 | + - name: Unit tests (vitest, full) |
| 65 | + run: npm test |
| 66 | + - name: MCP stdio transport smoke (boots, lists 6 tools, stdout pure JSON-RPC) |
| 67 | + run: node scripts/smoke-stdio.mjs |
| 68 | + - name: Eval — E01-E15 (engine evals E16/E17 self-skip with no engines) |
| 69 | + run: npm run eval |
| 70 | + - name: npm pack — runtime-only payload (no src/fixtures/evals/scripts/docs) |
| 71 | + run: | |
| 72 | + npm pack --dry-run --json | node -e ' |
| 73 | + const files = JSON.parse(require("fs").readFileSync(0, "utf8"))[0].files.map((f) => f.path); |
| 74 | + const leak = files.filter((f) => /^(src|fixtures|evals|scripts|docs)\//.test(f)); |
| 75 | + console.log("packed top-level:", [...new Set(files.map((f) => f.split("/")[0]))].sort().join(", ")); |
| 76 | + if (leak.length) { console.error("LEAK — non-runtime paths packed:", leak.join(", ")); process.exit(1); } |
| 77 | + console.log("OK — runtime-only,", files.length, "files"); |
| 78 | + ' |
| 79 | +
|
| 80 | + engine: |
| 81 | + name: engine (linux/amd64) |
| 82 | + runs-on: ubuntu-latest |
| 83 | + steps: |
| 84 | + # audit mode — logs egress for a future block-mode allowlist (CG-78); does not restrict. |
| 85 | + - uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 |
| 86 | + with: |
| 87 | + egress-policy: audit |
| 88 | + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 |
| 89 | + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 |
| 90 | + with: |
| 91 | + node-version: 20 |
| 92 | + cache: npm |
| 93 | + - name: Install cosign (mandatory, fail-closed, for opengrep + trivy verify) |
| 94 | + uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2 |
| 95 | + - run: npm ci |
| 96 | + - name: Build |
| 97 | + run: npm run build |
| 98 | + - name: Assert runner is linux/amd64 (lockfile platform linux-x64) |
| 99 | + run: | |
| 100 | + actual="$(node -e "process.stdout.write(process.platform + '-' + process.arch)")" |
| 101 | + echo "runner platform: $actual" |
| 102 | + test "$actual" = "linux-x64" |
| 103 | + - name: Install engines — fetch + cosign/checksum-verify + SHA-pin + Trivy DB |
| 104 | + run: node dist/index.js install-engines |
| 105 | + - name: Assert freshly-verified SHAs == committed engines.lock.json pins |
| 106 | + shell: bash |
| 107 | + run: | |
| 108 | + # install-engines rewrites only generated_at + provenance.at timestamps; every |
| 109 | + # sha256 pin must be byte-identical to what is committed. A changed pin means an |
| 110 | + # upstream asset moved under a fixed version tag -> fail closed (supply-chain). |
| 111 | + if git diff -- engines.lock.json | grep -qE '^[+-][[:space:]]*"sha256"'; then |
| 112 | + echo "FAIL: an engine SHA256 pin changed after a fresh verified install:" |
| 113 | + git diff -- engines.lock.json | grep -E '^[+-][[:space:]]*"sha256"' |
| 114 | + exit 1 |
| 115 | + fi |
| 116 | + echo "OK: all engine SHA256 pins unchanged (only install timestamps differ)." |
| 117 | + - name: Verify installed binaries against the lockfile (native OS) |
| 118 | + run: node dist/index.js verify-engines |
| 119 | + - name: Eval — full suite; REQUIRE E16 Opengrep + E17 Trivy to RUN (0 skipped) |
| 120 | + shell: bash |
| 121 | + run: | |
| 122 | + # The engine tier EXISTS to prove Opengrep + Trivy actually execute. A skip |
| 123 | + # here is a FAILURE, not acceptable degradation: if the Trivy DB pull silently |
| 124 | + # failed (populateTrivyDb logs + returns undefined, so install-engines still |
| 125 | + # exits 0) or an engine did not run, E16/E17 self-skip and the eval harness |
| 126 | + # exits 0 — a false green. Asserting 0 skipped turns that into a RED instead. |
| 127 | + out="${RUNNER_TEMP:-/tmp}/eval.out" |
| 128 | + set -o pipefail |
| 129 | + npm run eval | tee "$out" |
| 130 | + grep -qE '0 failed, 0 skipped' "$out" || { |
| 131 | + echo "FAIL: engine tier requires 0 skipped — E16 Opengrep + E17 Trivy must RUN, not skip (check the Trivy DB pull from mirror.gcr.io / ghcr.io)." |
| 132 | + exit 1 |
| 133 | + } |
| 134 | + - name: Redaction e2e — gitleaks-detected non-pattern secrets stay redacted |
| 135 | + run: npm run test:redaction |
| 136 | + - name: Real scan smoke on the fixture app (direct runScan path) |
| 137 | + run: npx tsx scripts/dev-scan.ts "$PWD/fixtures/vulnerable-app" |
0 commit comments