APK Secdb Processors (chainguard-json-processor, wolfi-json-processor)

Two daily processors that ingest the Alpine secdb security feeds published by Chainguard and Wolfi:

Both feeds share an identical shape, so the per-binary cmd/ entrypoint is a thin shim around a shared internal/apksecdb/ package.

Feed format

Validated against schemas/apk_secdb_advisory.schema.json.

{
  "apkurl": "{{urlprefix}}/{{reponame}}/{{arch}}/{{pkg.name}}-{{pkg.ver}}.apk",
  "archs": ["x86_64", "aarch64"],
  "reponame": "chainguard",
  "urlprefix": "https://packages.cgr.dev",
  "packages": [
    {"pkg": {"name": "aactl", "secfixes": {
      "0":            ["CVE-2023-45283"],         // unfixed → wildcard
      "0.4.12-r10":   ["CVE-2024-29902", "GHSA-..."]  // fixed in this version
    }}}
  ]
}

Storage model

For every (pkg.name, fixVersion, vulnID) tuple the processor:

  1. Looks up a base CVEMetadata row by cveId = vulnID, ordered by source preference: cve.orgnist-nvdgithubosv → any other. If no row exists yet, a minimal placeholder is created with the natural source for the ID prefix (CVE-* → cve.org, GHSA-* → github).
  2. Upserts a CVEAffected row at (cveId, baseSource, containerType="adp", affectedHash) with adpOrgId = "chainguard" | "wolfi", vendor = product = packageName = pkg.name, collectionURL = https://images.chainguard.dev | https://wolfi.dev, and a single wildcard CPE (cpe:2.3:a:<vendor>:<pkg>:*:*:*:*:*:*:*:*) in the cpes JSON column. The affectedHash includes the adpOrgId so two different ADPs don’t collide on the same CVE+source.
  3. Inserts CVEAffectedVersion rows for every observed fix version:
    • secfixes["0"] (unfixed) → Version="*", Status="affected", VersionType="apk".
    • secfixes["X.Y.Z-rN"] (fix shipped) → Version="0", LessThan="X.Y.Z-rN", Status="affected", VersionType="apk" — i.e. all versions strictly less than the fix are affected, mirroring the OSV/gemnasium convention.
  4. Drives EnrichAffectedWithDependency so PURLs and PackageVersionCVE junctions are populated. Generated PURL: pkg:apk/<reponame>/<pkg> (no @version when wildcard) or pkg:apk/<reponame>/<pkg>@<version> for explicit versions.

“Version 0 is wildcard, not zero”

The Alpine secdb format uses the literal string "0" as the secfixes key for advisories with no shipped fix. Generated CPEs and PURLs render that as * — never as a literal 0. The two helpers in internal/apksecdb/identifiers.go (CPE, PURL) and the row builder (versionRowFor) both encode this rule, and identifiers_test.go locks it in.

Tracker

Each processor owns its own BulkDataDumpTracker row (chainguard_secdb, wolfi_secdb) keyed on SHA-256 of the fetched feed body. A run that observes an unchanged hash exits early with a task.no_work notification. The tracker is only updated on a clean, full run; partial runs leave it stale so the next scheduled invocation retries.

Schedules

Both schedules are daily EventBridge crons:

  • chainguard-json-processorcron(30 8 * * ? *) (08:30 UTC)
  • wolfi-json-processorcron(0 9 * * ? *) (09:00 UTC)

CPU 512, memory 1024 MiB, expected duration 60 min.

Local execution

just go-chainguard-json-backfill          # local DB
just go-chainguard-json-backfill prod     # .env.production
just go-wolfi-json-backfill
just go-wolfi-json-backfill prod

S3 Persistence

Not used. This processor does not currently archive payloads or quarantine failures to S3. Per the S3 Persistence Contract this is non-compliant — see the compliance matrix for the implementation roadmap.

⚠ Not in the compliance matrix — status needs verification.

Expected paths when implemented:

  • Archive: apksecdb/files/{sha256}/{filename}
  • Quarantine: failed-feeds/apksecdb-processor/{YYYY-MM-DD}/{reason}/{filename}
  • Likely reasons: (none documented)