Skip to content

Security (OSS-CLI) #228

Security (OSS-CLI)

Security (OSS-CLI) #228

Workflow file for this run

name: Security (OSS-CLI)
# OSS-CLI security stack per RAN-46 AC §3 (board ruling, comment fa5ba510).
# Replaces Sonar + CodeQL + OWASP Dependency-Check.
#
# Six independent jobs — fail-fast off so all signals surface on a single run.
# All actions SHA-pinned per Scorecard `Pinned-Dependencies`. Top-level
# `permissions: read-all` per Scorecard `Token-Permissions`; jobs scope up
# only when needed (gitleaks needs full git history; sbom job uploads).
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '21 4 * * 1' # Mondays 04:21 UTC — catch newly-disclosed CVEs
permissions: read-all
jobs:
osv-scanner:
name: OSV-Scanner (SCA)
runs-on: ubuntu-latest
permissions:
contents: read
env:
OSV_SCANNER_VERSION: 2.3.5
GH_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2
# Install osv-scanner from the official GitHub release (binary, not the
# action — google/osv-scanner-action's `action.yml` is composite-only and
# fails when invoked as a job step). Using the preinstalled `gh` CLI
# avoids any external `curl`/`wget` per /home/dev/.claude/CLAUDE.md.
- name: Install osv-scanner
# `gh release download --output` is honoured only when downloading a single asset
# via `--archive` or by exact name; with `--pattern` the asset is written to the
# current dir at its source name. Download then move to a stable name.
run: |
gh release download "v${OSV_SCANNER_VERSION}" \
--repo google/osv-scanner \
--pattern 'osv-scanner_linux_amd64' \
--clobber
mv osv-scanner_linux_amd64 osv-scanner
chmod +x osv-scanner
./osv-scanner --version
- name: Run osv-scanner (Go module graph)
# Phase 6 cutover: project is Go-only. osv-scanner reads go.mod
# (the entire module graph including transitive deps) and emits
# any matching advisories from OSV.dev / GHSA. govulncheck (in
# go-ci.yml) is the call-graph-aware companion that filters to
# *reachable* vulns — keeping both gives both "have we got it"
# AND "are we exposed".
run: ./osv-scanner --lockfile=go.mod
trivy:
name: Trivy (filesystem + container scan)
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2
- uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
scan-type: fs
scan-ref: .
severity: HIGH,CRITICAL
exit-code: '1'
ignore-unfixed: true
semgrep:
name: Semgrep (SAST)
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.12'
- name: Install semgrep (pinned for reproducibility)
# Pinned per OpenSSF Scorecard `Pinned-Dependencies` (RAN-46 §5).
# Bump via Dependabot pip ecosystem on a documented cadence; floating
# `semgrep` was previously flagged by Scorecard. pip is left unpinned
# — setup-python@v6 ships a current vendored pip, and the Scorecard
# rule fires only on user-installed packages.
run: python -m pip install --quiet 'semgrep==1.161.0'
- name: Run semgrep (security-audit + owasp-top-ten + golang)
# Phase 6 cutover: project is Go-only. p/java replaced with
# p/golang. p/security-audit + p/owasp-top-ten stay (both
# language-agnostic rule sets).
run: |
semgrep scan \
--error \
--config p/security-audit \
--config p/owasp-top-ten \
--config p/golang \
--severity ERROR \
--metrics off
gitleaks:
name: Gitleaks (secret scan)
runs-on: ubuntu-latest
permissions:
contents: read
env:
GITLEAKS_VERSION: 8.30.1
GH_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2
with:
fetch-depth: 0
# The official `gitleaks/gitleaks-action` requires a paid license for
# GitHub organisations. The underlying gitleaks CLI is MIT-licensed and
# free; install it directly from the upstream release. Using the
# preinstalled `gh` CLI avoids any external `curl`/`wget`.
- name: Install gitleaks
run: |
gh release download "v${GITLEAKS_VERSION}" \
--repo gitleaks/gitleaks \
--pattern "gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
--output gitleaks.tar.gz
tar -xzf gitleaks.tar.gz gitleaks
chmod +x gitleaks
- name: Run gitleaks (full git history)
run: ./gitleaks detect --source . --redact --no-banner --exit-code 1
jscpd:
name: jscpd (duplication < 3% on touched code)
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '20'
- run: |
# Scope jscpd to Go production code only:
# - cmd — main entry point
# - internal — production code (100 detectors + pipeline + MCP)
# Tests share fixture/assertion shape by design (parallelism for
# catching contract regressions, not a refactoring target).
#
# `*language_extractor.go` (one per language under
# intelligence/extractor/{java,typescript,python,golang}) and
# `structures.go` (kotlin/scala/cpp/rust) implement the same
# template-method shape against per-language ASTs by design;
# collapsing into a base would couple unrelated grammars and
# erase per-language readability. Excluded.
#
# --min-tokens 200 calibrated to Go's verbosity floor: each
# detector file's package decl + imports + Type/init/Name/
# SupportedLanguages/DefaultConfidence/Detect scaffold is
# ~150-180 tokens of identical structural boilerplate across
# 100 detectors. 200 tokens is the floor for "real duplicate
# logic" rather than language scaffolding.
npx --yes jscpd@4 \
--threshold 3 \
--min-tokens 200 \
--reporters consoleFull \
--format "go" \
--ignore "**/vendor/**,**/testdata/**,**/grammar/**,**/generated/**,**/dist/**,**/coverage/**,**/intelligence/extractor/**/language_extractor.go,**/detector/**/structures.go" \
cmd internal
sbom:
name: SBOM (SPDX + CycloneDX)
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2
- name: Generate SPDX SBOM
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
with:
format: spdx-json
output-file: sbom.spdx.json
upload-artifact: false
- name: Generate CycloneDX SBOM
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
with:
format: cyclonedx-json
output-file: sbom.cdx.json
upload-artifact: false
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v4.6.2
with:
name: sbom
path: |
sbom.spdx.json
sbom.cdx.json
retention-days: 90