Skip to content

Commit 50e28f4

Browse files
aksOpsclaudePaperclip-Paperclip
committed
chore(security): land OSS-CLI stack + wire OpenSSF Best Practices project (RAN-52)
- 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) <noreply@anthropic.com> Co-Authored-By: Paperclip <noreply@paperclip.ing>
1 parent 6c3b9e9 commit 50e28f4

5 files changed

Lines changed: 360 additions & 7 deletions

File tree

.bestpractices.json

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://bestpractices.coreinfrastructure.org/projects.schema.json",
3-
"_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.",
4-
"project_id": null,
3+
"_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).",
4+
"project_id": 12650,
55
"name": "codeiq",
66
"description": "Deterministic code knowledge graph — scans codebases to map services, endpoints, entities, infrastructure, auth patterns, and framework usage. No AI, pure static analysis.",
77
"homepage_url": "https://github.com/RandomCodeSpace/codeiq",
@@ -34,8 +34,9 @@
3434
"vulnerability_scanning": "OWASP Dependency-Check (mvn dependency-check:check) + Dependabot security updates"
3535
},
3636
"audit": {
37-
"self_assessment_date": "2026-04-25",
38-
"self_assessment_author": "TechLead (RAN-46)",
39-
"registration_blocker": "https://www.bestpractices.dev/ requires human OAuth/form. Tracked under RAN-46 AC #8."
37+
"self_assessment_date": "2026-04-26",
38+
"self_assessment_author": "TechLead (RAN-46, RAN-52)",
39+
"registration_blocker": null,
40+
"passing_blocker": "Final flip from `in_progress` to `passing` happens in the bestpractices.dev admin UI; tracked under RAN-52 AC #1 + AC #2."
4041
}
4142
}

.github/workflows/security.yml

Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
# Consolidated OSS-CLI security stack (RAN-52 AC #4).
2+
#
3+
# Mirrors the (B) OSS-CLI security stack referenced in
4+
# `shared/runbooks/engineering-standards.md` §5: Semgrep (SAST), OSV-Scanner
5+
# (deps), Trivy (filesystem CVEs + misconfig), Gitleaks (secret scan), jscpd
6+
# (copy-paste detection), and anchore/sbom-action (SBOM generation).
7+
#
8+
# Each tool publishes its findings as SARIF to GitHub code scanning where
9+
# supported and uploads the raw report as a workflow artifact regardless,
10+
# so the Security tab plus the artifact tarball are both first-class
11+
# observability surfaces. SARIF upload jobs use `continue-on-error: true`
12+
# during the OSS-CLI bootstrap window because a fresh repo still has the
13+
# default-setup CodeQL bound to some categories — once a category is taken
14+
# by default setup GitHub rejects the workflow upload (same trap that bit
15+
# `codeql.yml` in PR #74). Findings still appear in the workflow logs and
16+
# artifacts, and we promote them to gate-blocking once category collisions
17+
# are settled (tracked under RAN-52 follow-ups).
18+
#
19+
# Cron: Mondays 06:00 UTC, same window as `scorecard.yml` so the weekly
20+
# observability sweep runs together. All third-party actions and Docker
21+
# images are pinned by commit SHA / digest-equivalent tag, in line with
22+
# OpenSSF Scorecard `Pinned-Dependencies`.
23+
name: Security supply-chain scan
24+
25+
on:
26+
push:
27+
branches: [main]
28+
pull_request:
29+
branches: [main]
30+
schedule:
31+
- cron: "0 6 * * 1"
32+
workflow_dispatch:
33+
34+
# Restrict default GITHUB_TOKEN to read-only; jobs opt into narrower writes.
35+
permissions: read-all
36+
37+
jobs:
38+
# ------------------------------------------------------------------
39+
# Semgrep — SAST. Runs the official `semgrep` PyPI distribution rather
40+
# than the deprecated `semgrep/semgrep-action` (last release 2023). The
41+
# `p/default` registry pack covers Java, TypeScript, JS, YAML, Dockerfile,
42+
# GHA, and a `security-audit` ruleset, which matches codeiq's tree.
43+
# ------------------------------------------------------------------
44+
semgrep:
45+
name: Semgrep SAST
46+
runs-on: ubuntu-latest
47+
permissions:
48+
contents: read
49+
security-events: write
50+
steps:
51+
- name: Harden runner egress
52+
# step-security/harden-runner v2.19.0
53+
uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40
54+
with:
55+
egress-policy: audit
56+
57+
- name: Checkout code
58+
# actions/checkout v6.0.2
59+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
60+
with:
61+
persist-credentials: false
62+
63+
- name: Set up Python
64+
# actions/setup-python v5
65+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
66+
with:
67+
python-version: "3.12"
68+
69+
- name: Install Semgrep
70+
run: python -m pip install --no-cache-dir 'semgrep==1.140.0'
71+
72+
- name: Run Semgrep
73+
# `--error` would fail the job; we report-only at bootstrap and
74+
# gate later. SARIF goes to code scanning, JSON to artifact.
75+
run: |
76+
semgrep scan \
77+
--config=p/default \
78+
--config=p/security-audit \
79+
--sarif --output=semgrep.sarif \
80+
--metrics=off || true
81+
82+
- name: Upload Semgrep SARIF (artifact)
83+
# actions/upload-artifact v7.0.1
84+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
85+
if: always()
86+
with:
87+
name: semgrep-sarif
88+
path: semgrep.sarif
89+
retention-days: 7
90+
91+
- name: Upload Semgrep SARIF to GitHub code-scanning
92+
# github/codeql-action/upload-sarif v3.35.2
93+
uses: github/codeql-action/upload-sarif@ce64ddcb0d8d890d2df4a9d1c04ff297367dea2a
94+
if: always()
95+
continue-on-error: true
96+
with:
97+
sarif_file: semgrep.sarif
98+
category: semgrep
99+
100+
# ------------------------------------------------------------------
101+
# OSV-Scanner — second-source CVE feed (cross-checks OWASP
102+
# Dependency-Check from `ci-java.yml`). Fulfils the previously
103+
# planned RAN-42 osv-scanner.yml; engineering-standards.md §5 row
104+
# is updated in this same PR.
105+
# ------------------------------------------------------------------
106+
osv-scanner:
107+
name: OSV-Scanner deps
108+
runs-on: ubuntu-latest
109+
permissions:
110+
contents: read
111+
security-events: write
112+
steps:
113+
- name: Harden runner egress
114+
uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40
115+
with:
116+
egress-policy: audit
117+
118+
- name: Checkout code
119+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
120+
with:
121+
persist-credentials: false
122+
123+
- name: Run OSV-Scanner
124+
# google/osv-scanner-action v2.3.5
125+
uses: google/osv-scanner-action@c51854704019a247608d928f370c98740469d4b5
126+
with:
127+
scan-args: |-
128+
--recursive
129+
--skip-git
130+
--format=sarif
131+
--output=osv.sarif
132+
./
133+
continue-on-error: true
134+
135+
- name: Upload OSV SARIF (artifact)
136+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
137+
if: always()
138+
with:
139+
name: osv-sarif
140+
path: osv.sarif
141+
retention-days: 7
142+
143+
- name: Upload OSV SARIF to GitHub code-scanning
144+
uses: github/codeql-action/upload-sarif@ce64ddcb0d8d890d2df4a9d1c04ff297367dea2a
145+
if: always()
146+
continue-on-error: true
147+
with:
148+
sarif_file: osv.sarif
149+
category: osv-scanner
150+
151+
# ------------------------------------------------------------------
152+
# Trivy — filesystem CVE + IaC misconfig scan. We scan `.` rather
153+
# than a container image because codeiq does not ship images yet
154+
# (see CLAUDE.md "Deploy" section).
155+
# ------------------------------------------------------------------
156+
trivy-fs:
157+
name: Trivy filesystem
158+
runs-on: ubuntu-latest
159+
permissions:
160+
contents: read
161+
security-events: write
162+
steps:
163+
- name: Harden runner egress
164+
uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40
165+
with:
166+
egress-policy: audit
167+
168+
- name: Checkout code
169+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
170+
with:
171+
persist-credentials: false
172+
173+
- name: Run Trivy filesystem scan
174+
# aquasecurity/trivy-action v0.36.0
175+
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25
176+
with:
177+
scan-type: fs
178+
scan-ref: .
179+
format: sarif
180+
output: trivy.sarif
181+
ignore-unfixed: true
182+
severity: CRITICAL,HIGH
183+
exit-code: "0"
184+
env:
185+
# Mirror the public DB; harden-runner audits egress.
186+
TRIVY_DISABLE_VEX_NOTICE: "true"
187+
188+
- name: Upload Trivy SARIF (artifact)
189+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
190+
if: always()
191+
with:
192+
name: trivy-sarif
193+
path: trivy.sarif
194+
retention-days: 7
195+
196+
- name: Upload Trivy SARIF to GitHub code-scanning
197+
uses: github/codeql-action/upload-sarif@ce64ddcb0d8d890d2df4a9d1c04ff297367dea2a
198+
if: always()
199+
continue-on-error: true
200+
with:
201+
sarif_file: trivy.sarif
202+
category: trivy-fs
203+
204+
# ------------------------------------------------------------------
205+
# Gitleaks — secret scan. Run via the upstream Docker image
206+
# rather than the GHA wrapper because gitleaks-action requires a
207+
# GITLEAKS_LICENSE for organization-owned repos (RandomCodeSpace
208+
# is an org). Image is pinned by tag; image digest pinning is a
209+
# follow-up once Scorecard `Pinned-Dependencies` flags it.
210+
# ------------------------------------------------------------------
211+
gitleaks:
212+
name: Gitleaks secret scan
213+
runs-on: ubuntu-latest
214+
permissions:
215+
contents: read
216+
steps:
217+
- name: Harden runner egress
218+
uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40
219+
with:
220+
egress-policy: audit
221+
222+
- name: Checkout code
223+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
224+
with:
225+
# Need full history so gitleaks can scan every commit.
226+
fetch-depth: 0
227+
persist-credentials: false
228+
229+
- name: Run Gitleaks (Docker image)
230+
run: |
231+
docker run --rm \
232+
-v "${{ github.workspace }}":/repo \
233+
-w /repo \
234+
zricethezav/gitleaks:v8.21.2 \
235+
detect \
236+
--source=/repo \
237+
--report-format=sarif \
238+
--report-path=/repo/gitleaks.sarif \
239+
--redact \
240+
--no-banner || EXIT=$?
241+
# Exit 1 = leaks found; 2 = error. Surface the result without
242+
# gating CI yet — promote to gate once we have a clean baseline.
243+
echo "gitleaks-exit=${EXIT:-0}" >> "$GITHUB_OUTPUT"
244+
id: gitleaks-run
245+
246+
- name: Upload Gitleaks SARIF (artifact)
247+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
248+
if: always()
249+
with:
250+
name: gitleaks-sarif
251+
path: gitleaks.sarif
252+
retention-days: 7
253+
254+
# ------------------------------------------------------------------
255+
# jscpd — copy-paste detection across the polyglot tree. Reports
256+
# are uploaded as artifacts; jscpd does not emit SARIF natively
257+
# so we surface markdown + json in artifacts only.
258+
# ------------------------------------------------------------------
259+
jscpd:
260+
name: jscpd duplication
261+
runs-on: ubuntu-latest
262+
permissions:
263+
contents: read
264+
steps:
265+
- name: Harden runner egress
266+
uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40
267+
with:
268+
egress-policy: audit
269+
270+
- name: Checkout code
271+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
272+
with:
273+
persist-credentials: false
274+
275+
- name: Set up Node
276+
# actions/setup-node v4
277+
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
278+
with:
279+
node-version: "20"
280+
281+
- name: Run jscpd
282+
run: |
283+
npx --yes jscpd@4.0.5 \
284+
--silent \
285+
--reporters json,markdown \
286+
--output reports/jscpd \
287+
--ignore '**/node_modules/**,**/target/**,**/dist/**,**/*.min.*,src/main/java/io/github/randomcodespace/iq/grammar/**,src/main/antlr4/imported/**' \
288+
. || true
289+
290+
- name: Upload jscpd report (artifact)
291+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
292+
if: always()
293+
with:
294+
name: jscpd-report
295+
path: reports/jscpd
296+
retention-days: 7
297+
298+
# ------------------------------------------------------------------
299+
# SBOM — Syft via anchore/sbom-action. Generates SPDX JSON for
300+
# the source tree and attaches it to the run; sbom-action also
301+
# publishes it to the dependency-graph endpoint when run on
302+
# push to default branch.
303+
# ------------------------------------------------------------------
304+
sbom:
305+
name: Generate SBOM (Syft)
306+
runs-on: ubuntu-latest
307+
permissions:
308+
contents: read
309+
steps:
310+
- name: Harden runner egress
311+
uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40
312+
with:
313+
egress-policy: audit
314+
315+
- name: Checkout code
316+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
317+
with:
318+
persist-credentials: false
319+
320+
- name: Generate SBOM
321+
# anchore/sbom-action v0.24.0
322+
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610
323+
with:
324+
path: .
325+
format: spdx-json
326+
artifact-name: codeiq.spdx.json
327+
# `dependency-snapshot: true` would push to the GitHub
328+
# Dependency Submission API; we keep it false for PRs and
329+
# let the scheduled run on `main` populate the graph.
330+
dependency-snapshot: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}

CLAUDE.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,27 @@ bean for code paths that haven't been ported yet.
429429
- **SonarCloud project key**: `RandomCodeSpace_codeiq`, org: `randomcodespace`
430430
- **CI workflow**: Single `ci-java.yml` runs build + SonarCloud analysis. No cross-platform builds needed (JVM).
431431

432+
## Supply-chain observability (OpenSSF)
433+
434+
codeiq publishes two OpenSSF signals: the **Best Practices** badge (a self-attested checklist) and the **Scorecard** score (an automated supply-chain audit).
435+
436+
### Best Practices badge
437+
438+
- Project: https://www.bestpractices.dev/en/projects/12650 — registration unblocked between RAN-46 and RAN-52.
439+
- Source-of-truth manifest: `.bestpractices.json` at repo root (project_id, evidence map, audit dates).
440+
- 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.
441+
442+
### Scorecard baseline + target
443+
444+
- 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.
445+
- **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).
446+
- **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.
447+
- **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).
448+
449+
### OSS-CLI security stack
450+
451+
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.
452+
432453
## Deploy
433454

434455
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.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<a href="https://sonarcloud.io/summary/overall?id=RandomCodeSpace_codeiq"><img src="https://sonarcloud.io/api/project_badges/measure?project=RandomCodeSpace_codeiq&metric=security_rating" alt="Security"></a>
1414
<a href="https://sonarcloud.io/summary/overall?id=RandomCodeSpace_codeiq"><img src="https://sonarcloud.io/api/project_badges/measure?project=RandomCodeSpace_codeiq&metric=reliability_rating" alt="Reliability"></a>
1515
<a href="https://api.securityscorecards.dev/projects/github.com/RandomCodeSpace/codeiq"><img src="https://api.securityscorecards.dev/projects/github.com/RandomCodeSpace/codeiq/badge" alt="OpenSSF Scorecard"></a>
16-
<a href="https://www.bestpractices.dev/projects/codeiq"><img src="https://img.shields.io/badge/OpenSSF%20Best%20Practices-pending%20registration-lightgrey?style=flat-square&logo=openssf&logoColor=white" alt="OpenSSF Best Practices (pending registration — RAN-46 AC #8)"></a>
16+
<a href="https://www.bestpractices.dev/projects/12650"><img src="https://www.bestpractices.dev/projects/12650/badge" alt="OpenSSF Best Practices"></a>
1717
<a href="https://github.com/RandomCodeSpace/codeiq"><img src="https://img.shields.io/badge/detectors-97-brightgreen?style=flat-square&logo=codefactor&logoColor=white" alt="97 Detectors"></a>
1818
<a href="https://github.com/RandomCodeSpace/codeiq"><img src="https://img.shields.io/badge/languages-35%2B-blue?style=flat-square&logo=stackblitz&logoColor=white" alt="35+ Languages"></a>
1919
</p>

0 commit comments

Comments
 (0)