Open-Xchange CSAF Processor
Data Source
- Publisher: Open-Xchange GmbH
- Format: CSAF 2.0 JSON advisories
- Index:
https://documentation.open-xchange.com/appsuite/security/advisories/csaf/index.txt - Advisory base:
https://documentation.open-xchange.com/appsuite/security/advisories/csaf/ - Volume: low tens of advisories (2022–present), each containing 1–6 CVEs — 91
open-xchangeCVEMetadatarows in production as of 2026-08 - Update frequency: New advisories added periodically; existing advisories may be revised
Index Format
Plain text file with one relative path per line:
2025/oxas-adv-2025-0003.json
2025/oxas-adv-2025-0002.json
2024/oxas-adv-2024-0005.json
Each path is appended to the base URL to construct the full advisory URL.
CSAF Advisory Structure
Standard CSAF 2.0 with these Open-Xchange-specific patterns:
- Product status: Uses
last_affectedandfirst_fixed(notknown_affected) - CWE: Singular
cweobject per vulnerability (not thecwesarray used by some vendors) - Product tree: Hierarchical branches with
product_identification_helper.cpeproviding CPE strings - Threats:
threats[]array withcategory: "impact"andcategory: "exploit_status" - IDs:
ids[]array with internal tracking references (e.g., GitLab issues)
Data Mapping
| CSAF Field | CVEMetadata Field |
|---|---|
vulnerabilities[].cve | cveId |
"open-xchange" | source |
"5.0" | dataVersion |
"PUBLISHED" | state |
tracking.initial_release_date | datePublished (Unix seconds) |
tracking.current_release_date | dateUpdated (Unix seconds) |
| First CVSS vector | vectorString |
document.title | title |
| Advisory URL | sourceAdvisoryRef |
"Open-Xchange" | affectedVendor |
| First affected product name | affectedProduct |
| SHA256 of advisory JSON | sourceFileHash |
| Full CSAF document | rawDataJSON |
Relations
- CVEDescription: Vulnerability notes (category: description/summary/general), fallback to document notes
- CVEMetadataReferences: Advisory URL + document refs + vulnerability refs + remediation URLs
- CVEMetric: CVSS v3.1 scores from
scores[].cvss_v3 - CVEProblemType: CWE from singular
cwefield - CVEAffected: Products from
product_status.last_affectedandknown_affected(internal/openxchange/mapper.go:217-220), vendor = “Open-Xchange” - Artifact + Link: raw CSAF uploaded to
open-xchange/advisories/{sha256}/{cveId}.json,type='OTHER',bomFormat='open-xchange',contentType='PLAIN_JSON'; theLink.idis written back toCVEMetadata.fileLinkId
DEFERRED advisories
An index entry whose advisory URL returns 403 / 404 / 410 is stored as a
placeholder row: state='DEFERRED', sourceAdvisoryRef set, no title, no raw
JSON, and datePublished = 0. Production currently holds zero DEFERRED
open-xchange rows.
Incremental Processing
- Fetch
index.txt, compute SHA256 - Compare against
BulkDataDumpTrackerkeyox_csaf_index - If unchanged and
--forcenot set, exit early (reportstask.nowork) - Load per-CVE
sourceFileHashresume set from DB - Per advisory (fetched concurrently by
--workersgoroutines behind a shared 200 ms ticker): compute SHA256, skip if the hash matches the record for the advisory’s first CVE - Store each mapped CVE in its own transaction (2-minute context, up to 3 attempts, invalid-UTF-8 sanitisation between attempts) — there is no batch commit
- Update tracker on completion
Soft deadline
softDeadline = now + (EXPECTED_DURATION_MINUTES − 10), defaulting to
now + 30 min when the env var is unset; workers stop dispatching at
softDeadline − 10 min. Effective work window under the 60-minute ECS budget is
40 minutes. The unconditional 30-minute fallback also applies to
just go-ox-csaf-backfill, truncating a local backfill after 20 minutes — the
index is small enough (~20 advisories) that this has no practical effect today.
Flags
| Flag | Default | Description |
|---|---|---|
--force | false | Reprocess all advisories regardless of hash |
--limit | 0 | Max advisories to fetch (0 = all) |
--workers | 5 | Concurrent fetch workers sharing one 200 ms ticker (≈5 req/s) |
Files
| Path | Purpose |
|---|---|
cmd/ox-csaf-processor/main.go | Entry point, fetch/parse/batch loop |
cmd/ox-csaf-processor/s3.go | S3 artifact upload helper |
internal/openxchange/types.go | CSAF 2.0 type definitions |
internal/openxchange/parser.go | Index and advisory parsing |
internal/openxchange/mapper.go | CSAF → CVESourceData conversion |
Local Development
# Direct Go run (fastest for testing)
just go-ox-csaf-backfill
# With production DB
just go-ox-csaf-backfill TARGET=prod
# Force reprocess all
just go-ox-csaf-backfill FORCE=true
# Container build (mirrors ECS)
just ox-csaf-processor
Deployment
- ECS schedule: Runs weekly on Saturdays at 03:00 UTC (
cron(0 3 ? * SAT *)). - Resources: 256 CPU / 512 MB memory
- ECR tag:
go-ox-csaf-processor-latest - Log group:
/ecs/vdb-scheduler/go-ox-csaf-processor
S3 Persistence
- Archive path:
open-xchange/files/{sha256}/{filename}✓ - Quarantine path:
failed-feeds/ox-csaf-processor/{YYYY-MM-DD}/{reason}/{filename}✓ - Failure reasons emitted:
parse-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.