rustsec-git-processor
Status: Live Source: rustsec/advisory-db Type:
git(shallow clone baked into the container image at/data/advisory-db) Source slug:rustsecSchedule: Runs daily at 04:00 UTC (cron(0 4 * * ? *)), 256 CPU / 512 MB,expected_duration_minutes = 45.
Overview
RustSec is the Rust ecosystem’s own advisory database and the upstream that
cargo audit consumes. Its advisories are Markdown files with a TOML
frontmatter block, not OSV YAML, so they cannot go through the shared
internal/osv mapper — internal/rustsec parses the frontmatter and the
Markdown body separately.
Only files whose path contains /crates/ or /rust/ are processed; root-level
Markdown (README, CONTRIBUTING) is filtered out
(cmd/rustsec-git-processor/main.go:148-154).
The advisory id is RUSTSEC-YYYY-NNNN. When the frontmatter aliases list
carries a CVE-YYYY-NNNN, that CVE becomes the primary cveId and the RUSTSEC
id is demoted to a CVEAlias edge; otherwise the RUSTSEC id is the primary id
(internal/rustsec/mapper.go:33-36).
Two things RustSec provides that the OSV mirror of the same data does not:
- Rust-specific weakness mapping —
internal/rustsec/cwe.goderives CWE ids from the advisorycategoriesandkeywords(mapper.go:92-102), so advisories that never received an NVD CWE still get one. - Proof-of-concept code — RustSec advisories frequently inline a PoC in the
Markdown body.
rustsec.ExtractPoCslifts those fenced code blocks into anExploitrow (main.go:336-344), which is what promotes a Rust advisory from “known” to “weaponised” in exploit-maturity scoring.
Records produced
| Condition | Records |
|---|---|
| Every changed advisory | CVEMetadata (source="rustsec", cveId = CVE alias or RUSTSEC-YYYY-NNNN, sourceAdvisoryRef = RUSTSEC id, sourceFileHash = SHA-1 of the file) |
| Always | CVEAlias via db.InsertAliases — RUSTSEC id + aliases[] + related[], plus the same-cveId cross-source backfill |
| Markdown body present | CVEDescription (prose with fenced code blocks stripped; [INFORMATIONAL: …] prefix when informational is set) |
url / references[] / Markdown links | CVEMetadataReferences (referenceSource="RustSec", type classified per URL) |
Frontmatter cvss | CVEMetric + CVEMetadata.vectorString |
categories / keywords | CVEProblemType (descriptionType="CWE") |
| Every advisory | CVEAffected (vendor crates.io, or rust for std-library packages) + CVEAffectedVersion rows derived from versions.patched / versions.unaffected (each patched range emits both the inverted affected range and the direct unaffected range) |
Via db.EnrichAffectedWithDependency | Dependency, DependencyRegistry, PackageVersion (ecosystem cargo), PackageVersionCVE, GitHubRepoDependency |
| Advisory body contains ≥1 fenced code block | Exploit (source="rustsec", exploitId = RUSTSEC id, platform="rust", category="poc", body SHA-256 + size) + ExploitCVE junctions for every CVE id in the record, resolved against the authoritative sources via db.ResolveExploitCVESources |
| Clean run | BulkDataDumpTracker (source="rustsec_advisory", sha256 = git HEAD SHA) |
Scheduling and resume
Three independent resume layers:
- Repo-level — HEAD SHA is compared to
BulkDataDumpTracker.sha256forrustsec_advisory; an unchanged repo exits 0 withNoWork(main.go:104-116). - Delta window —
processor.DetectChangedFilesnarrows the walk to files git reports as recently changed. Skipped on the first run and under--force(main.go:158-162). - Per-file —
db.LoadProcessedHashes(source="rustsec")givescveId → sourceFileHash; a file whose SHA-1 matches is counted unchanged (main.go:318-321).
Batches of 200 files share one transaction, each file inside its own
SAVEPOINT sp_file so a single bad advisory cannot poison the batch
(main.go:292-361).
S3 path deviation
The generated S3 section below states the contract path. In practice the
{sha256} slot of the archive key is filled with the SHA-1 file digest
already computed for resume, not a SHA-256 (main.go:303-304, 368), so real keys
look like rustsec/files/{sha1}/crates/foo/RUSTSEC-2024-0001.md. Quarantine
reasons are store-error (per-file failure) and tx-rollback (batch transaction
failed after files were staged) — a parse failure is reported as store-error
because the per-file closure does not distinguish it.
Failure modes
LoadKnownCVEIDsstartup cost — the Exploit→CVE junction needs the set of known CVE ids, loaded withSELECT "cveId","source" FROM "CVEMetadata" WHERE "cveId" LIKE 'CVE-%'(main.go:133). On production that is a multi-million row scan and adds a minute or more before the first batch starts.- Tracker is written even on a partial run —
db.UpsertTrackerruns before the errored-file check (main.go:235-254), so a run that errored on some files still records the HEAD SHA. Those files are retried only when the repo next changes; the per-filesourceFileHashmismatch is what picks them up. - Tracker is also written after a soft-deadline truncation — the batch loop
breaks at
main.go:196-199and falls straight through toUpsertTracker, with no equivalent of thectx.Err()guard the shared pipeline has. The next scheduled run sees an unchanged SHA and exitsNoWork, and because the delta window only looks back 3 days the unreached files can be missed entirely. Only ~1,200 advisories exist, so a 35-minute budget makes this unlikely today, but the guard is absent. - No AI enrichment — this processor stores through
processor.StoreCVESourceDatadirectly and never constructs anaienrich.Enricher, so CWE/ATT&CK/TreeSitter inference does not run here.
Flags
| Flag | Default | Description |
|---|---|---|
--force | false | Ignore the tracker SHA, the delta window and the per-file hashes |
--batch-size | 200 | Files per transaction |
--repo | /data/advisory-db | Path to the advisory-db clone |
--data-dir | (repo root) | Override the directory that is walked |
--no-pull | false | Use the baked clone as-is and read HEAD locally |
Local run: just go-rustsec-git-backfill (add prod to target production).
The recipe unsets EXPECTED_DURATION_MINUTES, so a backfill carries no soft
deadline.
S3 Persistence
- Archive path:
rustsec/files/{sha256}/{filename}✓ - Quarantine path:
failed-feeds/rustsec-git-processor/{YYYY-MM-DD}/{reason}/{filename}✓ - Failure reasons emitted:
store-error,tx-rollback
Uses s3client.Uploader from internal/s3client/uploader.go. Skipped when S3_BUCKET_NAME is unset (local dev).
See the S3 Persistence Contract for the full reason taxonomy.