Snyk Fetch Processor — Design
Overview
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 fetched and parsed via the
embedded __NUXT_DATA__ JSON array.
Records are stored under SNYK identifiers (source=snyk). CVE and GHSA
identifiers found in each record are stored as aliases.
Feed
| Property | Value |
|---|---|
| URL | https://security.snyk.io/disclosed-vulnerabilities/{page} |
| Auth | None — fully public |
| Format | HTML with embedded Nuxt.js __NUXT_DATA__ JSON |
| Language | English |
| Items | 10 per listing page, thousands total |
| Note | Per-page enumeration; pages return SSR HTML with SNYK hrefs |
Page Structure
Listing Pages
Each listing page at /disclosed-vulnerabilities/{n} contains:
- Anchor tags with
href="/vuln/SNYK-*"throughout the SSR HTML (full-page scan, no container regex needed) - Empty pages (beyond last) return 200 OK with 0 SNYK hrefs, terminating pagination
Detail Pages
Each vulnerability page at /vuln/{SNYK-ID} contains:
<script id="__NUXT_DATA__">with a JSON array- Index 4 of the array is a field map:
{"title": 6, "description": 7, ...} - Indexed values resolve to other array positions holding actual data
- String values throughout the array contain CVE/GHSA aliases, CWE IDs, CVSS vectors, version ranges, and lesson URLs
Parsing
NUXT_DATA Field Map (index 4)
| Field | Usage |
|---|---|
id | SNYK identifier |
title | Vulnerability title |
description | Full description text |
severity | Severity label (low/medium/high/critical) |
packageName | Affected package name |
packageManager | Ecosystem (pip, npm, maven, etc.) |
publicationTime | ISO 8601 publication date |
disclosureTime | ISO 8601 disclosure date |
CVSSv3 | CVSS v3.1 or v4.0 vector string |
cvssScore | Numeric CVSS score |
language | Programming language |
exploitMaturity | Exploit maturity level |
malicious | Boolean malicious package indicator |
Array Scan (prefix-matched strings)
| Prefix/Pattern | Stored as |
|---|---|
CVE-YYYY-NNNNN | CVEAlias |
GHSA-xxxx-xxxx-xxxx | CVEAlias |
CWE-NNN | CVEProblemType |
CVSS:3.1/... | CVEMetric (cvssV3_1) |
CVSS:4.0/... | CVEMetric (cvssV4_0) |
https://learn.snyk.io/lesson/... | CVEMetadataReferences |
[version,version) | CVEAffectedVersion (bracket range) |
<version, <=version, >=version | CVEAffectedVersion (comparison operator) |
Storage
No new tables or columns. All tables already exist.
| Table | Rows inserted |
|---|---|
CVEMetadata | One per SNYK ID; source="snyk" |
CVEDescription | One per vuln; lang="en" |
CVEMetadataReferences | Snyk vuln URL + lesson URLs |
CVEMetric | CVSS v3.1/v4.0 vectors with scores |
CVEProblemType | CWE IDs from array scan |
CVEAffected | Package with ecosystem, version ranges |
CVEAlias | CVE-* and GHSA-* cross-references |
Malware Threat-Actor Enrichment
A Snyk record is treated as a malicious package when its __NUXT_DATA__
malicious boolean is set, or its title equals “Malicious Package”. Such
records (isMaliciousPackage=true) qualify for threat-actor attribution under
source='snyk'. After a record’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='snyk' →
ThreatActor) and MalwareAttribution status tables — not the OSM-only
OsmThreat* tables. A (cveId, source) already carrying a MalwareAttribution
row is never re-enriched.
| Aspect | Behaviour |
|---|---|
| Detection rule | malicious boolean set, or title == “Malicious Package” |
| Attribution tables | MalwareThreatActor, MalwareAttribution, shared ThreatActor + ThreatActorKey |
| Attribution basis | repo owner / container namespace / registry maintainer / Go-module repo / commit author, per the engine’s identity heuristics |
| Hijack handling | compromised-account advisories mark the maintainer as hijack-victim-excluded (the victim is NOT attributed) |
| Impersonation trap | for 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 harvest | public 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.
Incremental Strategy
On startup, load all cveId values from CVEMetadata where source='snyk'
into a map[string]bool. Per listing page: for each SNYK ID, skip if already
known and --all/--force is false. Because the skip set is checked before
the detail fetch, re-walking an already-ingested listing page costs one listing
request and no detail requests.
Page frontier resume. The highest listing page reached is persisted as
BulkDataDumpTracker.totalCVEs under source snyk_fetch_progress, and the next
run starts at frontier - 5 and paginates forward until a page yields no SNYK
hrefs (main.go:149-154).
⚠ Known defect. New Snyk disclosures appear at the front of
/disclosed-vulnerabilities(the code comment atmain.go:145-147acknowledges that “new vulns shift older pages forward”). Starting atfrontier - 5therefore skips pages1 … frontier-6, which is where all new content lands. Production evidence: the frontier row was last updated 2026-08-01, butmax(lastFetchedAt)acrosssource='snyk'rows is 2026-03-30 andmax(datePublished)is 2026-03-22 — no new record has been stored in four months of weekly runs. Until this is fixed, treat the Snyk corpus as frozen at 3,081 records.
Rate limit: 600-900ms random delay between detail page fetches.
Locking: Uses BulkDataDumpTracker with key snyk_fetch_lock plus a 5-minute
heartbeat to prevent concurrent instances. enrichment.CheckLock treats a lock
row touched within the last 8 minutes as held.
Flags
| Flag | Default | Description |
|---|---|---|
--all | false | Reprocess all vulnerabilities (bypasses the known-ID skip set and the page-frontier resume) |
--limit | 0 | Maximum vulnerabilities to process (0 = unlimited) |
--force | false | Same effect as --all |
--backfill-refs | false | DB-only mode: re-reads every existing snyk CVEDescription, extracts URLs, classifies them, and inserts CVEMetadataReferences (referenceSource="Snyk"). No HTTP fetches, no locking, no notifier. Exposed as just go-snyk-fetch-backfill-refs. |
Soft deadline
softDeadlineDuration defaults to 110 minutes and is applied
unconditionally (main.go:74-78); EXPECTED_DURATION_MINUTES only overrides
the value, it does not disable the deadline. The just go-snyk-fetch-backfill
recipe unsets that env var, which means a local full-archive backfill is still
cut off after 110 minutes rather than running to completion — contrary to the
“backfill must not have a deadline” rule in scripts/go-processors/AGENTS.md.
Malware attribution post-pass
malwareactor.PostPass(ctx, pool, "snyk", …) runs after the lock is released
(main.go:283), i.e. outside the ingest loop and outside any transaction. See
the section below for what it writes.
ECS Schedule
Runs weekly on Saturdays at 04:00 UTC (cron(0 4 ? * SAT *)). CPU: 256, Memory: 512 MB,
Expected duration: 120 minutes.
Key Files
| File | Purpose |
|---|---|
cmd/snyk-fetch-processor/main.go | Main processor with pagination, page fetching, locking, frontier resume, --backfill-refs mode |
internal/snyk/types.go | SnykVuln data structure |
internal/snyk/parser.go | HTML extraction, __NUXT_DATA__ parsing, ExtractDescriptionURLs, ClassifyURL |
internal/snyk/mapper.go | SnykVuln to osv.CVESourceData mapping |
Not wired
- S3 archive / quarantine — no
s3client.Uploaderis constructed, so no raw payload is persisted at any point. This is the only processor in this batch that keeps nothing, and it is recorded as non-compliant in the compliance matrix. internal/aienrich— no enricher is constructed, so the CWE / ATT&CK / TreeSitter passes never run forsnykrecords. Snyk publishes its own CWE and CVSS, so the gap is narrower here than for the vendor-bulletin scrapers.
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:
snyk/files/{sha256}/{filename} - Quarantine:
failed-feeds/snyk-fetch-processor/{YYYY-MM-DD}/{reason}/{filename} - Likely reasons:
fetch-error,parse-error,store-error