GHSA RSS (Atom) Advisory Processor

Overview

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 published GHSA advisories.

Uses source="github" (same as the git processor) so that the git processor’s daily run upserts full OSV data over the lightweight records created here.

Feed

  • URL: https://github.com/security-advisories.atom
  • Format: Atom 1.0 (xmlns="http://www.w3.org/2005/Atom")
  • Size: ~70 most recent entries (no pagination)
  • Auth: None required (public feed)

Entry Structure

<entry>
  <id>tag:github.com,2008:GHSA-xxxx-xxxx-xxxx</id>
  <published>2026-03-23T04:05:36Z</published>
  <updated>2026-03-23T04:05:36Z</updated>
  <title type="html">[GHSA-xxxx-xxxx-xxxx] Summary text</title>
  <category term="NPM"/>
  <content type="html">
    <!-- HTML-encoded advisory body containing:
         - Description paragraph(s)
         - Affected Packages (<dl> with <dt>/<dd>)
         - References (<ul>)
         - CWEs (<ul>)
         - CVSS vector and score -->
  </content>
</entry>

HTML Content Fields

FieldHTML PatternExample
DescriptionFirst <p> before “Affected Packages”Advisory summary text
Package name<dt>proxy.py, uipathisfun
Ecosystem<dd>Ecosystem: X</dd>pip, npm, maven
Severity<dd>Severity: X</dd>critical, high, moderate, low
Versions<dd>Versions: X</dd>< 2.3.1, >= 0
Fixed in<dd>Fixed in: X</dd>2.3.1 (optional)
References<li> under “References”NVD, GitHub advisory URLs
CWEs<li>CWE-NNN - Description</li>CWE-506, CWE-287
CVSS vectorAfter <strong>CVSS:</strong>CVSS:4.0/AV:N/...
CVSS scoreAfter <strong>CVSS score:</strong>8.7

Package Layout

cmd/ghsa-rss-processor/
  main.go          — single-file processor (Atom fetch, HTML parse, DB store)

No internal package needed — the HTML parsing is specific to this feed format and not reusable. Shared DB operations come from internal/db/.

Flags

FlagTypeDefaultDescription
--forceboolfalseReprocess all entries even if hash unchanged
--limitint0Max entries to process (0 = unlimited)

Processing Flow

  1. Connect to DB (read + write pools)
  2. Check concurrency lock (BulkDataDumpTracker key ghsa_rss)
  3. Acquire lock; defer release
  4. Fetch Atom feed with retry (3 attempts)
  5. Load resume set (sourceFileHash per CVE for source=“github”)
  6. For each <entry>:
    • Extract GHSA ID from <id> tag
    • Parse HTML content for all fields
    • Extract CVE IDs via regex
    • Select primary ID: first CVE ID found, else GHSA ID
    • Detect malware: “Malware in” title or CWE-506
    • Compute hash: SHA1(ghsaID|updated|content)
    • Skip if hash matches and --force not set
    • Store in transaction with retry:
      • UpsertCVEMetadata (source=“github”)
      • UpdateSourceFileHash
      • InsertDescriptions
      • InsertReferences
      • InsertMetrics (CVSS)
      • InsertProblemTypes (CWEs)
      • UpsertAffected + InsertVersions (per package)
      • InsertAliases (GHSA ↔ CVE)
      • EnrichAffectedWithDependency
  7. Release lock

DB Impact

Tables written (all existing — no migrations needed):

  • CVEMetadata — primary record (cveId, source=“github”)
  • CVEMetadataReferences — advisory and reference URLs
  • CVEDescription — advisory description text
  • CVEMetric — CVSS vector/score
  • CVEProblemType — CWE entries
  • CVEAffected — affected packages
  • CVEAffectedVersion — version ranges
  • CVEAlias — GHSA ↔ CVE linkage
  • BulkDataDumpTracker — concurrency lock
  • Dependency, PackageVersion, PackageVersionCVE — enrichment

Upsert compatibility: Records use source="github" and the same table schemas as ghsa-git-processor. The git processor’s daily upsert will overwrite all fields with full OSV data, preserving the primary key.

Malware Detection

Two signals from the Atom feed:

  1. Title pattern: contains “Malware in” (case-insensitive)
  2. CWE-506: Embedded Malicious Code

Either signal sets isMaliciousPackage = true on the CVEMetadata record.

Malware Threat-Actor Enrichment

When an entry is flagged malicious — a “Malware in” title, CWE-506 (Embedded Malicious Code), or a MAL-* alias — the record qualifies for threat-actor attribution under source='github'. After the advisory’s store transaction commits, a first-time-only post-commit pass invokes the shared attribution engine (internal/actorintel), which resolves the malware author from package/repo identity plus upstream registry, GitHub, and Docker Hub lookups. Results land in the generic, shared MalwareThreatActor edge (cveId, cveSource='github'ThreatActor) and MalwareAttribution status tables — not the OSM-only OsmThreat* tables. A (cveId, source) already carrying a MalwareAttribution row is never re-enriched.

AspectBehaviour
Detection rule“Malware in” title, CWE-506, or a MAL-* alias
Attribution tablesMalwareThreatActor, MalwareAttribution, shared ThreatActor + ThreatActorKey
Attribution basisrepo owner / container namespace / registry maintainer / Go-module repo / commit author, per the engine’s identity heuristics
Hijack handlingcompromised-account advisories mark the maintainer as hijack-victim-excluded (the victim is NOT attributed)
Impersonation trapfor non-Go packages, a declared repository is often the dependency-confusion / typosquat target (e.g. a legit org’s repo) — captured as claimedRepo*, NEVER attributed as an actor
GitHub key harvestpublic SSH-auth, SSH-signing, and GPG keys → ThreatActorKey (OpenSSH SHA256 fingerprint / GPG key id); a reused fingerprint links operators across accounts

The existing backlog of malicious records is cleared by the one-time cmd/malware-actor-backfill (just go-malware-actor-backfill), which selects isMaliciousPackage=true AND source<>'osm' across all malware sources.

Decisions

  • source=“github” rather than a separate source — enables seamless upsert by the git processor without creating duplicate records.
  • Atom XML parsing via encoding/xml — the feed is well-structured Atom 1.0; no external library needed.
  • HTML parsing via strings/regexp — the HTML content is machine-generated by GitHub with a consistent structure. A full HTML parser would be overkill.
  • Concurrency lock — prevents overlapping hourly runs via BulkDataDumpTracker (same pattern as enrichment tasks).
  • 256 CPU / 512 MB — the feed is small (~70 entries) and processing is lightweight (no git clone, no large JSON parsing).

AI Enrichment (aienrich)

After each advisory’s transaction commits successfully, the processor fires a single-target aienrich.RunBatch for the primary GHSA id. Wire-in: cmd/ghsa-rss-processor/main.go immediately after uploader.ArchiveRecord. The target carries CveID = primaryID, Source = "github", Aliases = adv.cveIDs.

Five passes fire (all gated by enricher.Enabled()):

PassPersists to
vulnetix.affectedCVEAffected.{modules, programFiles, programRoutines}
vulnetix.attackCVEAttackTechnique + children
vulnetix.cweCVEProblemType (descriptionType = "CWE", derivedBy = "vulnetix")
vulnetix.treesitterCVETreeSitterQuery + CVETreeSitterCapture + CVETreeSitterPredicate
vulnetix.ghsapocExploit (source=vulnetix) + Markdown PoC in S3

RunBatch is invoked outside the parent transaction. See the aienrich overview for the full activity contract, environment variables, and operator runbook.

S3 Persistence

Not used. This processor does not currently archive payloads or quarantine failures to S3. Per the S3 Persistence Contract this is non-compliant — see the compliance matrix for the implementation roadmap.

Expected paths when implemented:

  • Archive: ghsa/files/{sha256}/{filename}
  • Quarantine: failed-feeds/ghsa-rss-processor/{YYYY-MM-DD}/{reason}/{filename}
  • Likely reasons: parse-error