drupal-git-processor
Status: Live Source: DrupalSecurityTeam/drupal-advisory-database (git) Type:
git(repo baked into the image by thedrupal-git-datastage, then pulled at runtime with pure-Go go-git) Source slug:drupalSchedule: Runs weekly on Tuesdays at 04:00 UTC (cron(0 4 ? * TUE *)). CPU 256 / memory 512 MB /expected_duration_minutes = 45.
Overview
Drupal’s security team publishes DRUPAL-SA-* advisories covering core and the
contrib module ecosystem. Many of those advisories never receive a CVE, and the
ones that do are usually filed weeks later — so this is the only feed that lets a
Drupal site’s composer.lock be matched against a Drupal-numbered advisory on
the day it lands.
Each run:
- Refreshes the repo with
processor.PullOrCloneagainst/data/drupal-advisory-database(baked at build time,.baked-sharecorded). The final image stage isscratch, so the pull goes through go-git rather than a git binary. - Short-circuits on an unchanged HEAD by comparing HEAD to
BulkDataDumpTracker.sha256for sourcedrupal_advisory; on a match without--forcethe run reportstask.noworkand exits 0. - Narrows to changed files — with a tracker present,
processor.DetectChangedFileslimits the walk to paths touched in the last 3 days. First run /--force/ un-diffable history → full scan. - Walks
advisories/for files matchingDRUPAL-*.json. - Maps and stores through the shared pipeline in transactions of
--batch-size(default 200), oneSAVEPOINTper file.
osv.MapAdvisory keys each row on the first CVE-YYYY-NNNN+ alias when the
advisory has one and on the DRUPAL-SA-* ID otherwise, so a Drupal-only
advisory keeps its native identifier and a CVE-cross-referenced one merges with
the CVE row from other sources. OSV malicious-package records are skipped.
Records produced
| Condition | Records |
|---|---|
| Every stored advisory | CVEMetadata (source="drupal", sourceFileHash = file SHA1), CVEDescription, CVEMetadataReferences (referenceSource="Drupal"), CVEProblemType (upstream cwe_ids), CVEAffected + CVEAffectedVersion (Drupal advisories carry Packagist package names) |
| Advisory carries a CVSS vector | CVEMetric per upstream severity entry |
| Advisory carries descriptions | one derived CVEMetric (containerType="vulnetix", cvssV4_0) |
| Always | CVEAlias edges via db.InsertAliases (OSV ID, aliases[], related[], upstream[], plus same-cveId cross-source edges) |
| Every affected package | Dependency, DependencyRegistry, PackageVersion, PackageVersionCVE (+ GitHubRepoDependency when a GitHub repo is named) via db.EnrichAffectedWithDependency |
| Clean run | BulkDataDumpTracker row drupal_advisory |
Upstream Drupal advisories carry no CVSS vectors, so in practice the only metric on these rows is the Vulnetix-derived v4 vector.
Resume
- Repo-level —
drupal_advisorytracker SHA. - Per-file —
db.LoadProcessedHashes("drupal")skip set onCVEMetadata.sourceFileHash.
The tracker is skipped when any file errored or the context was cancelled, so
partial runs are retried rather than silently dropped. --force bypasses both
layers.
AI enrichment
The pipeline is wired to aienrich, so ECS runs add the shared per-record
passes (affected routines, ATT&CK, CWE inference, TreeSitter) after each batch
commits. Local just runs are inference-free unless AIENRICH=true.
Failure modes
| Symptom | Cause |
|---|---|
repository unchanged, skipping processing | tracker SHA equals HEAD — normal for a quiet week |
failed to load resume hashes | read-replica timeout; run continues without resume and re-upserts |
soft deadline reached, stopping early | EXPECTED_DURATION_MINUTES − 10 elapsed |
| files errored > 0 | tracker untouched, next run retries; pages only if notify.Finalize judges it systemic |
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/drupal-advisory-database | Clone location |
--data-dir | (repo + /advisories) | Override the advisory directory |
Local run: just go-drupal-git-backfill (append prod to target production).
S3 Persistence
- Archive path:
drupal/files/{sha256}/{filename}✓ - Quarantine path:
failed-feeds/drupal-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.