Wiz Security Open CVDB Git Processor — Design

Overview

Processes YAML cloud vulnerability advisories from the Wiz Security Open Cloud Vulnerability Database (github.com/wiz-sec/open-cvdb) and creates or enriches CVEMetadata rows (source=opencloud) with minted CLOUD-YYYY-N identifiers.

This processor complements the existing opencloud-rss-processor which ingests the same dataset via RSS feed. YAML files contain richer structured data: severity, affected platforms/services, discoverer metadata, Piercing Index vectors, CVE aliases, and remediation guidance.

When an advisory slug matches an existing GcveAlias entry (from the RSS processor), the existing CLOUD record is enriched rather than duplicated.

Data Source

PropertyValue
Repositoryhttps://github.com/wiz-sec/open-cvdb.git
AuthNone — fully public
FormatYAML files in vulnerabilities/*.yaml
Items~282 cloud vulnerability advisories
BakedYes — shallow clone baked into container image (~37 MB)

Parsing

FieldYAML KeyType
Titletitlestring (required)
Slugslugstring (required)
CVE IDscves[]string (may be empty)
Affected PlatformsaffectedPlatforms[]string
Affected ServicesaffectedServices[]string
Imageimagestring (optional)
Severityseverityenum: critical/high/medium/low/null
Piercing IndexpiercingIndexVectormap (optional)
DiscovererdiscoveredByobject: name/org/domain/twitter
PublishedpublishedAt“YYYY/mm/dd” → Unix seconds
DiscloseddisclosedAt“YYYY/mm/dd” (optional)
ExploitabilityexploitabilityPeriodstring (optional)
Known ITWknownITWExploitationbool/null
Summarysummarystring (multiline)
RemediationmanualRemediationstring (optional)
DetectiondetectionMethodsstring (optional)
Contributorcontributorstring (GitHub URL)
Referencesreferences[]string (URLs)
StatusentryStatusenum: Stub/Draft/Finalized

ID Generation

Shares the CLOUD-{year}-{seq} namespace with opencloud-rss-processor:

  1. On startup, query all GcveAlias entries where aliasSource='opencloud' to build slug → cveID mapping
  2. For each YAML file, if the slug exists in the map, reuse that CLOUD ID (enrichment mode)
  3. If the slug is new, mint a fresh CLOUD-YYYY-N using the shared GcveIssuance sequence

Storage

No new tables or columns. All tables already exist. Everything below goes through processor.StoreCVESourceData, so each affected platform entry additionally feeds db.EnrichAffectedWithDependency (Dependency, DependencyRegistry, PackageVersion, PackageVersionCVE).

wizsec.MapToSourceData always sets DatePublished to wizsec.ParseDate(publishedAt), which returns 0 for a missing or non-YYYY/MM/DD date — so an advisory without a parseable publishedAt lands as datePublished = 0 rather than NULL (internal/wizsec/parser.go:29-39, mapper.go:118).

TableRows inserted
CVEMetadataOne per advisory; source="opencloud", cveId="CLOUD-YYYY-N"
CVEDescription1-3 per advisory: summary, remediation, detection
CVEMetadataReferencesOne per references[] entry + canonical advisory URL
CVEAffectedOne per affectedPlatforms[] entry
GcveIssuanceOne per new advisory; gcveId="GCVE-110-CLOUD-YYYY-N"
GcveAliasOne per new advisory; links slug to GCVE ID
CVEAliasOne per cves[] entry; links CLOUD ID to external CVE IDs
ArtifactOne per advisory (S3 upload of raw YAML)
LinkOne per advisory (links Artifact to CVEMetadata)
CVEMetricOne derived Vulnetix CVSS v4 row per advisory that has a description (containerType="vulnetix"), emitted by the shared storeAdvisory because MapToSourceData sets neither Metrics nor SuppressDerivedMetrics (internal/processor/pipeline.go:528-555)
Dependency, DependencyRegistry, PackageVersion, PackageVersionCVEPer affected platform, via db.EnrichAffectedWithDependency
BulkDataDumpTrackersource="wizsec_advisory", sha256 = git HEAD SHA, written only when the run had zero errors (main.go:305-311)

The advisory’s own severity enum and its piercingIndexVector are not mapped to a CVEMetric — they survive only inside rawDataJSON (internal/wizsec/mapper.go:132-155), which is why CVEMetadata.vectorString is NULL for every opencloud row.

Incremental Strategy

Resume uses per-file SHA1 hashes stored in CVEMetadata.sourceFileHash. On startup, load all hashes for source='opencloud' via LoadProcessedHashes(). Per file: if the slug maps to an existing cveID and the file hash matches, skip. Tracker (BulkDataDumpTracker) stores the git HEAD SHA to skip entire runs when the repo is unchanged.

Flags

FlagDefaultDescription
--repo/data/open-cvdbPath to git repository
--data-dir``Override: process files from this directory
--no-pullfalseSkip git pull, use baked data
--forcefalseReprocess all advisories
--batch-size200Files per transaction batch
--limit0Maximum advisories to process (0 = unlimited)
--emit-critfalse (true in ECS)Stage CRIT candidate envelopes to crit-candidates/pending/

CRIT staging

With --emit-crit (which terraform/go-schedules.tf:3078 passes on every scheduled run) the processor loads the CRIT spec + extended dictionaries and the canonical-pubdate lookup at startup (loadCritCtx, crit_ctx.go), then calls stageCRITForWizSec per advisory after the YAML has been archived so provenance.s3_key points at the stored copy (main.go:415-422). Cloud advisories are the natural CRIT population — an open-cvdb entry is already (provider, service, resource_type)-shaped — so this is the primary CRIT producer for the cloud namespace.

ECS Schedule

Runs weekly on Thursdays at 03:00 UTC (cron(0 3 ? * THU *)), 256 CPU units, 512 MB memory.

Key Files

FilePurpose
cmd/wizsec-git-processor/main.goMain processor
cmd/wizsec-git-processor/s3.goS3 uploader
internal/wizsec/types.goYAML advisory Go structs
internal/wizsec/parser.goYAML parsing, date parsing, file hashing
internal/wizsec/mapper.goAdvisory → CVESourceData mapping
schemas/wizsec_advisory.schema.jsonJSON Schema Draft 7 for rawDataJSON

S3 path deviations

Two uploaders coexist:

  1. Legacy, in-transaction (s3.go) — wizsec/advisories/{fileHash}/{slug}.yaml, written inside the per-file transaction so the resulting Artifact + Link rows and CVEMetadata.fileLinkId can reference the key (main.go:378-413). This is what backs the Artifact / Link rows in the storage table above.
  2. Contract-conformant, post-transaction (s3client.Uploader) — the paths in the generated section below, except that the {sha256} slot carries wizsec.ComputeFileHash, which is SHA-1 (main.go:296), and the filename is the repo-relative vulnerabilities/<slug>.yaml.

S3 Persistence

  • Archive path: opencloud/files/{sha256}/{filename}
  • Quarantine path: failed-feeds/wizsec-git-processor/{YYYY-MM-DD}/{reason}/{filename}
  • Failure reasons emitted: parse-error, store-error, tx-rollback

Uses s3client.Uploader from internal/s3client/uploader.go. Skipped when S3_BUCKET_NAME is unset (local dev).

flowchart LR SRC[Source feed] --> PROC[wizsec-git-processor] PROC -->|success| ARCHIVE[("S3: opencloud/files/{sha256}/{filename}")] PROC -->|failure| Q[("S3: failed-feeds/wizsec-git-processor/{date}/{reason}/{filename}")] PROC --> DB[(PostgreSQL)]

See the S3 Persistence Contract for the full reason taxonomy.