oss-malware-git-processor
Status: Live Source: ossf/malicious-packages (git) Type:
git(runtime clone/pull via pure-Go go-git — no baked data stage) Source slug:oss-malicious-packagesSchedule: Runs daily at 03:00 UTC (cron(0 3 * * ? *)). CPU 512 / memory 2048 MB /expected_duration_minutes = 60.
Overview
OpenSSF’s malicious-packages repository is the largest curated public corpus of
confirmed malicious registry publishes (npm, PyPI, RubyGems, crates.io,
Packagist, NuGet, Go). It is the reference set the package firewall blocks
against and the baseline every Vulnetix-minted GCVE-110-* malware advisory is
deduplicated against — without it the firewall’s known-bad list collapses to
whatever our own scanners have independently found.
Each run:
- Refreshes the repo.
processor.PullOrCloneclones (or pulls)/data/malicious-packagesat depth 1. The final image stage isscratchand ships no baked advisory data, so the clone happens at runtime through go-git.--no-pullreads the existing HEAD instead. - Short-circuits on an unchanged HEAD against
BulkDataDumpTracker.sha256for sourceoss_malicious_packages; a match without--forcereportstask.noworkand exits 0. - Narrows to changed files via
processor.DetectChangedFiles(3-day window) when a tracker exists; first run /--force/ un-diffable history → full scan. - Walks
osv/forMAL-*.jsonfiles and stores them in transactions of--batch-size(default 200), oneSAVEPOINTper file. - Attributes actors — after the walk,
malwareactor.PostPassruns for sourceoss-malicious-packages.
The mapper override is osv.MapMaliciousAdvisory: unlike the default OSV
mapper it keeps the MAL-* ID as the row key (CVE aliases are preserved as
CVEAlias edges rather than absorbing the record into a CVE row) and forces
isMaliciousPackage = true. CWE-506 (Embedded Malicious Code) is added when
the upstream cwe_ids list does not already carry it. Files whose advisory has
no usable ID are counted unchanged and skipped.
Records produced
| Condition | Records |
|---|---|
| Every stored advisory | CVEMetadata (source="oss-malicious-packages", cveId = MAL-YYYY-NNNN, isMaliciousPackage=true, sourceFileHash = file SHA1), CVEDescription, CVEMetadataReferences (referenceSource="OSS Malicious Packages"), CVEProblemType (upstream cwe_ids + CWE-506), CVEAffected + CVEAffectedVersion |
| Advisory carries a CVSS vector | CVEMetric per severity entry |
| Advisory carries descriptions | one derived CVEMetric (containerType="vulnetix", cvssV4_0) |
| Always | CVEAlias edges via db.InsertAliases (CVE / GHSA aliases, related[], upstream[], plus same-cveId cross-source edges) |
| Every affected package | Dependency, DependencyRegistry, PackageVersion, PackageVersionCVE via db.EnrichAffectedWithDependency — this is what makes an OpenSSF record blockable by package name + version |
| Post-pass, per attributed record | ThreatActor, ThreatActorKey, MalwareThreatActor edges, MalwareAttribution status |
| Clean run | BulkDataDumpTracker row oss_malicious_packages |
Threat-actor attribution
malwareactor.PostPass(source="oss-malicious-packages") runs once per execution,
after the walk and before the tracker write. It is first-time-only — a
(cveId, source) that already has a MalwareAttribution row is never
re-enriched, so steady-state runs do almost no work — and bounded to
MALWARE_ACTOR_BATCH records (default 150). MALWARE_ACTORS=false disables it.
The shared internal/actorintel engine resolves the malware author from package
and repo identity plus registry / GitHub / Docker Hub lookups:
| Aspect | Behaviour |
|---|---|
| Attribution tables | MalwareThreatActor, MalwareAttribution, shared ThreatActor + ThreatActorKey (never the OSM-only OsmThreat* tables) |
| Attribution basis | repo owner / container namespace / registry maintainer / Go-module repo / commit author |
| Hijack handling | compromised-account advisories mark the maintainer hijack-victim-excluded — the victim is not attributed |
| Impersonation trap | for non-Go packages a declared repository is usually the dependency-confusion or typosquat target, so it is captured as claimedRepo* and never attributed as an actor |
| GitHub key harvest | public SSH-auth, SSH-signing and GPG keys → ThreatActorKey; a reused fingerprint links operators across accounts |
| Rate limits | GITHUB_PAT / GITHUB_TOKEN; without one GitHub is capped at 60 req/hr and only bare handles resolve |
The historical backlog is cleared by the one-time cmd/malware-actor-backfill
(just go-malware-actor-backfill), which selects isMaliciousPackage=true AND source<>'osm' across every malware source.
Resume
- Repo-level — the
oss_malicious_packagestracker SHA. - Per-file —
db.LoadProcessedHashes("oss-malicious-packages")skip set (~230k keys on production; the loader has a 5-minute budget).
The tracker write is skipped when the run context was cancelled mid-way, so a truncated run re-scans next time instead of skipping on a matching SHA.
AI enrichment
The pipeline is wired to aienrich; in ECS each stored record gets the shared
post-batch passes (affected routines, ATT&CK, CWE inference, TreeSitter).
Local just runs are inference-free unless AIENRICH=true.
Failure modes
| Symptom | Cause |
|---|---|
repository unchanged, skipping processing | tracker SHA equals HEAD |
failed to load resume hashes, processing all files | read-replica scan of ~230k rows exceeded 5 minutes; the run re-upserts everything |
soft deadline reached, stopping early | EXPECTED_DURATION_MINUTES − 10 elapsed; tracker is skipped when the context was cancelled |
GITHUB_PAT/GITHUB_TOKEN not set | actor attribution degrades to unauthenticated GitHub (60 req/hr) |
Flags
| Flag | Default | Meaning |
|---|---|---|
--force | false | Ignore the tracker SHA and the per-file hash skip set |
--batch-size | 200 | Files per transaction |
--repo | /data/malicious-packages | Clone location |
--data-dir | (repo + /osv) | Override the advisory directory |
--no-pull | false | Use the existing clone’s HEAD instead of pulling |
Local run: just go-oss-malware-git-backfill (append prod for production).
S3 Persistence
- Archive path:
oss-malicious-packages/files/{sha256}/{filename}✓ - Quarantine path:
failed-feeds/oss-malware-git-processor/{YYYY-MM-DD}/{reason}/{filename}✓ - Failure reasons emitted:
parse-error,store-error
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.