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-xchange CVEMetadata rows 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_affected and first_fixed (not known_affected)
  • CWE: Singular cwe object per vulnerability (not the cwes array used by some vendors)
  • Product tree: Hierarchical branches with product_identification_helper.cpe providing CPE strings
  • Threats: threats[] array with category: "impact" and category: "exploit_status"
  • IDs: ids[] array with internal tracking references (e.g., GitLab issues)

Data Mapping

CSAF FieldCVEMetadata Field
vulnerabilities[].cvecveId
"open-xchange"source
"5.0"dataVersion
"PUBLISHED"state
tracking.initial_release_datedatePublished (Unix seconds)
tracking.current_release_datedateUpdated (Unix seconds)
First CVSS vectorvectorString
document.titletitle
Advisory URLsourceAdvisoryRef
"Open-Xchange"affectedVendor
First affected product nameaffectedProduct
SHA256 of advisory JSONsourceFileHash
Full CSAF documentrawDataJSON

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 cwe field
  • CVEAffected: Products from product_status.last_affected and known_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'; the Link.id is written back to CVEMetadata.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

  1. Fetch index.txt, compute SHA256
  2. Compare against BulkDataDumpTracker key ox_csaf_index
  3. If unchanged and --force not set, exit early (reports task.nowork)
  4. Load per-CVE sourceFileHash resume set from DB
  5. Per advisory (fetched concurrently by --workers goroutines behind a shared 200 ms ticker): compute SHA256, skip if the hash matches the record for the advisory’s first CVE
  6. 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
  7. 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

FlagDefaultDescription
--forcefalseReprocess all advisories regardless of hash
--limit0Max advisories to fetch (0 = all)
--workers5Concurrent fetch workers sharing one 200 ms ticker (≈5 req/s)

Files

PathPurpose
cmd/ox-csaf-processor/main.goEntry point, fetch/parse/batch loop
cmd/ox-csaf-processor/s3.goS3 artifact upload helper
internal/openxchange/types.goCSAF 2.0 type definitions
internal/openxchange/parser.goIndex and advisory parsing
internal/openxchange/mapper.goCSAF → 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).

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

See the S3 Persistence Contract for the full reason taxonomy.