Skip to main content
Private preview. fremforge is in private preview — invited customers only. Content is still subject to change. Request access →
Image scanning

Container image scanning

Container image scanning answers the what’s vulnerable in this image question — complementing the SBOM’s what’s in this image. fremforge ingests scan results produced by Trivy (the standard runner-image tool) and surfaces them next to the image + commit they describe.

How it works

The fremforge runner image ships Trivy. Your workflow runs the scan and POSTs the findings:

- name: Scan + push findings
  run: |
    trivy image --format json --output trivy.json my-registry/my-image:${{ github.sha }}
    curl -sS --fail \
      -H "Authorization: Bearer ${{ secrets.FREMFORGE_FINDINGS_TOKEN }}" \
      -H 'Content-Type: application/json' \
      -d @<(jq -c '{
        image_ref: "my-registry/my-image:'"${GITHUB_SHA}"'",
        commit_sha: "'"${GITHUB_SHA}"'",
        repo_full_name: "'"${GITHUB_REPOSITORY}"'",
        findings: [.Results[].Vulnerabilities[] | {
          cve_id: .VulnerabilityID,
          severity: (.Severity|ascii_downcase),
          package_name: .PkgName,
          installed_version: .InstalledVersion,
          fixed_version: .FixedVersion,
          title: .Title,
          target: .PkgPath
        }]
      }' trivy.json) \
      "https://frem.sh/_app/api/v1/orgs/${ORG}/image-scans/findings"

The PAT needs the findings:write scope.

Policy toggles

Admin → Code security → Image scanning carries three settings:

  • Enabled — when off, ingest returns 202 {accepted: 0, status: 'disabled'} (still parsed, just dropped). Useful for pre-launch teams that want the scan to run but not gate.
  • Block actionnone | warn | block_pull:
    • none — store findings, post no commit status. Build-side detection only.
    • warn — store findings, post commit status success with description noting the count. PR merge unblocked.
    • block_pull — store findings, post commit status failure when ≥1 high or critical finding exists. Branch protection with required fremforge/image-scan status check then blocks merge.
  • Require signed images — when on, the ingest also accepts a signature_status field (signed / unsigned / unknown). Unsigned images (or signed images from an issuer not on the allowlist) get a synthetic critical finding so the same block-pull path enforces the signing policy. See Cosign for the signing side.

Where findings surface

Admin → Code security → Image scanning lists scans by image-ref + commit + PR. Each row shows:

  • Severity counts (critical / high / medium / low)
  • Fixable counts (findings with a non-null fixed_version)
  • Status (success / failure / skipped) + the commit status posted
  • Links to the upstream advisories (CVE, GHSA when present)

What a finding looks like in the DB

Stored fields per finding: cve_id, severity, package, installed_version, fixed_version, target (the in-image file path), and the scan_run_id that ties findings produced by the same run. The full Trivy output is NOT stored — just the per-finding rows, which is the queryable shape.

Retention

Findings follow the per-tenant audit retention policy. Aged-out findings keep their hash in the chain (tamper-evidence) but lose the per-row payload.

See also

  • Cosign — image signing + signed-images policy.
  • SBOMs — package inventory in the image (independent of vulnerability state).
  • OSV recommendations — the source-side complement (vulnerabilities in your dependencies before they ship).
  • API referencePOST /api/v1/orgs/{slug}/image-scans/findings ingest endpoint.