CERT-SE RSS Processor — Design

Overview

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 and Telecom Authority (PTS). Advisories are published in Swedish and cover critical vulnerabilities affecting products widely used in Sweden and globally.

Records stored under original CVE IDs (source=cert-se). Items without CVE IDs (guidance articles, weekly news bulletins, product-agnostic hardening guides) are skipped. One CVEMetadata row is created per CVE ID found in an advisory.

The mapper passes the advisory’s other CVE IDs as the record’s Aliases (internal/certse/mapper.go:24-29), but db.InsertAliases deliberately drops them: when the primary id is a CVE and the alias list carries more than one CVE-prefixed entry, the list is treated as an upstream bundle rather than as identity aliases, so no CVE↔CVE edge is written (internal/db/cvealias.go:65-88). The only CVEAlias rows this processor produces are the same-cveId cross-source edges linking (CVE-x, cert-se) to every other source that already carries CVE-x.

Feed

PropertyValue
URLhttps://www.cert.se/feed/rss.xml
AuthNone — fully public
FormatRSS 2.0
Languagesv (Swedish)
Update frequencyDaily (sy:updatePeriod=daily)
Typical items10 most recent entries

Content Types

The feed mixes two item types:

  1. Vulnerability advisories (~60% of items): contain one or more CVE IDs and a numeric CVSS score embedded in the Swedish-language description text. These are the only items stored.
  2. Non-vulnerability content (~40%): weekly security bulletins, hardening guides, threat-landscape articles. These never contain CVE IDs and are silently skipped.

Item Structure

Each <item> contains only core RSS 2.0 fields. No Dublin Core, media, or <content:encoded> extensions are used.

FieldDescription
<title>Swedish-language headline, e.g. “Kritisk sårbarhet i Oracle Identity Manager”
<description>HTML-encoded <p> paragraph(s) with full advisory text in Swedish
<link>URL of the full article on cert.se (used as sourceAdvisoryRef)
<guid isPermaLink="true">Identical to <link>
<pubDate>RFC 1123 timestamp with CET/CEST offset

Reference URLs are not present in the RSS feed. The description contains numbered citation markers ([1], [2], …) that link to vendor advisories, but the actual URLs are only available in the linked HTML article, which this processor does not fetch.

Parsing

CVE Extraction

CVE IDs appear in the description text, typically inside parentheses:

  • (CVE-2026-21992) — most common
  • CVE-2026-3055 — bare, without parentheses

Regex: CVE-\d{4}-\d{4,}

CVSS Extraction

Numeric scores only — no CVSS vector strings in the RSS feed. Three Swedish phrasings are observed, matched in priority order:

PatternExample
Score then versionklassning på 9.8 enligt CVSS 3.1
Version then scoreCVSS v4.0-klassning på 9.3
Version then score (space sep.)CVSS v3.1-klassning på 8.8

A fallback regex catches any CVSS mention near a decimal number for future phrasing variations. When the CVSS version cannot be determined from the text, the metric type defaults to cvssV3_1 (the most common version seen in the feed).

CVSS version → MetricType mapping:

Version prefixMetricType
4.*cvssV4_0
3.1cvssV3_1
3.0cvssV3_0
2.*cvssV2_0
(unknown)cvssV3_1

Date Parsing

<pubDate> uses RFC 1123Z format with named or numeric timezone offsets (+0100, +0200). Multiple formats are tried in sequence to handle CET/CEST variation and any malformed entries.

Advisory ID

The last path segment of the article URL, with .html suffix stripped: https://www.cert.se/2026/03/kritisk-sarbarhet-i-oracle-…htmlkritisk-sarbarhet-i-oracle-…

Content Hash

SHA1(link|title|pubDate) used for idempotency tracking (sourceFileHash).

Storage

No new tables or columns.

TableRows inserted
CVEMetadataOne per CVE ID; source="cert-se"
CVEDescriptionOne per CVE; lang="sv"
CVEMetadataReferencesOne row: the cert.se article URL (type="advisory")
CVEMetricOne row per CVE if CVSS score present (containerType="cna"), plus the pipeline’s derived containerType="vulnetix" CVSS v4.0 row when a description is present
CVEAliasSame-cveId cross-source edges only (co-listed CVE↔CVE aliases are suppressed — see Overview)

No ProblemTypes (no CWE IDs in feed), no Affected (no product/package data).

Incremental Strategy

URL-based deduplication via sourceAdvisoryRef in CVEMetadata. At startup the processor loads all known cert-se advisory URLs from the read replica into a map. Items whose URL is already present are skipped unless --all or --force is set.

Flags

FlagDefaultDescription
--allfalseReprocess all advisories including already-stored ones
--limit0Maximum advisories to process per run (0 = unlimited)
--forcefalseForce reprocessing even if content is unchanged

Error Handling and Alerting

  • Feed fetch retried up to 3 times with exponential backoff (2s, 4s).
  • Per-item transaction failures are recorded via notifier.RecordError() and logged at WARN level; processing continues with the next item.
  • Fatal errors (feed fetch failure, XML parse failure) publish a task.errored SNS event and exit non-zero.
  • Overtime (execution exceeds EXPECTED_DURATION_MINUTES − 10 min soft deadline) cancels the context and publishes a task.overtime SNS event.
  • The item loop also stops 5 minutes before that soft deadline (cmd/cert-se-rss-processor/main.go:115-118). When EXPECTED_DURATION_MINUTES is unset — which is exactly what just go-cert-se-rss-backfill does — the soft deadline falls back to a hardcoded 10 minutes, so a local run stops walking the feed after ~5 minutes. The feed only carries 10 items, so this has no practical effect today, but it does not match the “backfills must run to completion” rule in scripts/go-processors/AGENTS.md.
  • The notify-dispatcher Lambda transforms task.errored and task.overtime SNS events into Slack Block Kit or Google Chat cardsV2 messages (selected at boot by NOTIFY_BACKEND) with CloudWatch and ECS deep-links.

ECS Schedule

Daily at 06:00 UTC (cron(0 6 * * ? *)), matching other CERT family processors.

ResourceValue
CPU256 units (0.25 vCPU)
Memory512 MB
Expected duration30 minutes
ECS familygo-cert-se-rss-processor
EventBridge schedulego-cert-se-rss-processor
CloudWatch log group/ecs/vdb-scheduler/go-cert-se-rss-processor
ECR image taggo-cert-se-rss-processor-latest

Key Files

FilePurpose
cmd/cert-se-rss-processor/main.goMain processor, flags, feed fetch loop
internal/certse/types.goRSS feed structs and Advisory type
internal/certse/parser.goXML parsing, CVE/CVSS extraction, date parsing
internal/certse/mapper.goAdvisory → CVESourceData mapping
terraform/go-schedules.tfECS task + EventBridge schedule (module "cert_se_rss_processor")
scripts/task-manager.tomlTUI metadata ([tasks.cert-se-rss-processor])
justfileLocal backfill recipe (go-cert-se-rss-backfill)

S3 Persistence

  • Archive path: cert-se/files/{sha256}/{filename}
  • Quarantine path: failed-feeds/cert-se-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).

flowchart LR SRC[Source feed] --> PROC[cert-se-rss-processor] PROC -->|success| ARCHIVE[("S3: cert-se/files/{sha256}/{filename}")] PROC -->|failure| Q[("S3: failed-feeds/cert-se-rss-processor/{date}/{reason}/{filename}")] PROC --> DB[(PostgreSQL)]

See the S3 Persistence Contract for the full reason taxonomy.