|
| 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' }} |
0 commit comments