community-suricata-snort-processor
Status: Live Source:
quadrantsec/suricata-rules(single file) +daffainfo/suricata-rulesper-yearCVE-{year}.rules(2000→current) andCNVD-{year}.rules(2018→current) Type:snort(Suricata rule text — syntax-compatible with the shared Snort 2.x parser) Source slug:community-suricata(Exploit,ExploitCVE, placeholderCVEMetadata);SnortRulerows are written under the shared slugsnortSchedule: Daily at 10:00 UTC (cron(0 10 * * ? *)), 256 CPU / 512 MB,expected_duration_minutes = 15.
Overview
Suricata is the more common open IDS deployment today, and the daffainfo
feeds are organised per vulnerability year — including a CNVD series that
carries signatures for Chinese national-vulnerability-database advisories that
never receive a CVE. That CNVD coverage is what this processor adds over
community-snort-processor (GitHub Snort
rulesets) and emergingthreats-snort-processor
(ET open ruleset): it is the only feed in the VDB that links a CNVD-…
identifier to a deployable network signature.
The URL list is built at runtime from the current year
(cmd/community-suricata-snort-processor/main.go:51-75), so a new year’s file
is picked up automatically. Missing years return HTTP 404 and are skipped
quietly — a 404 is expected, not an error. Rules are parsed with the shared
emergingthreats.ParseV2Rules and only those carrying a CVE, CNVD or GHSA
reference are stored, in batches of 100 with a per-rule savepoint.
Records produced
| Condition | Records |
|---|---|
| Every stored rule | Exploit (source="community-suricata", exploitId=SID, title=msg, category=classtype, cveIds JSON) |
Each reference:cve on the rule | ExploitCVE junction + db.EnsureMinimalCVEMetadata(cveId, "community-suricata") placeholder CVEMetadata row (state="PUBLISHED", datePublished=0, no title) |
| Every stored rule | SnortRule (source="snort", full parsed rule incl. rawText, classtype, disabled, metadata-derived confidence/severity/products/targets/MITRE ids) |
| Each CVE / CNVD / GHSA id | SnortRuleCVE junction — the cveId column intentionally also holds CNVD-… identifiers (cmd/community-suricata-snort-processor/main.go:320-327) |
| Successful batch | Rule text archived to community-suricata/files/{sha256}/{SID}.rules |
| Failed rule or batch | Rule text quarantined to failed-feeds/community-suricata-snort-processor/{date}/store-error/{SID}.rules |
CNVD ids get no placeholder CVEMetadata row (only reference:cve ids do),
so a CNVD-only rule is reachable through SnortRuleCVE but has no Exploit↔CVE
junction. That is intentional: CVEMetadata is keyed by (cveId, source) and a
CNVD advisory belongs to the cnvd source, not to this one.
Placeholder rows under source='community-suricata'
All 501 production CVEMetadata rows for this source are FK placeholders — no
title, no rawDataJSON, datePublished = 0. This is correct behaviour, not a
defect: exclude the source from title/CVSS coverage measurements entirely (see
the alias-shell rule in .repo/efficacy/README.md).
Known issues
- Shared resume set and shared
SnortRule.source. Like the Snort processor, this one loads its resume set withdb.LoadProcessedSnortRuleIDs(ctx, pool.Read, "snort")(cmd/community-suricata-snort-processor/main.go:123) and writes rules undersource='snort', unique on(snortId, source)(internal/db/snortrule.go:72). Because it runs an hour aftercommunity-snort-processor, any SID already claimed by a GitHub Snort rule causes this processor to skip its own Suricata rule with the same number, silently. Production shows 0 overlapping SIDs between the twoExploitsources — but a skipped rule leaves no row, so that is not proof of no loss. Exploit.originalUrlis wrong. The sharedMapToExploitRowhardcodes the Emerging Threats ruleset URL (internal/emergingthreats/mapper.go:12,34); all 573community-suricataexploit rows point there rather than at the daffainfo/quadrantsec file the rule came from.- The soft deadline applies to backfills. The batch loop stops at a
hardcoded 20-minute deadline when
EXPECTED_DURATION_MINUTESis unset (cmd/community-suricata-snort-processor/main.go:96-100,191-194), which is exactly whatjust go-community-suricata-snort-backfilldoes. With ~35 feed URLs to fetch, a cold backfill can be truncated.
Failure modes
| Failure | Behaviour |
|---|---|
| Per-year file missing (HTTP 404) | Counted in fetchErrors, not logged, not alerted — expected for years with no rules |
| Other fetch failure | 3 attempts with 3 s × attempt backoff, then RecordError and skip the file |
| Zero CVE/CNVD-bearing rules across all feeds | task.completed with stored=0; no error |
| Single rule fails | Rolled back to savepoint, quarantined, counted; run continues |
| Batch transaction fails | Whole batch quarantined, RecordError, next batch attempted |
| Any accumulated error | task.errored (Slack alert) at end of run |
Flags
| Flag | Default | Description |
|---|---|---|
--all | false | Reprocess every parsed rule, bypassing the SID resume set |
--limit | 0 | Maximum rules to store (0 = unlimited) |
--force | false | Same effect as --all for the resume set |
Local run: just go-community-suricata-snort-backfill prod (positional:
TARGET ALL LIMIT FORCE).
Key files
| File | Purpose |
|---|---|
cmd/community-suricata-snort-processor/main.go | URL builder, fetch/parse loop, batch + savepoint writes |
internal/emergingthreats/parser_v2.go | Rule parser (shared with the Snort processors) |
internal/emergingthreats/mapper.go | MapToExploitRow, MapToSnortRuleRow (shared) |
internal/db/snortrule.go | UpsertSnortRule, InsertSnortRuleCVEs, LoadProcessedSnortRuleIDs |
Verification
-- rules stored by this processor
SELECT count(*) FROM "Exploit" WHERE source = 'community-suricata';
-- CNVD identifiers linked to signatures
SELECT src."cveId" FROM "SnortRuleCVE" src
JOIN "SnortRule" sr ON sr.uuid = src."snortRuleUuid"
WHERE sr.source = 'snort' AND src."cveId" LIKE 'CNVD-%'
LIMIT 10;
S3 Persistence
- Archive path:
community-suricata/files/{sha256}/{filename}✓ - Quarantine path:
failed-feeds/community-suricata-snort-processor/{YYYY-MM-DD}/{reason}/{filename}✓ - Failure reasons emitted: (none documented)
Uses s3client.Uploader from internal/s3client/uploader.go. Skipped when S3_BUCKET_NAME is unset (local dev).
See the S3 Persistence Contract for the full reason taxonomy.