Documentation

Go Processor Blueprint

End-to-end recipe for adding a new `{source}-{type}-processor` to the Vulnetix VDB pipeline. Every section maps to concrete files in this repo and to schemas/migrations in `../saas/`. **The process...

End-to-end recipe for adding a new {source}-{type}-processor to the Vulnetix VDB pipeline. Every section maps to concrete files in this repo and to schemas/migrations in ../saas/. The processor is not “done” until the run/verify section at the end has been executed against .env and the resulting rows inspected.

Where processors run: All scheduled processors execute as ECS Fargate tasks in the vdb-scheduler cluster (terraform/ecs.tf). EventBridge Scheduler triggers each task on its cron; failed invocations are routed to the vdb-scheduler-dlq SQS dead-letter queue (terraform/logs.tf) with 14-day retention. The scanner processor is also in this cluster but triggered on-demand via ecs:RunTask.


0. Naming convention (decided before any code is written)

Refer to AGENTS.md § “Processor Naming Convention” for the authoritative rules. Pick one combination from each axis and lock it in:

AxisValues
sourceshort slug, lowercase, hyphens (rhsa, cisa-kev, nist-nvd, enrich-nuclei, …)
typegit json rss fetch cvrf csaf csv gsheet snort yara
roleprocessor (cloud) and/or backfill (local-only)

Locked names show up in 8 places — keep them character-identical:

cmd/{source}-{type}-processor/                  scripts/go-processors/
Containerfile.go-processors  → AS {source}-{type}-processor
ECR tag                      → go-{source}-{type}-processor-latest
ECS family / EventBridge     → go-{source}-{type}-processor
task-manager.toml key        → [tasks.{source}-{type}-processor]
terraform module             → module "{source}_{type}_processor"
post-push-ecr.sh TARGETS     → {source}-{type}-processor
justfile recipe              → go-{source}-{type}-backfill   (or go-enrich-{source}-{type})

1. Phase 0 — Source reconnaissance (DO THIS FIRST)

Before touching any file in the repo, verify the source is reachable, parseable, and has the shape you assume. Most processor rewrites trace back to skipping this.

Run from a scratch directory:

# 1. Reachability + headers (anti-bot? Cloudflare? auth?)
curl -sSI -A 'Mozilla/5.0 …' "$URL" | head -40

# 2. Sample payload (≥1 advisory, ≥1 page-2 if paginated)
curl -sS -A 'Mozilla/5.0 …' "$URL" -o /tmp/sample.{json,xml,html}

# 3. Identifier inspection — what IS the natural key?
jq '.[0] | keys' /tmp/sample.json
# or
xmllint --xpath '//*[local-name()="vulnerability"][1]' /tmp/sample.xml | head

# 4. CVE alias presence — does this source emit CVE-YYYY-NNNN?
grep -oE 'CVE-[0-9]{4}-[0-9]{3,7}' /tmp/sample.* | sort -u | wc -l

# 5. Pagination / freshness signal
# Is there a Last-Modified header? An incremental "since" parameter? A "page" cursor?
# A SHA-able listing endpoint? An RSS pubDate? A baked git HEAD?

Document the answers in the design doc (§3) under Source contract. The four questions that determine the rest of the architecture:

  1. Cadence: How often does it change? (drives cron_match and frequency_secs)
  2. Volume: How many records total / per delta? (drives cpu_units/memory_mb/expected_duration_minutes)
  3. Identifier: CVE-prefixed natively, source-prefixed, or hybrid? (drives §6 cveId policy)
  4. Backfillability: Can the full historical archive be pulled in one job, or is the source incremental-only? (drives whether to add a cmd/{source}-{type}-backfill/ companion)

2. Phase 1 — Pick the processor archetype

Each of the existing types has a representative reference processor. Read its main.go end-to-end before writing yours.

TypeRepresentativeUse when
gitcmd/pypi-git-processor/main.goSource is a git repo of YAML/JSON advisories. Bake small repos (≤12 MB) in the Containerfile data stage; clone large repos at runtime via processor.PullOrClone.
jsoncmd/aws-security-bulletins-json-processor/main.goREST/JSON API or downloadable JSON dump. Add CRIT staging here when records map to cloud resources.
rsscmd/acsc-rss-processor/main.goRSS/Atom feed. Use when no JSON/git equivalent exists.
fetchcmd/pwno-fetch-processor/main.goHTML scraping or non-API HTTP fetch. Often needs httpclient.NewHTTP1 + browser headers. Use cert-il-fetch (Alpine + Chromium / go-rod) only when Cloudflare challenge bypass is required.
csaf / cvrfcmd/redhat-csaf-processor/, cmd/cisco-cvrf-processor/OASIS CSAF 2.0 JSON or CVRF XML provider trees.
csv / gsheetcmd/epss-csv-backfill/, cmd/gpz-0day-itw-gsheet-processor/CSV-typed feed, including public Google Sheets exports.
snort / yaracmd/community-snort-processor/, cmd/github-yara-fetch-processor/Detection-rule corpora. Populates SnortRule/YaraRule and *RuleCVE junctions, not CVEMetadata directly.
KEV-onlycmd/cisa-kev-json-processor/Source emits only (cveId, exploit metadata) rows. Writes the Kev table; does not create CVEMetadata.
AI-discoverycmd/pwno-fetch-processor/, cmd/gpz-0day-itw-gsheet-processor/Source attributes 0-days to AI-driven discovery. Writes CVEAiDiscovery + AIDiscoveryTag.

If the source feeds cloud-resource advisories (vendor:product:resource_type triples) include CRIT staging following the aws-security-bulletins-json-processor pattern.


3. Design document (scripts/go-processors/{source}-{type}-processor.design.md)

Required before writing main.go. Use this template (mirrors pypi-processor.design.md, sap-patch-day-fetch-processor.design.md):

# {source}-{type}-processor — Design

## 1. Overview
- Purpose, source URL, owner, licence
- Schedule cadence (`cron(...)`) and rationale
- ECS resources: cpu_units, memory_mb, expected_duration_minutes
- Reads: external API/feed, BulkDataDumpTracker, LoadProcessedHashes
- Writes: CVEMetadata / CVEAlias / Exploit / Kev / SnortRule / YaraRule / CrowdSecSighting / CritRecord / VEX / AIDiscovery / S3 archive

## 2. Source contract (from Phase 0 recon)
- Cadence, volume, identifier shape, backfill feasibility
- Anti-bot / auth / rate-limit profile
- Pagination/freshness mechanism (Last-Modified, ETag, listing SHA, cursor, etc.)

## 3. Architecture diagram
```mermaid
graph LR
  Source --> HTTP[httpclient/git PullOrClone] --> Parse --> Map[osv.MapAdvisory or source-specific]
  Map --> Tx[(pgx Tx)] --> Pipeline[processor.StoreCVESourceData]
  Pipeline --> CVEMetadata
  Pipeline --> CVEAlias[(db.InsertAliases)]
  Pipeline --> CVEMetric & CVEDescription & CVEReference & CVEAffected
  Tx -.commit.-> S3[s3client.ArchiveRecord]
  Pipeline --> CRIT[critutil.NewEnvelope -> StageCandidate] --> Drain[critpublisher.DrainKeys] --> CritRecord & VEX

4. Source → DB field mapping

Exhaustive table: every field on the source record → target column on CVEMetadata / Description / Reference / Metric / ProblemType / Affected / AffectedVersion / Alias. Note any synthesised fields (e.g. derived CVSS via cvss.DeriveV4FromDescription).

5. Identifier policy

  • What becomes CVEMetadata.cveId? (CVE-prefix vs source-prefix — see §6)
  • What goes into CVEAlias? Direction. Bundle-suppression risk.
  • If minting source IDs (e.g. {SOURCE}-YYYY-NNNN, GCVE form GCVE-110-{SOURCE}-YYYY-NNNN): how is the sequence loaded (db.LoadMaxGcveSequence pattern) and persisted?

6. CRIT / VEX

  • Which (Provider, Service, ResourceType) triples does this source map to?
  • Are extended dictionaries needed (internal/critutil/dictionaries/extended/{vendor}.json)?
  • VEX status derivation rules.

7. S3 / source-file archive layout

  • Bucket, key prefix, payload schema (raw or normalised)
  • Quarantine reasons enumerated

8. Error handling & Slack

  • Per-record vs batch failure semantics
  • Retry profile (count, backoff, transient vs fatal)
  • Slack events emitted (Started/Completed/Errored/NoWork) and the stats dict shape

9. Performance

  • Concurrency (fan-out width), per-request timeouts, rate-limit pacing
  • Soft deadline behaviour (EXPECTED_DURATION_MINUTES, 5–10 min grace)
  • Resume strategy (LoadProcessedHashes, listing SHA tracker, per-item hash)

10. Backfill

  • Is cmd/{source}-{type}-backfill/ warranted? If yes, what differs from the scheduled binary?
  • Justfile recipe and expected runtime on prod data

---

## 4. Code structure

scripts/go-processors/ ├── cmd/ │ └── {source}-{type}-processor/ │ └── main.go # CLI flags, env, pool init, top-level orchestration ├── internal/ │ └── {source}/ # Source-specific parser/mapper (only if no existing pkg fits) │ ├── client.go # HTTP / git / file fetch │ ├── parse.go # XML/JSON/RSS → typed structs │ ├── map.go # typed structs → osv.CVESourceData │ └── parse_test.go # golden-file tests against schemas/{source}_*.schema.json └── {source}-{type}-processor.design.md


**Reuse first**  before adding a new `internal/{source}/` package, check whether
an existing one fits (`internal/osv` for OSV YAML/JSON, `internal/cisco`, `internal/redhat`,
`internal/critutil`, `internal/sourceident` for ID extraction).

### `main.go` skeleton (json/fetch archetype)

```go
package main

import (
    "context"
    "flag"
    "log/slog"
    "os"
    "time"

    "github.com/vulnetix/vdb-manager/go-processors/internal/db"
    "github.com/vulnetix/vdb-manager/go-processors/internal/httpclient"
    "github.com/vulnetix/vdb-manager/go-processors/internal/notify"
    "github.com/vulnetix/vdb-manager/go-processors/internal/osv"
    "github.com/vulnetix/vdb-manager/go-processors/internal/processor"
    "github.com/vulnetix/vdb-manager/go-processors/internal/s3client"
)

const (
    processorName = "{source}-{type}-processor"
    sourceSlug    = "{source}"
    frequencySecs = 86_400 // tracker cadence
)

func main() {
    force := flag.Bool("force", false, "Bypass tracker freshness check")
    limit := flag.Int("limit", 0, "Limit records (0 = all)")
    flag.Parse()

    logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
    notif := notify.New(logger)
    notif.Started(processorName)

    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    notif.SetOvertimeCancel(ctx, cancel) // honours EXPECTED_DURATION_MINUTES

    pool, err := db.NewPool(ctx, db.EnvDatabaseURL(), db.EnvDatabaseURLRead())
    if err != nil { notif.Errored(processorName, nil, err); os.Exit(1) }
    defer pool.Close()

    // 0. Zombie / lock check (only if multiple writers possible)
    // 1. Tracker freshness
    if !*force {
        fresh, err := db.TrackerFresh(ctx, pool.Read, sourceSlug, frequencySecs)
        if err != nil { notif.Errored(processorName, nil, err); os.Exit(1) }
        if fresh { notif.NoWork(processorName); return }
    }

    httpC := httpclient.New() // or NewHTTP1 for anti-bot sources
    uploader := s3client.NewUploaderFromEnv(logger)

    // 2. Fetch + parse
    items, listingSHA, err := fetchAndParse(ctx, httpC)
    if err != nil { notif.Errored(processorName, nil, err); os.Exit(1) }

    // 3. Resume set
    seen, err := db.LoadProcessedHashes(ctx, pool.Read, sourceSlug)
    if err != nil { notif.Errored(processorName, nil, err); os.Exit(1) }

    stats := map[string]any{"fetched": len(items)}
    softDeadline := notif.SoftDeadline()

    // 4. Per-item processing
    for i, it := range items {
        if *limit > 0 && i >= *limit { break }
        if !softDeadline.IsZero() && time.Now().After(softDeadline) {
            logger.Warn("soft deadline reached, stopping cleanly")
            break
        }
        if _, dup := seen[it.SourceFileHash]; dup && !*force { continue }

        data := mapToSourceData(it) // *osv.CVESourceData with Aliases populated
        if err := processor.RunOne(ctx, pool, data, processor.Hooks{
            OnArchive:    uploader.PipelineArchive(sourceSlug),
            OnQuarantine: uploader.PipelineQuarantine(processorName),
        }); err != nil {
            notif.RecordError(it.ID + ": " + err.Error())
            continue
        }
        stats["stored"] = stats["stored"].(int) + 1
    }

    // 5. Tracker bump (only if no fatal errors)
    if !notif.HasErrors() {
        _ = db.UpsertTracker(ctx, pool.Write, sourceSlug, listingSHA, frequencySecs)
    }

    if notif.HasErrors() {
        notif.Errored(processorName, stats, nil)
        os.Exit(1)
    }
    notif.Completed(processorName, stats)
}

5. Central pipeline contract — DO NOT BYPASS

Every advisory MUST flow through processor.StoreCVESourceData (file: internal/processor/pipeline.go:595). It composes:

  1. db.UpsertCVEMetadata (composite key (cveId, source))
  2. db.InsertDescriptions, InsertReferences, InsertMetrics, InsertProblemTypes, UpsertAffected, InsertVersions
  3. db.EnrichAffectedWithDependency (opt-in)
  4. db.InsertAliases(ctx, tx, cveID, source, aliases, logger) — see § “Alias Writes” in AGENTS.md

Direct UpsertCVEMetadata callers MUST also call db.InsertAliases (even with aliases == nil) so the same-cveId cross-source backfill runs. Never issue raw INSERT INTO "CVEAlias" SQL — bundle-suppression and canonicalisation are implemented inside InsertAliases only.


6. Identifier policy — CVEMetadata.cveId vs CVEAlias

Decision is per-source and must be documented in §5 of the design doc.

Rule of thumb (matches existing processors):

Source emits…CVEMetadata.cveIdCVEAlias rows
Native CVE-prefixed IDs only (cve.org, NVD)CVE-YYYY-NNNNCross-source peers (nist-nvd ⇄ cve.org) auto-linked
Source-prefixed + CVE alias(es) (RHSA, GHSA, MSRC, EUVD)CVE-YYYY-NNNN (preferred — osv.MapAdvisory extracts it from aliases[])The source-prefixed ID (RHSA-…, GHSA-…)
Source-prefixed only, no CVE alias (PWNO, CIRCL, BDU, CNVD, ACSC-minted ACSC-…)The source/minted IDAny future CVE references discovered later
Source-prefixed and want to KEEP the source ID as primaryUse osv.MapAdvisoryKeepID(...)All CVE-prefixed aliases

Why prefer the CVE-prefix as primary when it exists: it maximises hit-rate for downstream lookups (Kev, EPSS, Exploit, customer SBOM scans) which all key on CVE-YYYY-NNNN. The source-prefixed ID survives as a CVEAlias row, so reverse lookups from RHSA-/GHSA-/MSRC- still resolve via the alias graph.

Bundle suppression: when an RHSA/MSRC/etc. bundles N CVE aliases, only the first becomes cveId; the rest are dropped from the alias edges to prevent fan-out (implemented in internal/db/cvealias.go:50). Document the chosen ordering.

Minted IDs: if the source needs a synthetic prefix (e.g. ACSC’s ACSC-YYYY-NNNN, no VVD- prefix), follow the GCVE pattern: db.LoadMaxGcveSequence at startup, increment in-memory, persist via the GCVE issuance row + GcveAlias junctions. The GCVE identifier uses Vulnetix’s GCVE Numbering Authority (GNA) id 110 — i.e. GCVE-110-ACSC-YYYY-NNNN. Mirror the CVE alias edges via db.InsertAliases so reverse lookups work in both directions.


7. CRIT and VEX records

CRIT (cloud advisory) records belong to processors whose source attributes vulnerabilities to (Provider, Service, ResourceType) triples — AWS bulletins, GCP bulletins, Cloudflare advisories, Oracle CPU, ServiceNow KB, SAP Patch Day, IBM Security Bulletins, Broadcom/VMware, Salesforce.

Pattern (from aws-security-bulletins-json-processor):

import (
    "github.com/vulnetix/vdb-manager/go-processors/internal/critutil"
    "github.com/vulnetix/vdb-manager/go-processors/internal/critpublisher"
)

// Once per process:
dictRegistry, _ := critutil.LoadAllExtendedDictionaries()
// or critutil.LoadDictionary("internal/critutil/dictionaries/extended/{vendor}.json")

// Per (vulnId × ServiceMatch):
candidate := mapBulletinToCRIT(item, match) // build critutil.Candidate
env, err := critutil.NewEnvelope(processorName, candidate, provenance, validation)
key, err := critutil.StageCandidate(ctx, uploader, env) // S3 → crit-candidates/pending/...
_ = db.RegisterS3QueueObject(ctx, pool.Write, "pending", key)

// End of run:
if !envBool("CRIT_DISABLE_INPROCESS_DRAIN") {
    _ = critpublisher.DrainKeys(ctx, pool, uploader, stagedKeys, logger)
}

DrainKeys validates each envelope against crit-record-v0.2.0.schema.json, inserts CritRecord + sibling VEX statement (openvex table), or moves the envelope to the rejected/spec-violation prefix. Processors do not emit VEX directly — that responsibility belongs to the publisher.

If the source needs new (provider, service, resource_type) tokens, extend internal/critutil/dictionaries/extended/{vendor}.json and reference it from the design doc.


8. Niche tables (Exploit / Kev / Snort / Yara / CrowdSec / VulnCheck / AIDiscovery)

Pick one (or more) based on source character. Helpers all live in internal/db/:

Table familyHelperUsed by
Exploit + ExploitCVEdb.UpsertExploit, db.InsertExploitCVE0day-today, ExploitDB, Metasploit, snort/yara processors (legacy compat)
Kevdb.UpsertKevcisa-kev, eukev, vulncheck-kev (writes Kev only — does NOT create CVEMetadata)
VulnetixKevdb.UpsertVulnetixKev(reason)vulnetix-kev (synthesised, reason ∈ {crowdsec_sighting, snort_rule, nuclei_template, nse_script, big_sleep, …})
SnortRule + SnortRuleCVEdb.UpsertSnortRule, db.InsertSnortRuleCVEcommunity-snort, emergingthreats-snort, community-suricata-snort
YaraRule + YaraRuleCVEdb.UpsertYaraRule, db.InsertYaraRuleCVEgithub-yara-fetch
CrowdSecSighting + CrowdSecLogdb.UpsertCrowdSecSightingcrowdsec-json-processor
VulnCheckKEV + VulnCheckKEVCVEdb.UpsertVulnCheckKEVvulncheck-kev-json
CVEAiDiscovery + AIDiscoveryTagdb.UpsertAIDiscoverypwno-fetch, gpz-0day-itw-gsheet, big-sleep

For rule-corpus processors (snort/yara) the workflow is: parse rule → extract CVE refs → upsert *Rule row → write *RuleCVE junctions → optionally write a legacy Exploit row for backward compat (sourceSlug = source name). No CVEMetadata write; rules attach to whatever CVEMetadata the central pipeline already created.


9. Schemas (schemas/{source}_*.schema.json)

Add a JSON Schema only when:

  1. The source emits a JSON/YAML format not already covered, AND
  2. Either Go code will validate parsed payloads at runtime (//go:embed + jsonschema), or
  3. The schema is needed as fixture/contract documentation for parse_test.go golden tests.

Place at schemas/{source}_advisory.schema.json following neighbouring files (e.g. apk_secdb_advisory.schema.json, csaf_2.0.schema.json). For CRIT producers, the canonical schema lives in scripts/go-processors/internal/critutil/crit-record-v0.2.0.schema.json — do not duplicate it under schemas/.


10. Database migrations (../saas/prisma/migrations/)

The vdb-manager repo defines schema in ../saas/prisma/models/*.prisma but all migrations are owned by the saas project. If your processor needs new columns/tables/indexes:

  1. Add the model edits to the appropriate file under ../saas/prisma/models/ (e.g. niche tables → exploit.prisma, threat-intel.prisma, ai-discovery.prisma; CRIT → crit.prisma).
  2. Generate a new migration directory at ../saas/prisma/migrations/YYYYMMDDNNNNNN_add_{thing}/migration.sql with idempotent SQL:
-- Migration: Add {thing}
-- Idempotent: safe to run multiple times

CREATE TABLE IF NOT EXISTS "{Table}" ( ... );

DO $$ BEGIN
    IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = '{Table}_unique_key') THEN
        ALTER TABLE "{Table}" ADD CONSTRAINT "{Table}_unique_key" UNIQUE ("col1","col2");
    END IF;
END $$;

CREATE INDEX CONCURRENTLY IF NOT EXISTS "{Table}_idx" ON "{Table}" ("col");
  1. Apply locally via npx prisma migrate deploy from ../saas/; apply to production via set -a; source .env.production; set +a; psql "$DATABASE_URL" -f migration.sql from the ../saas/ directory. Use CREATE INDEX CONCURRENTLY on large tables to avoid locking out ECS connections.

Do NOT commit a Prisma migration without first running the SQL against a local copy of production schema (pg_dump --schema-only is sufficient).


11. Infrastructure wiring — every place to touch

Every file edit listed here is required. Skipping any one of them breaks deploy or scheduling.

11.1 Containerfile.go-processors

Append a final-target stage. Pattern:

# ---- {Source} {Type} Processor (final target) ----
FROM scratch AS {source}-{type}-processor
COPY --from=cert-builder /etc/ssl/ /etc/ssl/
COPY --from=builder --chmod=0755 /out/{source}-{type}-processor /app/{source}-{type}-processor
ENTRYPOINT ["/app/{source}-{type}-processor"]

Variants:

  • Git-baked data (≤12 MB repo): add a FROM alpine:3.21 AS {source}-git-data stage above and COPY --from={source}-git-data /data/{repo} /data/{repo} into the final stage. Always write .baked-sha so the scratch container can resolve HEAD without a git binary.
  • Runtime git pull (large repo): omit the data stage; the binary calls processor.PullOrClone at runtime.
  • Needs Chromium (Cloudflare bypass): use FROM alpine:3.21 final stage and RUN apk add --no-cache chromium. Reserved for cert-il-fetch-processor-class sources.
  • Needs runtime git (small repo, want freshness on each ECS run): use FROM alpine:3.21 + RUN apk add --no-cache git ca-certificates (see ocaml-git-processor, hsec-git-processor).

11.2 scripts/task-manager.toml

[tasks.{source}-{type}-processor]
name        = "{Source} {Type} Processor"
description = "One-line purpose"
category    = "go-standalone"   # or "go-git"
container   = true
runner_cmd  = "go {source}-{type}-processor"
aws_schedule = "go-{source}-{type}-processor"
cron_match  = "0 6 * * *"        # daily 06:00 UTC unless source dictates otherwise
cpu_units   = 256
memory_mb   = 512
expected_duration_minutes = 30
design_doc  = "scripts/go-processors/{source}-{type}-processor.design.md"

[[tasks.{source}-{type}-processor.args]]
name = "force"
type = "flag"
required = false
default = "false"

11.3 terraform/go-schedules.tf

module "{source}_{type}_processor" {
  source = "./modules/ecs-go-task"

  task_name                 = "{source}-{type}-processor"
  schedule_expression       = "cron(0 6 * * ? *)"
  command                   = ["/app/{source}-{type}-processor"]
  cpu                       = 256
  memory                    = 512
  schedule_enabled          = true
  expected_duration_minutes = 30

  cluster_arn        = aws_ecs_cluster.vdb.arn
  container_image    = "${aws_ecr_repository.go_processors.repository_url}:go-{source}-{type}-processor-${var.go_container_image_tag}"
  execution_role_arn = aws_iam_role.ecs_execution.arn
  task_role_arn      = aws_iam_role.ecs_task.arn
  scheduler_role_arn = aws_iam_role.scheduler.arn
  subnet_ids         = var.subnet_ids
  security_group_ids = [aws_security_group.ecs_tasks.id]
  secrets            = local.go_task_secrets   # use _github variant if GH_TOKEN needed
  environment        = local.go_task_environment
  dlq_arn            = aws_sqs_queue.scheduler_dlq.arn
  aws_region         = var.aws_region
  log_retention_days = var.log_retention_days
}

task_name MUST match the value scheduleToTaskName(aws_schedule) derives — i.e. the toml aws_schedule with go- stripped. Stale task_name values land the row in the dashboard’s “Unknown” frequency bucket.

Logging & retention

Every processor log group (/ecs/vdb-scheduler/*) keeps only 3 days of CloudWatch retention (var.log_retention_days). A CloudWatch subscription filter streams all events through a single Kinesis Firehose into the private vdb-manager-logs S3 bucket, where a lifecycle rule transitions objects to Glacier Deep Archive at day 30 and expires them at day 395 — durable ~1-year history at Glacier prices, without paying for long CloudWatch retention. The Firehose wiring lives in terraform/logs-archive.tf; the per-group filter is part of the ecs-go-task module.

11.4 .claude/hooks/post-push-ecr.sh

Add {source}-{type}-processor to TARGETS (alphabetical order).

11.5 scripts/task-dashboard/cmd/ecr-build/targets.go

Add to simpleTargets (no data stage) or dataTargets (git-baked).

11.6 justfile

Backfill recipe:

go-{source}-{type}-backfill TARGET="local" BATCH_SIZE="200" FORCE="false" LIMIT="0":
    #!/usr/bin/env bash
    set -euo pipefail
    if [ "{{TARGET}}" = "prod" ]; then ENV_FILE=".env.production"; else ENV_FILE=".env"; fi
    [ -f "$ENV_FILE" ] || { echo "ERROR: $ENV_FILE not found"; exit 1; }
    set -a; source "$ENV_FILE"; set +a
    unset EXPECTED_DURATION_MINUTES   # backfill MUST run to completion — no soft deadline
    cd scripts/go-processors && go run ./cmd/{source}-{type}-processor \
        --batch-size={{BATCH_SIZE}} --force={{FORCE}} --limit={{LIMIT}}

For git-baked sources also bind --repo from REPO/REPO_ABS=$(realpath …) before cd scripts/go-processors (the binary’s cwd changes).

For enrichment processors use go-enrich-{source}-{type} (no -backfill suffix).

11.7 .github/workflows/go-ecr-deploy.yml

The matrix is sourced from task-manager.toml; once steps 11.2 and 11.4 are complete the manual workflow_dispatch trigger picks up the new target without file edits to the workflow itself. Confirm by listing inputs: gh workflow view go-ecr-deploy.yml.

11.8 scripts/go-processors/entrypoint.sh

No edit required — already constructs DATABASE_URL/DATABASE_URL_READ from ECS secrets for every processor.

11.9 scripts/sql/

Only add a SQL helper here for one-off operational fixes (e.g. backfilling a source attribution column for legacy rows). Pattern: normalize-{thing}.sql. Not used at processor runtime.


12. Slack notifications (mandatory)

Every processor wraps its top-level orchestration with the notify package (internal/notify/notify.go):

notif := notify.New(logger)
notif.Started(processorName)
defer func() {
    if r := recover(); r != nil {
        notif.Errored(processorName, stats, fmt.Errorf("panic: %v", r))
        os.Exit(2)
    }
}()
// per-record:
notif.RecordError(itemID + ": " + err.Error())
// final:
if notif.HasErrors() { notif.Errored(processorName, stats, nil); os.Exit(1) }
notif.Completed(processorName, stats)

notif.SetOvertimeCancel(ctx, cancel) cancels the root context 10 min before EXPECTED_DURATION_MINUTES elapses, letting in-flight transactions commit. SNS topic + bot are wired through ECS env vars by Terraform — no per-processor config.


13. Performance & resilience checklist

  • HTTP client uses httpclient.New (HTTP/2 default) or httpclient.NewHTTP1 (only when source rejects HTTP/2 — common for older WAFs).
  • Per-request context.WithTimeout (90 s default; tune for the source).
  • Concurrent fetches behind a chan struct{} semaphore (4 workers is the default in existing processors).
  • Per-record retry: 3 attempts, exponential backoff, classify unexpected EOF / connection reset as transient.
  • Resume set loaded once at startup via db.LoadProcessedHashes.
  • Tracker freshness check skipped under --force.
  • Soft deadline checked before dispatching new work, not in the middle of a transaction.
  • Pool sized via db.NewPool defaults (read=5, write=5). Do not create ad-hoc pgx.Pool instances.
  • S3 archive happens after transaction commit (post-commit hook). Quarantine on parse/store failure.

14. Tests

  • Place golden-file fixtures under internal/{source}/testdata/.
  • parse_test.go: parse a recorded payload, assert mapped osv.CVESourceData matches an expected JSON snapshot (use cmp.Diff).
  • map_test.go: cveId selection across the cases enumerated in design § 5.
  • alias_test.go (when bundle suppression is in play): assert db.InsertAliases keeps the right primary CVE under multi-CVE bundles.
  • Run from repo root:
    cd scripts/go-processors && go test ./internal/{source}/... ./cmd/{source}-{type}-processor/...
    

15. Build & deploy verification

Before opening a PR:

# 1. Compile every binary (catches type errors that go test misses)
cd scripts/go-processors
go build ./...
go vet ./...

# 2. Local container build (smoke — uses podman, ARM64 cross-compile)
cd ../..
podman build -f Containerfile.go-processors --target {source}-{type}-processor \
    -t local/{source}-{type}-processor:dev .

# 3. Run via the dashboard TUI — exercises the targets.go entry
just dashboard       # navigate to the new processor, "Build" then "Run"

Manual ECR push for the first production build:

# Triggered three ways — pick whichever the situation calls for
gh workflow run go-ecr-deploy.yml -f target={source}-{type}-processor   # GHA
just ecr-push TARGET={source}-{type}-processor                           # justfile
# Or simply: git push origin main → .claude/hooks/post-push-ecr.sh fires

16. End-to-end run/verify (REQUIRED before declaring done)

This is the gate. The processor is not finished until these steps pass against real data and the resulting rows are inspected.

16.1 Local run against production DB (read-only verification)

# From vdb-manager root
just go-{source}-{type}-backfill TARGET=prod LIMIT=10 FORCE=true

Expected log shape:

{"time":"…","level":"INFO","msg":"started","processor":"{source}-{type}-processor"}
{"time":"…","level":"INFO","msg":"fetched","count":<n>}
{"time":"…","level":"INFO","msg":"stored","cveId":"<id>","source":"{source}"}
{"time":"…","level":"INFO","msg":"completed","stats":{"fetched":10,"stored":N,"skipped":M}}

Confirm Slack channel received Started and Completed events.

16.2 Database inspection

set -a; source .env.production; set +a
psql "$DATABASE_URL" <<'SQL'
-- 1. CVEMetadata rows landed and have the expected source slug
SELECT "cveId","source","datePublished","title"
  FROM "CVEMetadata" WHERE "source" = '{source}'
  ORDER BY "datePublished" DESC NULLS LAST LIMIT 10;

-- 2. Aliases were written and direction is canonical
SELECT "primaryCveId","primarySource","aliasCveId","aliasSource","discoveredFrom"
  FROM "CVEAlias"
 WHERE "primarySource" = '{source}' OR "aliasSource" = '{source}'
 ORDER BY "discoveredAt" DESC LIMIT 20;

-- 3. References + Metrics + Affected populated
SELECT count(*) FROM "CVEReference" r
  JOIN "CVEMetadata" m ON r."cveId"=m."cveId" AND r."source"=m."source"
 WHERE m."source" = '{source}';

-- 4. BulkDataDumpTracker bumped
SELECT * FROM "BulkDataDumpTracker" WHERE "source" = '{source}';

-- 5. Niche tables (only the ones your processor writes)
-- SELECT count(*) FROM "Kev" WHERE "source" = '{source}';
-- SELECT count(*) FROM "SnortRule" WHERE "source" = '{source}';
-- SELECT count(*) FROM "CritRecord" WHERE "producer" LIKE '{source}%';
SQL

Failure modes to watch for and what they mean:

SymptomLikely cause
discoveredAt near 1700000000 (Unix seconds, not millis)Used time.Now().Unix() — must be UnixMilli()
Alias rows missing the same-cveId cross-source backfillBypassed db.InsertAliases (raw SQL or only called on non-empty lists)
cveId is the source-prefixed ID despite a CVE alias being presentosv.MapAdvisory not used or KeepID accidentally selected
Tracker not bumpedFinal UpsertTracker conditioned incorrectly, or processor exited via os.Exit(1) after errors (correct — only bump on clean exit)
Connection error to 127.0.0.1:5432Pool init bug (see CLAUDE.md “Prisma Connection Issues” — applies to pgx too: pass DSN string, not a pre-built Pool)
CRIT_DISABLE_INPROCESS_DRAIN rows stuck in S3QueueObject with processingStatus='pending'critpublisher.DrainKeys not invoked at end of run

16.3 ECS smoke run

After the first ECR push, manually invoke the EventBridge target once to confirm Fargate task scheduling, IAM, secrets, and CloudWatch logging:

aws scheduler get-schedule --name go-{source}-{type}-processor --group-name vdb
aws events test-event-pattern ...    # if pattern-based
# Easiest: tweak the cron to `cron(*/5 * * * ? *)` in terraform, apply, watch one
# fire in CloudWatch Logs `/ecs/vdb-scheduler/go-{source}-{type}-processor`,
# then revert.

Confirm the ECS task exits 0 with a Completed Slack message.

16.4 Re-run idempotency

just go-{source}-{type}-backfill TARGET=prod LIMIT=10 FORCE=false

Should report NoWork (tracker fresh) or zero new rows. Re-running with FORCE=true must not produce duplicate CVEMetadata, duplicate CVEAlias, or duplicate Exploit/Kev/SnortRule rows. If it does, an upsert key is wrong.


17. Definition of done

Tick every box before merging. Anything unticked is a regression vector.

  • Phase 0 recon documented in design doc § 2 (with curl evidence)
  • cmd/{source}-{type}-processor/main.go compiles and go vet is clean
  • Reuses an existing internal/ package wherever possible; new packages have a parse_test.go golden test
  • processor.StoreCVESourceData is the only path to CVEMetadata writes (or db.UpsertCVEMetadata + explicit db.InsertAliases if direct)
  • Containerfile.go-processors, task-manager.toml, terraform/go-schedules.tf, .claude/hooks/post-push-ecr.sh, scripts/task-dashboard/cmd/ecr-build/targets.go, justfile all updated with the locked name (character-identical)
  • Migration added to ../saas/prisma/migrations/ if schema changed; SQL is idempotent and uses CREATE INDEX CONCURRENTLY on large tables
  • CRIT staging + drain wired (only if cloud-resource source)
  • Slack Started/Completed/Errored/NoWork events confirmed in channel
  • § 16 run/verify executed end-to-end against .env.production (logs, DB rows, Slack, idempotency check)
  • One Fargate fire observed in CloudWatch with exit code 0
Design: ABB CSAF Processor
Fetches ABB PSIRT security advisories published in **CSAF 2.0** (Common Security Advisory Framework) JSON format from the public TLP:WHITE feed index and stores the parsed vulnerability data into t...
ACSC Alert RSS Processor — Design
Fetches security alerts from the Australian Cyber Security Centre (ACSC) RSS 2.0 feed (`https://www.cyber.gov.au/rss/alerts`) and creates first-class
adobe-security-fetch-processor
Scrapes Adobe Product Security Bulletins (APSB) and Advisories (APSA) from helpx.adobe.com. Ingests per-CVE records with CVSS v3.1, CWE, affected versions, and solution metadata.
aur-fetch-processor
Walks the Arch User Repository package listing (newest-modified first), records each package as a PackageVersion, and mints malware advisories for packages with factual malicious PKGBUILD evidence via a Go port of the traur detection engine.
homebrew-fetch-processor
AUR-equivalent malware processor for the Homebrew ecosystem. Enumerates homebrew-core + homebrew-cask (formulae.brew.sh API) and discovers third-party taps via GitHub search; persists GitHub repo data first, then runs the shared malscan-engine detect engine over the Ruby DSL to mint source='homebrew' malware advisories with threat-actor + IOC linkage.
ALAS Processor — superseded
Historical note on the pre-2026-04-30 ALAS ingestion model, which wrote one CVE-keyed row per Amazon Linux release line under source=amazon-linux / amazon-linux-2 / amazon-linux-2023. Superseded by alas-rss-processor.
go-json-processor
Walks the public Go module index (incremental by an RFC3339 timestamp cursor), records each module@version as a PackageVersion, and mints malware advisories for modules with factual malicious evidence in their published .go sources or go.mod via the shared malscan-engine.
npm-json-processor
Walks the npm registry CouchDB _changes firehose (incremental by sequence), records each changed package's latest version as a PackageVersion, and mints malware advisories for packages with factual malicious evidence in their lifecycle install hooks or published tarball JavaScript via the shared malscan-engine.
packagist-json-processor
Walks the Packagist changes feed (incremental by microsecond timestamp cursor), records each changed package's latest version as a PackageVersion, and mints malware advisories for PHP/Composer packages with factual malicious evidence in their composer.json lifecycle scripts or bundled PHP via the shared malscan-engine.
Design: AWS ALAS RSS Processor
Fetches Amazon Linux Security Advisories (ALAS) from the three official RSS feeds (AL1, AL2, AL2023), parses package-level CVE references, stores per-advisory rows under `source='amazon'` keyed by...
alpine-apk-fetch-processor
Fetches the Alpine Linux APKINDEX for edge/main and edge/community, records each recently-built apk as a PackageVersion, and mints malware advisories for packages whose APKBUILD build recipe carries factual malicious evidence via the shared malscan-engine.
cargo-json-processor
Walks the crates.io recent-updates feed (paginated, sorted by update time, incremental by an updated_at watermark), records each changed crate's newest version as a PackageVersion, and mints malware advisories for crates with factual malicious evidence in their build.rs build script or published Rust source via the shared malscan-engine.
maven-json-processor
Walks the Maven Central solrsearch GAV feed (newest published group:artifact:version coordinates, incremental by publish-timestamp watermark), records each changed artifact as a PackageVersion, and mints malware advisories for artifacts with factual malicious evidence in their POM build manifest or published JAR scripts via the shared malscan-engine.
pubdev-json-processor
Walks the pub.dev recent-packages JSON listing (newest first, paged via next_url), records each package's latest version as a PackageVersion (ecosystem pub), and mints malware advisories for Dart/Flutter packages with factual malicious evidence in their pubspec manifest or published archive's Dart sources via the shared malscan-engine.
pypi-json-processor
Walks the PyPI XML-RPC changelog (incremental by serial), records each changed package's latest release as a PackageVersion, enriches with conda-forge presence, and mints malware advisories for packages with factual malicious evidence in setup.py or sdist sources via the shared malscan-engine.
rubygems-json-processor
Walks the RubyGems.org activity feeds (just_updated + latest), records each changed gem's latest version as a PackageVersion, and mints malware advisories for gems with factual malicious evidence in their gemspec native-extension build, install hooks, or bundled Ruby/C sources via the shared malscan-engine.
alibaba-cloud-fetch-processor — Design (RETARGETED — commentary enricher)
programmatic feed; discovery requires sitemap or `site:alibabacloud.com` Google search, which makes this an enrichment-of-known-CVE flow rather than a CVE-discovery flow.
cran-json-processor
Walks CRAN's recent-releases feed (via the crandb mirror, incremental by release date), records each released R package's latest version as a PackageVersion, and mints malware advisories for packages with factual malicious evidence in their install-time scripts (configure / cleanup / Makevars) or .R sources via the shared malscan-engine.
alinux2-rss-processor
Ingests the Alibaba Cloud Linux 2 YUM updateinfo XML feed (mirrors.aliyun.com/alinux/cve/alinux2.xml), writing one record per ALINUX2-SA / HOTFIX-BA advisory under source=alibaba-cloud with per-CVE CVSS3 vectors, RPM package lists and CVEAlias edges.
conan-git-processor
Clones/pulls the ConanCenter index git repository at runtime (pure-Go go-git), diffs the commit range since the saved commit-SHA watermark to find changed C/C++ recipes, records each changed recipe's newest version as a PackageVersion, and mints malware advisories for recipes whose conanfile.py carries factual malicious evidence via the shared malscan-engine.
almalinux-git-processor
Walks the AlmaLinux OSV advisory database (ALSA-*.json) baked into the container image, maps each advisory through the shared OSV mapper, and upserts CVEMetadata plus its child rows under source=almalinux so RPM-level AlmaLinux errata are resolvable alongside the upstream CVE.
Anchore ADP Git Processor
Processes [Anchore ADP (Authorized Data Publisher)](https://github.com/anchore/cve-data-enrichment) CPE enrichment JSON files into CVEMetadata and related tables. Anchore provides CPE configuration...
APK Secdb Processors (chainguard-json-processor, wolfi-json-processor)
Two daily processors that ingest the Alpine `secdb` security feeds published by Chainguard and Wolfi:
chainguard-json-processor
Ingests the Chainguard Alpine-secdb security feed (packages.cgr.dev) and writes ADP-style CVEAffected + CVEAffectedVersion rows under whichever authority already owns each CVE, plus Chainguard container-registry attribution.
atlassian-json-processor
Ingests Atlassian's Vulnerability Transparency API — the /v1/products document, since /v1/cves answers 200 with an empty body — inverting its product→version→CVE map into one CVEMetadata record per CVE under source='atlassian', and stages CRIT candidates for the Atlassian-scoped products it recognises.
wolfi-json-processor
Ingests the Wolfi OS Alpine-secdb feed (packages.wolfi.dev/os/security.json) and writes ADP-style CVEAffected + CVEAffectedVersion rows against existing CVE records, so a CVE can be answered with the exact Wolfi apk version that fixes it. Writes no primary CVEMetadata of its own.
Design: AWS Security Bulletins JSON Processor
Fetches AWS Security Bulletins via the public aws.amazon.com directory JSON API, then per-item HTML body fetch from server-rendered bulletin pages, parses CVE/GHSA aliases and AWS service reference...
binarly-rss-processor
Fetches Binarly UEFI/firmware security advisories from the public RSS 2.0 feed and creates CVEMetadata rows (source=binarly) for every CVE ID embedded in an advisory's description. Advisories with no CVE assigned yet are dropped.
bitnami-git-processor
Walks the Bitnami vulndb OSV advisory database (BIT-*.json) baked into the container image and upserts CVEMetadata plus its child rows under source=bitnami, giving container-image consumers the Bitnami-packaged component and version boundaries that the upstream CVE record does not carry.
box-fetch-processor — Design (DEFERRED)
HackerOne-private; no advisory feed exists in any form.
broadcom-vmware-security-fetch-processor — Design
Ingest VMware Security Advisories (VMSA) and Tanzu (TKG / TAS / Ops Manager / Harbor / CF buildpack / BOSH stemcell) enrichment data into vdb-manager via four unauthenticated, structured public sou...
Bugcrowd Crowdstream Processor — Design Document
1. **Global feed** (`/crowdstream.json?page=N`) — 7-day rolling window, ~925 entries, 20/page 2. **Per-program feed** (`/engagements/{code}/crowdstream.json?page=N`) — configurable window (7 days t...
canonical-git-processor
Walks the Canonical Ubuntu Security Notices OSV database (osv/usn/USN-*.json) sparse-baked into the container image and upserts CVEMetadata plus its child rows under source=canonical, so Ubuntu's per-release deb package/version fix boundaries are resolvable alongside the upstream CVE.
CERT.at RSS Processor — Design
Fetches security warnings from the Austrian national CERT (CERT.at) Atom 1.0 feed and creates `CVEMetadata` rows (source=`cert-at`) under original CVE IDs.
CERT-AU (AUSCERT) RSS Processor — Design
Fetches security bulletins from the AUSCERT (Australian Computer Emergency Response Team) RSS 2.0 feed and creates `CVEMetadata` rows (source=`cert-au`) under original upstream CVE IDs.
CERT-BE Fetch Processor — Design
Fetches security advisories from the Belgian Centre for Cybersecurity (CERT-BE / CCB). The RSS feed provides item URLs and titles, but the description is hardcoded ('CCB Advisories'). Each advisory...
CERT-CA RSS Processor — Design
Fetches security advisories from the Canadian Centre for Cyber Security (CCCS) Atom 1.0 feeds in English and French. EN is the primary language; FR entries are merged by advisory ID matching. Recor...
CERT-EU RSS Processor — Design
Fetches security advisories from the CERT-EU (Cybersecurity Service for the EU Institutions, Bodies, Offices and Agencies) RSS 2.0 feed and creates
CERT-IL Fetch Processor Design
Ingests Israeli CERT (CERT-IL) ILVN advisories. The gov.il listing is unreachable behind a Cloudflare challenge from every environment this fleet has, so advisories arrive through INCD's CNA record...
CERT-IT RSS Processor — Design
Fetches security advisories from CSIRT-ITA (Italian Computer Security Incident Response Team, under ACN — Agenzia per la Cybersicurezza Nazionale) and creates
CERT-JP RSS Processor Design
Fetches advisory feeds from the Japanese Vulnerability Notes Database (JVN/JVNDB) published by CERT-JP (IPA / JPCERT/CC). Per-year historical feeds cover 2002–present. Both the English and Japanese...
CERT-LV RSS Processor — Design
Fetches vulnerability advisories from the Latvian national CERT (CERT-LV / cert.lv) RSS feed. The feed republishes CERT/CC VU# notes and CISA alerts with English-language HTML descriptions containi...
cert-pt-rss-processor Design
Fetches security advisories from the Portuguese national cybersecurity centre (CNCS — Centro Nacional de Cibersegurança / CERT-PT) RSS feed and creates
CERT-SE RSS Processor — Design
Fetches vulnerability advisories from Sweden's national CERT (CERT-SE / cert.se) RSS 2.0 feed. CERT-SE is Sweden's national computer security incident response team, operated by the Swedish Post an...
CERT-TW Fetch Processor Design
Fetches Taiwan CERT (TWCERT/CC) vulnerability advisories from the paginated HTML listing at `https://www.twcert.org.tw/tw/lp-132-1-{page}-60.html`. For each new advisory, both the Chinese (TW) and...
epss-csv-backfill
Imports the historical FIRST EPSS daily score archive (one gzipped CSV per day, 2021-present) into the EpssScore time-series table, resuming from the newest date already in the database so repeat runs only load the delta.
CERT-TW RSS Processor — Design (RETIRED)
Fetches vulnerability advisories from the Taiwan CERT (TWCERT/CC) RSS 2.0 feed, scrapes each TW and EN advisory page, and stores records keyed by CVE ID (source=`cert-tw`). Items with no CVE IDs ar...
CERT-UA RSS Processor — Design
Fetches APT campaign reports and vulnerability advisories from the Ukrainian national CERT (CERT-UA / cert.gov.ua) RSS feed. Advisory text is narrative Ukrainian prose; CVE IDs, CVSS vectors, and r...
CERT-US (CISA) RSS Processor — Design
Fetches cybersecurity advisories from the CISA (Cybersecurity and Infrastructure Security Agency) RSS 2.0 feed and creates `CVEMetadata` rows (source=`cert-us`) under original CVE IDs.
CERT BUND CSAF Processor
Walks the BSI (CERT-BUND) CSAF 2.0 index at wid.cert-bund.de, fetches every WID-SEC advisory with 10 concurrent workers, and writes one CVEMetadata row per CVE listed in each advisory — the German-language advisory corpus plus its vendor/product coverage.
CERT-CC JSON Processor Design
Imports CERT/CC vulnerability notes (VU#nnnnnn) from the kb.cert.org month-index JSON API, mints a CERTCC-{year}-{id} identifier plus its GCVE-110 issuance, and preserves CERT/CC's own CAM, VRDA and temporal/environmental CVSS assessments that no other feed publishes.
CERT-FR Processor — Design Document
Fetches CERT-FR (French national CERT, ssi.gouv.fr) security advisories from the public alerte and avis JSON feeds. Parses full advisory content — including French descriptions, affected systems, a...
CIRCL Vulnerability Lookup Processor — Design Document
Database-driven lookup against the CIRCL Vulnerability Lookup API: enriches CVEs we already hold with CIRCL's own v5.1 record, materialises every cross-referenced (linked) advisory document under source=circl, and records community sightings as Exploit rows.
CISA KEV Processor — Design Document
Fetches the CISA Known Exploited Vulnerabilities catalog in full on every run and batch-upserts every entry into the Kev table under source='CISA', gated by an elapsed-time freshness check on BulkDataDumpTracker.
Cisco CVRF Processor — Design Document
The Cisco CVRF Processor ingests Cisco security advisories published in
cisco-cvrf-backfill
On-demand local twin of cisco-cvrf-processor: walks the entire Cisco Security Center advisory list, ignores the listing-page tracker, and re-imports every CVRF advisory in batches — the tool used to seed or repair the cisco source.
cleanstart-git-processor
Pulls the CleanStart security-advisory git repository at runtime and processes its OSV JSON advisories into CVEMetadata (source=cleanstart) so CleanStart's hardened container images are covered by the same CVE graph as every other distro.
Cloudflare Advisories Processor — Design Document
Ingests Cloudflare's published security advisories from three complementary public surfaces and writes one `CVEMetadata` row per advisory under
cnvd-git-processor
Clones the CIRCL CNVD-Dump mirror at runtime and imports China's CNVD vulnerability records as CVEMetadata (source=cnvd), keyed on the CNVD identifier with CVE cross-references kept as alias edges.
community-snort-processor
Harvests CVE-bearing Snort rules from community GitHub rulesets (travisbgreen hunting-rules, thereisnotime/Snort-Rules) and turns each one into a SnortRule + Exploit record joined to the CVEs it detects, so a vulnerability can answer "is there a network signature for this?".
community-suricata-snort-processor
Harvests CVE- and CNVD-bearing Suricata rules from community GitHub feeds (quadrantsec, daffainfo per-year CVE and CNVD rulesets) into SnortRule + Exploit records, extending network-detection coverage to Chinese CNVD advisories that no Snort feed indexes.
confluent-json-processor
Ingests Confluent Security Advisories (CONFSA) from the public Zendesk Help Center JSON API at support.confluent.io, emits CVEMetadata under source="confluent" keyed on the referenced CVE, and stages per-product CRIT envelopes (Cloud / Platform / ksqlDB / Schema Registry / Connect).
crit-emit-post-processor — Design (RETIRED)
Phase 6 of the CRIT pipeline: re-read every envelope still sitting in crit-candidates/pending/ and correlated CISA-KEV, VulnCheck-KEV, EPSS and CrowdSec onto it, attaching remediation deadlines before a human reviewer ever saw the queue.
Design: CRIT Publisher
Drains CRIT candidate envelopes staged at `s3://{bucket}/crit-candidates/pending/...` by every CRIT-emitting processor — runs the publish-time validation suite, upserts the `CritRecord` row with ne...
Design: CRIT Inference Processor
Derives CRIT records for advisories the deterministic mappers could not attribute — dictionary-grounded model inference with a validation gate, one corrective retry, and discard-on-failure.
CrowdSec Processor — Design Document
Ingests the two public CrowdSec free feeds, then spends the daily CrowdSec CTI API quota on the highest-priority CVEs, writing honeypot IP sightings to CrowdSecSighting and one CrowdSecLog row per attempt.
Design: Exploit Defence Processor
Turns ingested PoC exploits into deployable defences — a triage stage that decides where an exploit can be caught, an authoring stage that writes only the formats that answer admits, and a validator with a corrective retry.
CVE Prefix JSON Processor — Design Document
On startup, `LoadDistinctPrefixes` queries `CVEMetadata` for all distinct `SPLIT_PART('cveId', '-', 1)` values excluding the Vulnetix-minted prefixes, ordered by distinct `cveId` count descending. The `--prefix` flag fil...
cvelistv5-json-backfill
Local-only, on-demand historical import of a cvelistV5 git clone (CVE Program record repository) into CVEMetadata under source="cve.org", with DB-driven per-file resume and a local statefile checkpoint.
cxsecurity-rss-processor
Fetches CXSecurity WLB exploit, dork, and advisory RSS feeds, downloads each item's ASCII page, and stores exploit PoCs as Exploit rows and advisory-only items as CVEMetadata under source=cxsecurity.
Design: Exploit CVE Relink Processor
Repairs advisory linkage on Exploit rows that carry none — retries the junction write ingest dropped, recovers identifiers from cross-references, and queues the remainder for human review.
databricks-fetch-processor
Discovers Databricks KB security bulletins via kb.databricks.com/sitemap.xml and ingests per-CVE records with CVSS v3.1, affected versions, and remediation metadata.
digitalocean-fetch-processor
Scrapes the DigitalOcean security-topic blog and emits CVEMetadataReferences (type=third-party-analysis) for existing CVEs, with optional CRIT staging when posts name a DO product (Droplet, Managed K8s, App Platform, Spaces, Managed Databases).
Docker Hardened Images Processor
Docker publishes a public advisory repository at `https://github.com/docker-hardened-images/advisories.git` containing two distinct data types, each processed by a dedicated binary:
dropbox-fetch-processor — Design (DEFERRED)
No CVE-tagged feed exists; Intigriti BBP/VDP is structurally no-disclosure; dropbox.tech RSS is CVE-free; Dropbox-assigned CVEs already land in NVD/MITRE.
drupal-git-processor
Pulls the Drupal advisory database git repository at runtime and processes its OSV JSON advisories (DRUPAL-SA-*) into CVEMetadata (source=drupal), giving Drupal core and contrib modules first-party coverage keyed to the Packagist package that ships the fix.
tenable-rss-processor
Ingests Tenable's own CVE analysis from its updated and newest RSS feeds as first-class TNCVE- records, with the original CVE id linked as an alias so a CVE lookup surfaces Tenable's assessment alongside the CNA's.
elastic-rss-processor
Ingests Elastic Security Announcements (ESA-YYYY-NN) from the public Discourse RSS feed, keys each advisory on its referenced CVE, and stages dual CRIT envelopes for Elastic Cloud SaaS and the self-managed Stack.
Emerging Threats Snort Processor — Design
Fetches Snort IDS rules from the Emerging Threats open ruleset, parses CVE references, and creates Exploit records (source=`emergingthreats`) linked to CVEMetadata records (source=`vulnetix`). The...
EUVD Processor — Design Document
There are two binaries in this design:
FSTEC BDU Git Processor Design
Imports vulnerabilities from the [FSTEC BDU](https://bdu.fstec.ru/) (Bank of Data on Threats and Vulnerabilities) maintained by Russia's Federal Service for Technical and Export Control. Records ar...
GCP Security Bulletins Processor — Design Document
Single Go binary that ingests Google Cloud security bulletins from three public Atom feeds and writes one `CVEMetadata` row per bulletin under
GCVE JSON Processor — Design Document
On each run the processor compares the current `GcveIssuance` row set against the previous manifest:
gemnasium-git-processor
Clones GitLab's gemnasium-db at runtime and stores each advisory twice: a provenance-only CVEMetadata row under source=gitlab, and the full advisory (descriptions, metrics, affected ranges, references) under the source implied by the advisory's own identifier prefix.
Gentoo Bugzilla Security RSS Processor — Design
Fetches security vulnerability reports from the Gentoo Bugzilla Atom feed and creates first-class `CVEMetadata` rows (source=`gentoo`) with minted
variot-json-backfill
Local-only historical import of VARIoT IoT vulnerability and exploit data, walking backwards year by year to ~2002 and stopping after two consecutive empty years — the only path to VARIoT's archive, since the scheduled processor reads a 3-day window.
ghsa-git-processor
Clones the GitHub Advisory Database at runtime and processes every GHSA-*.json advisory into CVEMetadata under source=github, keeping the GHSA identifier as the row key so the GHSA namespace stays first-class and CVE identifiers become alias edges.
GHSA RSS (Atom) Advisory Processor
Fetches the GitHub Security Advisory Atom feed hourly and seeds CVEMetadata records for new advisories. Complements the daily `ghsa-git-processor` by providing near-real-time visibility into newly...
GHSA PoC Generation Activity — Design Document
carries at least one `type='patch'` GitHub-commit reference, generate a Markdown Proof-of-Concept (PoC) that triggers the bug **before** the patch is applied. The same PoC also serves as a regressi...
GitHub PoC Processor — design
PoC discovery used to live in `saas/src/services/vdb/vulnProcessor.ts`:
Tree-sitter Query Generation Activity — Design Document
From CVE/OSV descriptions and advisory references, emit language-specific tree-sitter queries (S-expressions with captures and predicates) that match the vulnerable code pattern.
gitlab-json-processor
Ingests GitLab's first-party CVE database (gitlab.com/gitlab-org/cves) — CVE 5.0 JSONs in one tarball — into CVEMetadata under source="gitlab", and stages dual SaaS / self-managed CRIT envelopes.
GPZ 0day ITW Google Sheets Processor — Design Document
1. Fetch CSV from Google Sheets 2. Compute SHA256 hash of response body 3. Compare against `BulkDataDumpTracker.sha256` for `google_project_zero_0day_itw` 4. If match → exit with 'data unchanged' 5...
HackerOne Hacktivity Processor — Design Document
Fetches publicly disclosed HackerOne reports via the unauthenticated GraphQL API and stores each one as a BugBountySubmission, an Exploit (category bug-bounty), and an H1-<reportId> CVEMetadata record, aliasing any cve_ids the report carries.
hashicorp-discuss-fetch-processor — Design
Ingests HashiCorp security advisories (HCSEC-YYYY-NN) from the discuss.hashicorp.com security category via the Discourse JSON API, keeping the HCSEC id as the primary identifier and linking embedded CVEs as aliases.
hetzner-rss-processor — Design (DEFERRED)
and ISO 27001 marketing pages — no public channel emits CVE-prefixed advisories, no RSS, no JSON, and the help-center pages cover abuse/phishing reporting policy rather than vulnerabilities.
IBM Security Bulletins JSON Processor — Design Document
Ingests security advisories from the IBM Support public JSON API and writes one `CVEMetadata` row per CVE ID found in each bulletin under `source = 'ibm'`.
ISC Advisory RSS Processor — Design
Fetches ISC (Internet Systems Consortium) security advisories from the public RSS 2.0 feed (`https://kb.isc.org/v1/rss/en`) and creates first-class
Design: Linode (Superseded by Akamai)
Linode-branded PSIRT was consolidated under Akamai post-acquisition; Linode advisories are ingested by akamai-fetch-processor and tagged with affectedProduct=Linode. No standalone Linode binary exists.
Mageia JSON Processor Design
1. **Fetch Indexes** — GET `vulns.json` (~1057 MGASA entries) and `bugs.json` (~1889 MGAA entries) 2. **Deduplicate** — Merge indexes, remove duplicates by ID 3. **Filter** — Daily: only entries mo...
summary-processor
Computes the whole-corpus statistics the public VDB API and website render — coverage counts, CVSS/EPSS/KEV rollups, exploit and vendor trends, package and malware aggregates — and writes each as a SummaryLog row so no consumer ever aggregates the corpus itself.
MISP Galaxy JSON Processor — design
The MISP Project publishes a public threat-actor cluster at
MITRE CVE Processor — Design Document
The HTTP response body is streamed through a `io.TeeReader` that simultaneously: 1. Feeds bytes to `gzip.NewReader` → `tar.NewReader` for extraction 2. Hashes the entire stream with `sha256.New` to...
mongodb-rss-processor — Design
graph LR Source[RSS Feed] --> HTTP[httpclient.NewHTTP1] --> Parse[mongodb.ParseFeed] Parse --> Item[mongodb.ParseItem] --> Map[mongodb.MapToSourceData] Map --> Tx[(pgx Tx)] --> Pipeline[processor.S...
MSRC CSAF Processor — Design Document
Microsoft publishes **one CSAF file per CVE identifier**, unlike other vendors that publish one file per advisory bulletin.
NCSC-FI RSS Processor — Design
Fetches vulnerability advisories from the Finnish National Cyber Security Centre (NCSC-FI / Kyberturvallisuuskeskus) RSS feeds and creates `CVEMetadata` rows (source=`ncsc-fi`).
NCSC-NL CSAF Processor
Fetches every CSAF 2.0 advisory listed in the NCSC-NL (Dutch National Cyber Security Centre) index.txt and writes one advisory-keyed CVEMetadata row per bulletin (NCSC-YYYY-N, source=ncsc-nl), with the constituent CVE IDs written as CVEAlias edges and a GCVE-110 issuance minted for each advisory.
NIST NVD Modified Processor — Design Document
Fetches CVEs last-modified since the resume tracker (with a 1-hour overlap, capped at a 7-day lookback) from the NVD REST API 2.0, stores them directly to CVEMetadata in a two-phase ingest-then-enrich loop, and advances the tracker only when the full window was ingested.
NIST NVD Recent Processor — Design Document
Fetches CVEs published in the last ~8 days from the NVD REST API 2.0, stores them directly to CVEMetadata in a two-phase ingest-then-enrich loop, and advances a resume tracker only when the full window was ingested.
Nozomi Networks PSIRT RSS Processor — Design
Fetches Nozomi Networks PSIRT security advisories from the public RSS 2.0 feed (`https://security.nozominetworks.com/rss.xml`) and creates first-class `CVEMetadata` rows (source=`nozomi`) for every...
ocaml-git-processor
Pulls the OCaml security-advisories repository at runtime and parses its custom Markdown DSL (OSEC-*) into CVEMetadata (source=ocaml) plus Exploit/ExploitCVE records for advisories carrying PoC-language code blocks.
Open Cloud Vulnerability DB RSS Processor — Design (RETIRED)
Fetched cloud-specific vulnerability advisories from the Open Cloud Vulnerability Database RSS 2.0 feed (`https://www.cloudvulndb.org/rss/feed.xml`) and created first-class `CVEMetadata` rows (sour...
Design: Oracle CPU CSAF Processor
Discovers Oracle Critical Patch Update (CPU) bulletins (quarterly: Jan/Apr/Jul/Oct), fetches each CPU's CSAF 2.0 JSON document plus per-bug HTML page for free-text enrichment, and writes one `CVEMe...
OSM Malicious Package Processor — Design
Fetches malicious package threat intelligence from the OpenSourceMalware.com API (`https://api.opensourcemalware.com/functions/v1/query-latest`) and creates
oss-malware-git-processor
Clones the OpenSSF malicious-packages repository at runtime and imports every MAL-* OSV advisory as a malicious-package CVEMetadata record (source=oss-malicious-packages), then attributes a bounded batch of them to threat actors via the shared actorintel engine.
OSS-Fuzz Advisory Processor — Design Document
OSS-Fuzz uses the identical shared pipeline as PyPI processor (`internal/processor`). The only differences are constants: source=`ossfuzz`, trackerSource=`ossfuzz_advisory`, referenceSource=`OSS-Fu...
OSV File Processor — Design Document
OSV advisories carry an OSV-native `id` (e.g. `MINI-jhg8-3g7g-q6mp`, `GHSA-xxxx-xxxx-xxxx`, `PYSEC-2024-1234`, `RUSTSEC-2024-0001`) and may list `aliases[]` containing CVE-* identifiers. This proce...
Repo Scan Git Processor — Design Document
Discovers git repositories referenced anywhere in the VDB and runs the Vulnetix CLI (analyze/cdx/cbom/aibom) against a single ephemeral clone per repo, once per owning tenant org, authenticating the CLI per org via Authentik + KMS.
ovhcloud-json-processor
Walks OVHcloud's six Statuspage.io sub-instances, filters incidents whose name or update bodies mention CVE-* identifiers, and emits CVEMetadataReferences rows linking existing CVE records to OVHcloud incident shortlinks. Optional CRIT staging when an incident names an OVHcloud managed product.
Open-Xchange CSAF Processor
Fetches every CSAF 2.0 advisory listed in the Open-Xchange App Suite index.txt and writes one CVE-keyed CVEMetadata row per vulnerability (source=open-xchange), archiving each raw advisory to S3.
pwno-fetch-processor — Design
Daily ingest of [Pwno's](https://bugs.pwno.io/) public bug-disclosure index into `CVEMetadata` under the `pwno` source namespace. Pwno is the AI-driven security research startup founded by Ruikai P...
PyPI Advisory Processor — Design Document
Uses `processor.PullOrClone` which attempts `git pull --ff-only` on the existing clone at `/data/advisory-database`. If the pull succeeds, returns the new HEAD SHA. On pull failure the process exit...
Red Hat CSAF Processor — Design Document
Red Hat publishes **one CSAF file per RHSA** (multi-CVE bundle) in the `advisories/` tree, and **one VEX file per CVE** (portfolio-wide per-product disposition) in the `vex/` tree. The archive zip...
Red Hat Security Errata (RHSA) RSS Processor — Design
Fetches Red Hat Security Errata advisories from the public RSS 2.0 feed (`https://security.access.redhat.com/data/meta/v1/rhsa.rss`) and creates first-class
Design: Salesforce Advisories RSS Processor
Fetches Salesforce security advisories from the master RSS feed at `security.salesforce.com/security-advisories/rss`, parses CVE/GHSA aliases and bracketed product lists from each item, stores per-...
Design: SAP Patch Day Fetch Processor
Scrapes the public SAP Security Patch Day archive pages on
Design: ServiceNow KB Fetch Processor
Fetches ServiceNow PSIRT (Product Security Incident Response Team) advisories from the master 'ServiceNow Common Vulnerabilities & Exposures (CVE) Security Advisories' KB landing page (KB1226057) p...
Shadowserver Dashboard Processor — Design Document
No API keys.
SICK PSIRT RSS Processor — Design
Fetches SICK AG PSIRT security advisories from the public Atom 1.0 feed (`https://tools.sick.com/rss/psirt/advisories.atom`) and creates first-class `CVEMetadata` rows (source=`sick-psirt`) for eve...
Design: Siemens CSAF Processor
Fetches Siemens ProductCERT security advisories published in **CSAF 2.0** (Common Security Advisory Framework) JSON format from the public TLP:WHITE feed index and stores the parsed vulnerability d...
Design: Akamai Fetch Processor
Fetches Akamai PSIRT advisories published as blog posts at www.akamai.com/blog/security[-research]/advisory-cve-* using a uTLS Chrome ClientHello to defeat Akamai Bot Manager. Operates over a seed list of slugs maintained in-tree; grows by extracting related-advisory hrefs from each fetched page.
slack-fetch-processor — Design (RETARGET — do not build)
ingested; no Slack-the-SaaS-platform CVE feed exists.
snowflake-fetch-processor — Design
graph LR Source[Snowflake HTML Listing] --> HTTP[httpclient.New] --> ParseListing[Regex parse CVE links] ParseListing --> ForEach[For each CVE URL] ForEach --> FetchDetail[HTTP fetch detail page] F...
Snyk Fetch Processor — Design
Scrapes the Snyk vulnerability database (security.snyk.io) to ingest SNYK-prefixed vulnerability records. Listing pages are paginated to discover identifiers, then individual detail pages are fetch...
SUSE CSAF Processor
Fetches every CSAF 2.0 advisory in the SUSE index.txt, correlates each CVE with its SUSE VEX document, archives both to S3, and writes CVE-keyed CVEMetadata rows (source=suse).
tailscale-rss-processor — Design
curl -sS https://tailscale.com/security-bulletins/index.xml | grep -oE 'CVE-[0-9]{4}-[0-9]{3,7}' | sort -u
tencent-blade-fetch-processor — Design
Machine. The original portal at `blade.tencent.com` is geo-blocked (verified live: connections from ap-southeast-2 to the main domain hang). The Wayback Machine has indexed the English advisory pag...
tencent-cloud-fetch-processor — Design (IMPLEMENTABLE)
reachable via paginated server-rendered HTML, no anti-bot
twilio-fetch-processor — Design
graph LR TC[Trust Center HTML] --> HTTP1[httpclient.SetBrowserHeaders] RSS[Changelog RSS XML] --> HTTP2[httpclient.SetBrowserHeaders] HTTP1 --> ParseTC[twilio.ParseTrustCenterHTML] HTTP2 --> ParseR...
VARIoT JSON Processor — Design
Fetches IoT vulnerability and exploit records from the VARIoT JSON API (variotdbs.pl). Vulnerabilities are stored in `CVEMetadata` + relations. Exploits are stored in `Exploit` + `ExploitCVE` + `Ex...
vercel-security-bulletins-fetch-processor — Design
graph LR Source[Vercel KB /bulletin] --> HTTP[httpclient.New] --> Parse[regex extract slugs] Parse --> PerSlug[per-slug: fetch + parse HTML] PerSlug --> Map[map to CVEMetadata] Map --> Tx[(pgx Tx)]...
vulncheck-kev-json-processor
Pages VulnCheck's commercial KEV index (/v3/index/vulncheck-kev) and upserts every Known Exploited Vulnerability record plus its CVE, CWE, XDB exploit and reported-exploitation relations. VulnCheck KEV is several times the size of the CISA catalog and is the source of the exploited-in-the-wild signal for CVEs CISA never lists.
vulncheck-nvd-json-processor
Downloads the VulnCheck NVD2 backup ZIP, walks its inner .json.gz chunks, and stores every changed CVE record directly under source vulncheck-nvd with a three-tier resume (archive SHA256, per-file SHA256, per-CVE sourceFileHash), an S3 archive per record, and AI enrichment.
Vulnetix KEV Processor — Design Document
No API keys.
CISA Vulnrichment Git Processor
Processes [CISA Vulnrichment](https://github.com/cisagov/vulnrichment) CVE Record Format v5 JSON files into CVEMetadata and related tables. The vulnrichment repository contains ~136K files with bot...
vultr-fetch-processor — Design (RETARGETED — commentary enricher, low priority)
vulnerability response article (Spectre/Meltdown, VENOM) that references third-party CVE IDs, but no first-party advisory feed exists and historical volume is <5 posts/year.
wiz-json-processor
Enrichment processor: finds CVE/GHSA ids with no source='wiz' counterpart and pulls Wiz.io's curated vulnerability-database record for each (description, CVSS base score, affected software per source feed, advisory URL), optionally staging CRIT cloud-resource candidates.
Wiz Security Open CVDB Git Processor — Design
Processes YAML cloud vulnerability advisories from the Wiz Security Open Cloud Vulnerability Database (`github.com/wiz-sec/open-cvdb`) and creates or enriches
workday-fetch-processor — Design (DEFERRED)
enterprise-gated; vulnerability disclosures only reach authenticated customers via Workday Community.
ZDI Advisory Processor Design
Zero Day Initiative (ZDI/Trend Micro) publishes per-year RSS advisory feeds at
zoom-fetch-processor — Design
per-bulletin `/ZSB-YYNNN/` detail pages, emit CVEMetadata rows under
NVD Deep-Dive Processor — Design Document
Daily aggregation that counts published CVE records missing each of ten enrichment fields (CVSS, CPE, CISA ADP container, affected versions shape, packageName, programRoutines/modules/programFiles, repo, platforms, configurations/workarounds/solutions). Output is one `SummaryLog` row (label `nvd_deep_dive`) consumed by vdb-api `GET /v2/nvd-deep-dive` and the website's `/articles/just-patch` NVD deep-dive section.
nuget-json-processor
Walks the NuGet v3 catalog (a chronological event log) incrementally by catalog-page commitTimeStamp watermark, records each changed package version as a PackageVersion, and mints malware advisories for packages with factual malicious evidence in their PowerShell install hooks, MSBuild targets/props, or bundled assemblies via the shared malscan-engine.
hex-json-processor
Walks the Hex.pm recent-packages feed (incremental by an updated_at watermark) for the Elixir/Erlang BEAM ecosystem, records each package's latest stable version as a PackageVersion, and mints malware advisories for packages with factual malicious evidence in their mix.exs compile-time build manifest or published release source tree via the shared malscan-engine.
julia-git-processor
Clones the Julia General registry git repo, walks the package directories whose Package.toml/Versions.toml changed since the watermark commit, records each newest registered version as a PackageVersion, and mints malware advisories for packages with factual malicious evidence in their Pkg.build execution vector (deps/build.jl) via the shared malscan-engine.
Malscan STIX Processor
Republishes the malware pipeline's C2/exfil indicators as public per-ecosystem STIX 2.1 feeds (dns + urls kinds) to S3, each with a .sha256 and an index.json manifest, so the detection engine can match future packages against infrastructure burned in past attacks.
rustsec-git-processor
Walks the RustSec advisory-db git clone (Markdown files with TOML frontmatter under crates/ and rust/), stores each advisory as a CVEMetadata record under source='rustsec' with crates.io affected ranges, and promotes any proof-of-concept code block in the advisory prose into an Exploit record linked to every CVE the advisory names.
alpine-json-processor
Fetches the upstream Alpine Linux secdb JSON tree (every released branch plus edge, main and community) and writes ADP-container CVEAffected/CVEAffectedVersion rows plus docker-hub-alpine ContainerOriginAdvisory attribution against whichever source already owns each CVE.
0day-today-fetch-backfill
Local-only backfill that turns a clone of the 0day.today exploit archive into first-class Exploit records — body content archived to S3, CVE junctions resolved against every source that knows the CVE, and affected vendor/product/version extracted from four different advisory header conventions.
GitHub Yara Rules Fetcher — Design
Searches GitHub for YARA detection rules referencing CVEs (sharded by year × prefix × extension to bypass the 1000-result Code Search cap) and stores them as first-class YaraRule + YaraRuleCVE rows.
HashiCorp HCSEC RSS Processor — Design
Ingests HashiCorp Security Bulletins (HCSEC) from the public Discourse RSS feed and emits CVEMetadata rows under source="hashicorp" with CVE aliases and dual HCP+Enterprise CRIT envelopes per affected product.
rocky-linux-json-processor
Fetches Rocky Linux security advisories from the Apollo Errata Management API (apollo.build.resf.org) through two feeds — the OSV feed under source rocky_linux_osv and the native RLSA advisory feed under source rocky_linux_advisory — and stores CVE-linked records with RPM NEVRA package lists and Bugzilla fix references.
Fastly Fetch Processor — Design
Scrapes the Fastly Security Advisories HTML page (fastly.com/security-advisories) and links existing CVE records to Fastly commentary posts, optionally staging not_affected CRIT candidates.
zombie-sweeper-json-processor
Operational sweeper: scans every /ecs/vdb-scheduler/* log group for task log streams that have gone quiet (idle > 6 h) or that started before today's UTC midnight, confirms via ecs:DescribeTasks that the task is still RUNNING, and only then calls ecs:StopTask and publishes a task.zombie_detected SNS event. Writes no database rows.
EOL Vendor Fetch Processor — Design
Scrapes vendor product-lifecycle pages not carried by endoflife.date (Virtuozzo, Scientific Linux, …) and upserts EolProduct/EolRelease/EolIdentifier rows via the shared eol.UpsertProduct path.
eol-json-processor
Fetches the endoflife.date full product list (https://endoflife.date/api/v1/products/full) and upserts EolProduct/EolRelease/EolIdentifier lifecycle rows.
eukev-json-processor
Fetches the ENISA EU Known Exploited Vulnerabilities (EU KEV) catalog and batch-upserts records into the Kev table with source='enisa'.
hsec-git-processor
Pulls the Haskell security-advisories repository at runtime and parses its Markdown + TOML-frontmatter advisories (HSEC-*) into CVEMetadata (source=haskell), minting one row per affected package, issuing GCVE identifiers for the HSEC namespace, and recording PoC-language code blocks as Exploit records.
nist-nvd-year-json-processor
Ingests one calendar year of the NIST NVD 2.0 bulk archive (nvdcve-2.0-{year}.json.gz), gated on the feed's published SHA256 so an unchanged year is a no-op. On-demand only — no EventBridge schedule.
ai-models-processor
Reconciles every globally-enabled AiProvider's OpenAI-compatible /v1/models listing into the AiModel catalog that the AI Firewall gateway resolves per-org allow/deny policy against, stamping releasedAt once on first sight, flipping isActive when a provider delists a model, and recording every field change in AiModelChangeLog.
ai-discovered-vulns-runner
Operator-only helper that recomputes just the ai_discovered_vulns_outcomes SummaryLog row — the exploit-intelligence outcome panel behind the AI-discovered-vulnerabilities article — without waiting for the whole daily summary-processor run.
malwarehost-backfill
Local one-shot that projects host IOCs (domains, IPv4, IPv6, onion) out of OsmThreatIoc and MalwareIoc into the unified MalwareHost / MalwareHostLink store with precomputed STIX 2.1 indicator ids and patterns. Idempotent; the same sync also runs inside malscan-stix-processor.
malware-actor-backfill
Local-only, deadline-free tool that attributes malware authors (threat actors) for the whole backlog of malicious CVEs across every non-OSM malware source, writing the generic MalwareThreatActor / ThreatActor / MalwareAttribution tables. The comprehensive net behind the per-processor inline post-pass.
Go Processor Blueprint
End-to-end recipe for adding a new `{source}-{type}-processor` to the Vulnetix VDB pipeline. Every section maps to concrete files in this repo and to schemas/migrations in `../saas/`. **The process...
snowflake-fetch-processor — Implementation Plan
Build a `fetch` type processor to ingest Snowflake Security Bulletins from