From 50e28f46b67c6a91df59f1b6c980e913a18bb27e Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Sun, 26 Apr 2026 02:22:28 +0000 Subject: [PATCH] chore(security): land OSS-CLI stack + wire OpenSSF Best Practices project (RAN-52) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add `.github/workflows/security.yml`: Semgrep, OSV-Scanner, Trivy fs, Gitleaks (Docker image, no license dep), jscpd, anchore/sbom-action. All actions SHA-pinned; SARIF → code scanning where supported, raw reports → workflow artifacts. Triggers: push/PR/weekly cron/dispatch. Subsumes the previously planned `osv-scanner.yml` (RAN-42). - Wire `.bestpractices.json` to project_id 12650 (registration unblocked between RAN-46 and RAN-52). - Replace the placeholder OpenSSF Best Practices README badge with the live `bestpractices.dev/projects/12650/badge`. - Document Scorecard baseline + target and the OSS-CLI stack in `CLAUDE.md` (new "Supply-chain observability (OpenSSF)" section). - Update `engineering-standards.md` §1 to reflect that OSV-Scanner has landed inside `security.yml`, and add a §5 row pointing at it. Auth-blocked items deferred to the board (called out in the PR body): final flip of the bestpractices.dev page from `in_progress` → `passing` and verification that signed-commit branch protection is enforced on `main`. Co-Authored-By: Claude Opus 4.7 (1M context) Co-Authored-By: Paperclip --- .bestpractices.json | 11 +- .github/workflows/security.yml | 330 +++++++++++++++++++++++ CLAUDE.md | 21 ++ README.md | 2 +- shared/runbooks/engineering-standards.md | 3 +- 5 files changed, 360 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/security.yml diff --git a/.bestpractices.json b/.bestpractices.json index 0488cc60..ec579138 100644 --- a/.bestpractices.json +++ b/.bestpractices.json @@ -1,7 +1,7 @@ { "$schema": "https://bestpractices.coreinfrastructure.org/projects.schema.json", - "_comment": "OpenSSF Best Practices self-assessment skeleton for RandomCodeSpace/codeiq. The numeric project_id and badge URL are populated by a board admin after registering the project at https://www.bestpractices.dev/ — RAN-46 AC #8 calls this out as auth-blocked. Once the registration is complete, fill `project_id` and re-render the README badge with the resolved URL.", - "project_id": null, + "_comment": "OpenSSF Best Practices self-assessment for RandomCodeSpace/codeiq. Project page: https://www.bestpractices.dev/en/projects/12650. RAN-46 AC #8 (registration) was unblocked by the board between RAN-46 and RAN-52 — project_id is now wired and the README badge points at the live project URL. Flipping `badge_level` from `in_progress` to `passing` happens in the bestpractices.dev admin UI (still board-owned).", + "project_id": 12650, "name": "codeiq", "description": "Deterministic code knowledge graph — scans codebases to map services, endpoints, entities, infrastructure, auth patterns, and framework usage. No AI, pure static analysis.", "homepage_url": "https://github.com/RandomCodeSpace/codeiq", @@ -34,8 +34,9 @@ "vulnerability_scanning": "OWASP Dependency-Check (mvn dependency-check:check) + Dependabot security updates" }, "audit": { - "self_assessment_date": "2026-04-25", - "self_assessment_author": "TechLead (RAN-46)", - "registration_blocker": "https://www.bestpractices.dev/ requires human OAuth/form. Tracked under RAN-46 AC #8." + "self_assessment_date": "2026-04-26", + "self_assessment_author": "TechLead (RAN-46, RAN-52)", + "registration_blocker": null, + "passing_blocker": "Final flip from `in_progress` to `passing` happens in the bestpractices.dev admin UI; tracked under RAN-52 AC #1 + AC #2." } } diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 00000000..7c73c358 --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,330 @@ +# Consolidated OSS-CLI security stack (RAN-52 AC #4). +# +# Mirrors the (B) OSS-CLI security stack referenced in +# `shared/runbooks/engineering-standards.md` §5: Semgrep (SAST), OSV-Scanner +# (deps), Trivy (filesystem CVEs + misconfig), Gitleaks (secret scan), jscpd +# (copy-paste detection), and anchore/sbom-action (SBOM generation). +# +# Each tool publishes its findings as SARIF to GitHub code scanning where +# supported and uploads the raw report as a workflow artifact regardless, +# so the Security tab plus the artifact tarball are both first-class +# observability surfaces. SARIF upload jobs use `continue-on-error: true` +# during the OSS-CLI bootstrap window because a fresh repo still has the +# default-setup CodeQL bound to some categories — once a category is taken +# by default setup GitHub rejects the workflow upload (same trap that bit +# `codeql.yml` in PR #74). Findings still appear in the workflow logs and +# artifacts, and we promote them to gate-blocking once category collisions +# are settled (tracked under RAN-52 follow-ups). +# +# Cron: Mondays 06:00 UTC, same window as `scorecard.yml` so the weekly +# observability sweep runs together. All third-party actions and Docker +# images are pinned by commit SHA / digest-equivalent tag, in line with +# OpenSSF Scorecard `Pinned-Dependencies`. +name: Security supply-chain scan + +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + - cron: "0 6 * * 1" + workflow_dispatch: + +# Restrict default GITHUB_TOKEN to read-only; jobs opt into narrower writes. +permissions: read-all + +jobs: + # ------------------------------------------------------------------ + # Semgrep — SAST. Runs the official `semgrep` PyPI distribution rather + # than the deprecated `semgrep/semgrep-action` (last release 2023). The + # `p/default` registry pack covers Java, TypeScript, JS, YAML, Dockerfile, + # GHA, and a `security-audit` ruleset, which matches codeiq's tree. + # ------------------------------------------------------------------ + semgrep: + name: Semgrep SAST + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + steps: + - name: Harden runner egress + # step-security/harden-runner v2.19.0 + uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 + with: + egress-policy: audit + + - name: Checkout code + # actions/checkout v6.0.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + persist-credentials: false + + - name: Set up Python + # actions/setup-python v5 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + with: + python-version: "3.12" + + - name: Install Semgrep + run: python -m pip install --no-cache-dir 'semgrep==1.140.0' + + - name: Run Semgrep + # `--error` would fail the job; we report-only at bootstrap and + # gate later. SARIF goes to code scanning, JSON to artifact. + run: | + semgrep scan \ + --config=p/default \ + --config=p/security-audit \ + --sarif --output=semgrep.sarif \ + --metrics=off || true + + - name: Upload Semgrep SARIF (artifact) + # actions/upload-artifact v7.0.1 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a + if: always() + with: + name: semgrep-sarif + path: semgrep.sarif + retention-days: 7 + + - name: Upload Semgrep SARIF to GitHub code-scanning + # github/codeql-action/upload-sarif v3.35.2 + uses: github/codeql-action/upload-sarif@ce64ddcb0d8d890d2df4a9d1c04ff297367dea2a + if: always() + continue-on-error: true + with: + sarif_file: semgrep.sarif + category: semgrep + + # ------------------------------------------------------------------ + # OSV-Scanner — second-source CVE feed (cross-checks OWASP + # Dependency-Check from `ci-java.yml`). Fulfils the previously + # planned RAN-42 osv-scanner.yml; engineering-standards.md §5 row + # is updated in this same PR. + # ------------------------------------------------------------------ + osv-scanner: + name: OSV-Scanner deps + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + steps: + - name: Harden runner egress + uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 + with: + egress-policy: audit + + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + persist-credentials: false + + - name: Run OSV-Scanner + # google/osv-scanner-action v2.3.5 + uses: google/osv-scanner-action@c51854704019a247608d928f370c98740469d4b5 + with: + scan-args: |- + --recursive + --skip-git + --format=sarif + --output=osv.sarif + ./ + continue-on-error: true + + - name: Upload OSV SARIF (artifact) + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a + if: always() + with: + name: osv-sarif + path: osv.sarif + retention-days: 7 + + - name: Upload OSV SARIF to GitHub code-scanning + uses: github/codeql-action/upload-sarif@ce64ddcb0d8d890d2df4a9d1c04ff297367dea2a + if: always() + continue-on-error: true + with: + sarif_file: osv.sarif + category: osv-scanner + + # ------------------------------------------------------------------ + # Trivy — filesystem CVE + IaC misconfig scan. We scan `.` rather + # than a container image because codeiq does not ship images yet + # (see CLAUDE.md "Deploy" section). + # ------------------------------------------------------------------ + trivy-fs: + name: Trivy filesystem + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + steps: + - name: Harden runner egress + uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 + with: + egress-policy: audit + + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + persist-credentials: false + + - name: Run Trivy filesystem scan + # aquasecurity/trivy-action v0.36.0 + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 + with: + scan-type: fs + scan-ref: . + format: sarif + output: trivy.sarif + ignore-unfixed: true + severity: CRITICAL,HIGH + exit-code: "0" + env: + # Mirror the public DB; harden-runner audits egress. + TRIVY_DISABLE_VEX_NOTICE: "true" + + - name: Upload Trivy SARIF (artifact) + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a + if: always() + with: + name: trivy-sarif + path: trivy.sarif + retention-days: 7 + + - name: Upload Trivy SARIF to GitHub code-scanning + uses: github/codeql-action/upload-sarif@ce64ddcb0d8d890d2df4a9d1c04ff297367dea2a + if: always() + continue-on-error: true + with: + sarif_file: trivy.sarif + category: trivy-fs + + # ------------------------------------------------------------------ + # Gitleaks — secret scan. Run via the upstream Docker image + # rather than the GHA wrapper because gitleaks-action requires a + # GITLEAKS_LICENSE for organization-owned repos (RandomCodeSpace + # is an org). Image is pinned by tag; image digest pinning is a + # follow-up once Scorecard `Pinned-Dependencies` flags it. + # ------------------------------------------------------------------ + gitleaks: + name: Gitleaks secret scan + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Harden runner egress + uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 + with: + egress-policy: audit + + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + # Need full history so gitleaks can scan every commit. + fetch-depth: 0 + persist-credentials: false + + - name: Run Gitleaks (Docker image) + run: | + docker run --rm \ + -v "${{ github.workspace }}":/repo \ + -w /repo \ + zricethezav/gitleaks:v8.21.2 \ + detect \ + --source=/repo \ + --report-format=sarif \ + --report-path=/repo/gitleaks.sarif \ + --redact \ + --no-banner || EXIT=$? + # Exit 1 = leaks found; 2 = error. Surface the result without + # gating CI yet — promote to gate once we have a clean baseline. + echo "gitleaks-exit=${EXIT:-0}" >> "$GITHUB_OUTPUT" + id: gitleaks-run + + - name: Upload Gitleaks SARIF (artifact) + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a + if: always() + with: + name: gitleaks-sarif + path: gitleaks.sarif + retention-days: 7 + + # ------------------------------------------------------------------ + # jscpd — copy-paste detection across the polyglot tree. Reports + # are uploaded as artifacts; jscpd does not emit SARIF natively + # so we surface markdown + json in artifacts only. + # ------------------------------------------------------------------ + jscpd: + name: jscpd duplication + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Harden runner egress + uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 + with: + egress-policy: audit + + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + persist-credentials: false + + - name: Set up Node + # actions/setup-node v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 + with: + node-version: "20" + + - name: Run jscpd + run: | + npx --yes jscpd@4.0.5 \ + --silent \ + --reporters json,markdown \ + --output reports/jscpd \ + --ignore '**/node_modules/**,**/target/**,**/dist/**,**/*.min.*,src/main/java/io/github/randomcodespace/iq/grammar/**,src/main/antlr4/imported/**' \ + . || true + + - name: Upload jscpd report (artifact) + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a + if: always() + with: + name: jscpd-report + path: reports/jscpd + retention-days: 7 + + # ------------------------------------------------------------------ + # SBOM — Syft via anchore/sbom-action. Generates SPDX JSON for + # the source tree and attaches it to the run; sbom-action also + # publishes it to the dependency-graph endpoint when run on + # push to default branch. + # ------------------------------------------------------------------ + sbom: + name: Generate SBOM (Syft) + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Harden runner egress + uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 + with: + egress-policy: audit + + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + persist-credentials: false + + - name: Generate SBOM + # anchore/sbom-action v0.24.0 + uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 + with: + path: . + format: spdx-json + artifact-name: codeiq.spdx.json + # `dependency-snapshot: true` would push to the GitHub + # Dependency Submission API; we keep it false for PRs and + # let the scheduled run on `main` populate the graph. + dependency-snapshot: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} diff --git a/CLAUDE.md b/CLAUDE.md index fb423120..9d720df3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -429,6 +429,27 @@ bean for code paths that haven't been ported yet. - **SonarCloud project key**: `RandomCodeSpace_codeiq`, org: `randomcodespace` - **CI workflow**: Single `ci-java.yml` runs build + SonarCloud analysis. No cross-platform builds needed (JVM). +## Supply-chain observability (OpenSSF) + +codeiq publishes two OpenSSF signals: the **Best Practices** badge (a self-attested checklist) and the **Scorecard** score (an automated supply-chain audit). + +### Best Practices badge + +- Project: https://www.bestpractices.dev/en/projects/12650 — registration unblocked between RAN-46 and RAN-52. +- Source-of-truth manifest: `.bestpractices.json` at repo root (project_id, evidence map, audit dates). +- Hard gate per the board: badge level **`passing`**. Final flip from `in_progress` → `passing` happens in the bestpractices.dev admin UI (board-owned). The repo-side criteria (CHANGELOG, SECURITY.md, signed commits, CI, code scanning, vulnerability scanning, SBOM, Scorecard wiring, dependency updates) all already point to evidence in this repo. + +### Scorecard baseline + target + +- Workflow: [`.github/workflows/scorecard.yml`](.github/workflows/scorecard.yml) — push to `main`, weekly cron (Mondays 06:00 UTC), `workflow_dispatch`. SARIF goes to the GitHub Security tab; results also land on https://api.securityscorecards.dev/projects/github.com/RandomCodeSpace/codeiq. +- **Baseline (RAN-52 close, 2026-04-26):** captured by the first scheduled run after this PR lands. Read live from the Scorecard project page above; no static checked-in score (it would rot). +- **Target:** ≥ **8.0 / 10**, with these checks at max: `Pinned-Dependencies`, `Token-Permissions`, `Branch-Protection`, `Code-Review`, `Maintained`, `License`, `SAST`, `Vulnerabilities`. **Stretch only** — Scorecard is observational; the `passing` Best Practices badge is the only hard gate per the board. +- **Known floor reductions:** `Webhooks` (no public webhook surface — N/A), `Signed-Releases` (release-java workflow signs the GA commit; we are not yet signing every release artifact via Sigstore — tracked under follow-up). + +### OSS-CLI security stack + +The (B) OSS-CLI stack runs in [`.github/workflows/security.yml`](.github/workflows/security.yml): **Semgrep** (SAST), **OSV-Scanner** (deps, second-source CVE feed cross-checking OWASP Dependency-Check), **Trivy** (filesystem CVEs + IaC misconfig), **Gitleaks** (secret scan, Docker-image), **jscpd** (copy-paste detection), and **anchore/sbom-action** (SPDX SBOM). Triggers: push to `main`, PR, weekly cron, `workflow_dispatch`. SARIF outputs are uploaded to GitHub code scanning where supported, and every job uploads its raw report as a workflow artifact regardless. Findings are observability-only at the OSS-CLI bootstrap window — promote to gate-blocking once a clean baseline exists. + ## Deploy codeiq's deploy surface is **Maven Central + GitHub Releases** (per RAN-46 AC #10 ruling, option a). The single Java JAR (with the React UI bundled inside) is published via two `workflow_dispatch`-only workflows: `.github/workflows/beta-java.yml` (manual beta cut → Sonatype Central beta + GitHub pre-release) and `.github/workflows/release-java.yml` (manual GA cut with a `version` input → the workflow builds a GPG-signed release commit on a detached HEAD, deploys from that exact tree, then creates and pushes a GPG-signed annotated `vX.Y.Z` tag pointing at the release commit + a GitHub Release). There is no static-CDN frontend, no hosted backend, no VPS — codeiq runs on the developer's machine. See [`shared/runbooks/release.md`](shared/runbooks/release.md) and [`shared/runbooks/engineering-standards.md`](shared/runbooks/engineering-standards.md) §7.1. diff --git a/README.md b/README.md index 35da5f30..1d45cd0f 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Security Reliability OpenSSF Scorecard - OpenSSF Best Practices (pending registration — RAN-46 AC #8) + OpenSSF Best Practices 97 Detectors 35+ Languages

diff --git a/shared/runbooks/engineering-standards.md b/shared/runbooks/engineering-standards.md index d5c64d0c..7cd9d948 100644 --- a/shared/runbooks/engineering-standards.md +++ b/shared/runbooks/engineering-standards.md @@ -20,7 +20,7 @@ The rule of last resort: **`/home/dev/.claude/rules/*.md` wins.** This file does Coverage exclusions are enumerated in `pom.xml` `` config — only generated ANTLR sources, the `application/` Spring Boot main, and pure data records are excluded. Adding to that list requires TechLead sign-off. -**Planned, not yet enforced:** OSV-Scanner as a second-source CVE feed (cross-checks OWASP Dependency-Check against the OSV / GitHub Advisory Database). Tracked under [RAN-42](/RAN/issues/RAN-42); will land as `.github/workflows/osv-scanner.yml` and a row added to the table above. Until then OSV is **not** part of the gate — only OWASP Dependency-Check is. +**Observability stack (RAN-52, not part of the merge gate):** the (B) OSS-CLI stack runs in [`.github/workflows/security.yml`](../../.github/workflows/security.yml) — Semgrep (SAST), OSV-Scanner (deps, second-source CVE feed cross-checking OWASP Dependency-Check against the OSV / GitHub Advisory Database), Trivy (filesystem CVEs + IaC misconfig), Gitleaks (secret scan), jscpd (copy-paste detection), and `anchore/sbom-action` (SPDX SBOM). Triggers: push to `main`, PR, weekly cron, `workflow_dispatch`. SARIF outputs are uploaded to GitHub code scanning where supported and to workflow artifacts regardless. Findings stay observability-only at the OSS-CLI bootstrap window — promotion to gate-blocking happens once a clean baseline exists. The previously planned standalone `osv-scanner.yml` ([RAN-42](/RAN/issues/RAN-42)) is satisfied by this consolidated workflow. --- @@ -73,6 +73,7 @@ Ground rules: - **Secrets** — never in code, config, or commit history. CI secrets are repo-level; rotation cadence is annual or on suspected exposure. - **CVE policy** — High/Critical → block; Medium → fix if a patched version exists, else document non-exploitability with TechLead sign-off; Low → tracked in the next dependency bump cycle. - **Vulnerability reporting** — see [`/SECURITY.md`](../../SECURITY.md). Private disclosure only. +- **OSS-CLI observability stack** — [`.github/workflows/security.yml`](../../.github/workflows/security.yml) runs Semgrep / OSV-Scanner / Trivy / Gitleaks / jscpd / `anchore/sbom-action` weekly + on every PR; OpenSSF Scorecard runs in [`.github/workflows/scorecard.yml`](../../.github/workflows/scorecard.yml). See `/CLAUDE.md` "Supply-chain observability (OpenSSF)" for baseline + targets. ---