CERT-CA RSS Processor — Design
Overview
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. Records stored under original CVE IDs
(source=cert-ca).
Feeds
| Feed | URL | Lang |
|---|---|---|
| EN | https://www.cyber.gc.ca/api/cccs/atom/v1/get?feed=alerts_advisories&lang=en | en |
| FR | https://www.cyber.gc.ca/api/cccs/atom/v1/get?feed=alerts_advisories&lang=fr | fr |
Both feeds: Auth=none, Format=Atom 1.0, ~42 entries each.
Parsing
The <content> CDATA contains HTML with:
- Serial number and date in first
<p>(<strong>Serial number:</strong> AV26-258) - Affected products in
<ul><li>items - CVE IDs in text and
<a href>links - Occasional CVSS scores in text (e.g., “CVSS 10.0”)
- Reference URLs in
<ul class="list-unstyled"><li><a href>at bottom <summary>is always empty — use<content>only
EN/FR Merge
Match by advisory ID extracted from URL: regex (av\d+-\d+).
- EN:
/en/alerts-advisories/...-av26-258→av26-258 - FR:
/fr/alertes-avis/...-av26-258→av26-258
EN primary. Matched FR entries add lang=fr description.
Storage
| Table | Details |
|---|---|
CVEMetadata | cveId=CVE-YYYY-NNNN, source=cert-ca |
CVEDescription | lang=en + lang=fr (when merged) |
CVEMetadataReferences | Advisory URL + vendor refs |
CVEMetric | Occasional CVSS score |
CVEAffected | Products from <ul><li> |
CVEAlias | Cross-refs between CVEs — only for two-CVE advisories; db.InsertAliases drops CVE↔CVE edges when a CVE-prefixed primary carries ≥2 CVE aliases (internal/db/cvealias.go:65). Same-cveId cross-source edges are always written. |
The occasional CVSS score is written to
CVEMetric;CVEMetadata.vectorStringis NULL for everycert-carow, correctly — the CCCS content carries a bare score and no vector.
Flags
--all, --limit, --force
Error Alerting
A failed EN feed fetch/parse is fatal (notifier.Errored + os.Exit(1)); a failed
FR feed is a warning and the run proceeds English-only. Per-CVE transaction
failures call notifier.RecordError and quarantine the advisory, then the loop
continues — but the run always ends in notifier.Completed, which does not
flush RecordError messages (internal/notify/notify.go:213). Those failures
therefore never reach Slack.
ECS Schedule
Daily (cron(0 6 * * ? *)), schedule_enabled = true,
expected_duration_minutes = 30. Soft deadline is
EXPECTED_DURATION_MINUTES - 10 and the loop guard fires 5 minutes earlier
still, so an ECS run stops after 15 minutes. Unsetting
EXPECTED_DURATION_MINUTES for a local backfill falls back to a hard-coded
15-minute default (10-minute effective budget) rather than no deadline.
Key Files
| File | Purpose |
|---|---|
cmd/cert-ca-rss-processor/main.go | Main processor with EN/FR merge |
internal/certca/types.go | Atom feed and advisory structs |
internal/certca/parser.go | Feed parsing, HTML extraction, EN/FR merge |
internal/certca/mapper.go | Advisory → CVESourceData mapping |
schemas/cert_ca_rss_advisory.schema.json | JSON Schema Draft 7 |
S3 Persistence
- Archive path:
cert-ca/files/{sha256}/{filename}✓ - Quarantine path:
failed-feeds/cert-ca-rss-processor/{YYYY-MM-DD}/{reason}/{filename}✓ - Failure reasons emitted:
parse-error,store-error
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.