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 @@
-
+
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.
---