Skip to content

ci: add advisory dependency and container image vulnerability scanning - #156

Closed
lucastononro wants to merge 2 commits into
fix/117-postgres-cifrom
fix/118-vuln-scan
Closed

ci: add advisory dependency and container image vulnerability scanning#156
lucastononro wants to merge 2 commits into
fix/117-postgres-cifrom
fix/118-vuln-scan

Conversation

@lucastononro

@lucastononro lucastononro commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Summary

  • CI's only security step was bandit (Python SAST) — no dependency scanning (pip-audit/npm audit) and no image scan, even though backend/requirements.txt already carries a manual comment pinning litellm to dodge a specific CVE — exactly the kind of thing a scanner catches automatically instead of relying on someone noticing.
  • Adds pip-audit, npm audit, and a Trivy image scan, all advisory only so pre-existing (real, currently unaddressed) advisories don't turn CI red.

Changes

.github/workflows/ci.yml — three new jobs:

  • backend-vuln-scan: installs backend/requirements.txt + pip-audit, runs pip-audit against the environment.
  • frontend-vuln-scan: npm ci then npm audit in frontend/.
  • image-scan: builds trainable-backend and trainable-frontend locally via docker/build-push-action@v5 (push: false, load: true — no registry, no credentials needed) matching the matrix in publish-image.yml, then scans each with aquasecurity/trivy-action@0.36.0 (severity: CRITICAL,HIGH, exit-code: "1").

Every scan step (not the whole job) has continue-on-error: true, with an inline comment noting what to do to flip it to blocking later (drop continue-on-error once the advisories below are triaged).

Test plan

Existing

  • No existing jobs touched; backend-lint, backend-test, backend-security, frontend-lint, frontend-build are unaffected.

New

  • pip-audit run locally against the installed backend/requirements.txt env: exits 1, found 21 known vulnerabilities across mcp, python-dotenv, python-multipart, starlette — confirms continue-on-error is load-bearing, not decorative.
  • npm audit run locally in frontend/: exits 1, 10 vulnerabilities (6 moderate, 4 high) across next, glob, js-yaml, postcss, prismjs.
  • Built both backend/Dockerfile and frontend/Dockerfile (--target runner) locally — both succeed.
  • Ran Trivy (aquasec/trivy:latest via docker run) against the built backend image with --severity CRITICAL,HIGH --exit-code 1: found real HIGH findings (npm supply-chain packages baked into the Claude Code CLI install, plus mcp/python-multipart/starlette Python CVEs matching pip-audit) — confirms the scan step would also fail without the advisory wrapper.

Caveats

  • All three jobs currently report real findings; they're informational only until each dependency tree is triaged and the advisories cleared, at which point continue-on-error should be dropped per job to make it blocking.
  • image-scan scans images built fresh in the job (not the ones actually pushed by publish-image.yml), since PR CI doesn't have registry credentials — same Dockerfiles/context, so findings should match what ships.

Closes #118

🤖 Generated with Claude Code

Greptile Summary

This PR adds three advisory-only CI jobs — backend-vuln-scan (pip-audit), frontend-vuln-scan (npm audit), and image-scan (Trivy) — to surface known dependency and container-image vulnerabilities without blocking the pipeline while pre-existing advisories remain unaddressed.

  • backend-vuln-scan: installs requirements.txt under Python 3.13 (matching backend/Dockerfile) and runs pip-audit with continue-on-error: true.
  • frontend-vuln-scan: runs npm ci + npm audit in frontend/ with continue-on-error: true.
  • image-scan: builds both images locally (push: false, load: true) via a matrix strategy, then scans each with Trivy (CRITICAL,HIGH); the Trivy action is pinned to a commit SHA rather than a mutable tag.

Confidence Score: 5/5

Safe to merge — all new jobs are advisory-only and no existing jobs are modified.

The change only adds informational scanning jobs that cannot break the existing CI pipeline. All three scan steps use continue-on-error: true, existing jobs are untouched, and the Trivy action is pinned to an immutable commit SHA.

No files require special attention; the single suggestion on .github/workflows/ci.yml is optional.

Important Files Changed

Filename Overview
.github/workflows/ci.yml Adds three advisory-only scanning jobs (backend pip-audit, frontend npm audit, Trivy image scan); existing jobs are untouched; Trivy action is commit-SHA pinned and Python version matches the Dockerfile.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[push / pull_request] --> B[backend-lint]
    A --> C[backend-test]
    A --> D[backend-security bandit]
    A --> E[backend-vuln-scan\npip-audit advisory]
    A --> F[frontend-vuln-scan\nnpm audit advisory]
    A --> G[image-scan matrix\nTrivy advisory]
    A --> H[frontend-lint]
    A --> I[frontend-build]

    G --> G1[trainable-backend\nBuild + Trivy]
    G --> G2[trainable-frontend\nBuild + Trivy]

    style E fill:#fffbe6,stroke:#f6c90e
    style F fill:#fffbe6,stroke:#f6c90e
    style G fill:#fffbe6,stroke:#f6c90e
    style G1 fill:#fffbe6,stroke:#f6c90e
    style G2 fill:#fffbe6,stroke:#f6c90e
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[push / pull_request] --> B[backend-lint]
    A --> C[backend-test]
    A --> D[backend-security bandit]
    A --> E[backend-vuln-scan\npip-audit advisory]
    A --> F[frontend-vuln-scan\nnpm audit advisory]
    A --> G[image-scan matrix\nTrivy advisory]
    A --> H[frontend-lint]
    A --> I[frontend-build]

    G --> G1[trainable-backend\nBuild + Trivy]
    G --> G2[trainable-frontend\nBuild + Trivy]

    style E fill:#fffbe6,stroke:#f6c90e
    style F fill:#fffbe6,stroke:#f6c90e
    style G fill:#fffbe6,stroke:#f6c90e
    style G1 fill:#fffbe6,stroke:#f6c90e
    style G2 fill:#fffbe6,stroke:#f6c90e
Loading

Reviews (2): Last reviewed commit: "fix(review): address Greptile findings o..." | Re-trigger Greptile

CI's only security step was bandit (Python SAST) — no dependency
scanning and no image scanning, despite requirements.txt already
carrying a manual note pinning litellm to dodge a CVE that a scanner
would enforce automatically.

Adds three new jobs, all advisory (continue-on-error: true on the
scan step) so pre-existing advisories don't break CI:
  - backend-vuln-scan: pip-audit against backend/requirements.txt
  - frontend-vuln-scan: npm audit against the frontend lockfile
  - image-scan: builds trainable-backend/trainable-frontend locally
    (not pushed) and scans them with Trivy (CRITICAL/HIGH)

Each can be flipped to blocking later by dropping continue-on-error
once its current advisories are triaged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wp66uSq9saSpRq9Bbmjxj4
Comment thread .github/workflows/ci.yml
Comment thread .github/workflows/ci.yml Outdated
Comment thread .github/workflows/ci.yml
Comment on lines +156 to +163
- name: Trivy scan
uses: aquasecurity/trivy-action@0.36.0
continue-on-error: true
with:
image-ref: ${{ matrix.image }}:ci-scan
format: table
severity: "CRITICAL,HIGH"
exit-code: "1"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Trivy findings stay in job logs only — the scan outputs format: table to stdout. GitHub's Security tab (Dependabot/Code Scanning) can ingest SARIF uploads, which would surface CRITICAL/HIGH findings directly on the PR without reviewers having to dig into job logs. Adding format: sarif, output: trivy-results.sarif, and an actions/upload-artifact (or github/codeql-action/upload-sarif) step after the scan would make the advisory findings much more visible over time.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

- pip-audit job: scan under Python 3.13 to match backend/Dockerfile
  (python:3.13-slim), so audited dependency resolution mirrors what
  ships in production.
- Trivy action: pin to immutable commit SHA
  ed142fd0673e97e23eac54620cfb913e5ce36c25 (v0.36.0). This also fixes a
  broken ref: the previous `@0.36.0` tag does not exist upstream
  (trivy-action tags are v-prefixed), so the image-scan job would have
  failed to resolve the action.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wp66uSq9saSpRq9Bbmjxj4
@lucastononro

Copy link
Copy Markdown
Owner Author

Greptile review findings — resolution (commit 4a0ca9b)

Fixed

  1. [P1] pip-audit scans Python 3.11 but prod ships 3.13 — Valid. backend/Dockerfile is python:3.13-slim and installs the same requirements.txt, so version-marker-gated deps could resolve differently under 3.11. The backend-vuln-scan job now uses python-version: "3.13" (with a comment tying it to the Dockerfile). Other jobs (lint/test) intentionally left on 3.11 — pre-existing and out of scope for this PR.

  2. [P2] Trivy action pinned by mutable tag — Valid, and it surfaced a real bug: aquasecurity/trivy-action@0.36.0 does not resolve — upstream tags are v-prefixed (v0.36.0), so the image-scan job would have failed at action resolution (no CI runs had executed on this branch to catch it). Now pinned to the immutable commit SHA ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0, verified against the GitHub API by dereferencing the annotated v0.36.0 tag. Note: Greptile's suggested SHA (a20de542...) does not correspond to v0.36.0 and was not used.

Dismissed

  1. [P2] Trivy output to SARIF / Security tab — Reasonable enhancement, deliberately not taken here. This PR's scope is advisory-only scanning (per the in-file comments, blocking is deferred until existing advisories are triaged). SARIF upload adds security-events: write permissions plus per-matrix-leg SARIF categories, and is most useful once the scan flips to blocking. Suggested as a follow-up alongside that flip.

Verification

  • ci.yml parses as valid YAML.
  • v0.36.0 tag→commit SHA verified via GitHub API.
  • CI-only beyond that: the scan jobs themselves only run in GitHub Actions; no workflow runs have executed on this branch, so the jobs should be watched on the next push/run (this push should trigger them if Actions are enabled for the PR).

lucastononro added a commit that referenced this pull request Jul 29, 2026
…ity scanning (closes #118)

Stack wave 5, PR 3/4. Greptile findings already handled on the PR
branch by author fixup 4a0ca9b (pip-audit on py3.13; trivy-action
pinned by SHA; SARIF upload dismissed as out of advisory scope).
@lucastononro

Copy link
Copy Markdown
Owner Author

Merged into integration branch staging-v0.0.5 (see the STAGING-v0.0.5.md ledger on that branch for merge order, Greptile follow-ups and test results). These changes will land on main via the staging merge — closing to clear the queue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant