Skip to content

Data Retention

OpenTremor’s most sensitive data isn’t the audit trail or the request telemetry — it’s the raw infrastructure-as-code content it analyzes (resources.value: Terraform source, diffs, whatever was ingested) and the security findings derived from it. Both used to have no retention policy at all: kept forever, with no delete path, while the lower-stakes telemetry/usage data was capped at a fixed 30 days. This page documents the fix — a retention config section, admin-editable via PATCH /admin/settings, with a lifecycle that matches each data category’s actual sensitivity and audit value instead of the reverse.

The policy, and why each field has the value it does

Section titled “The policy, and why each field has the value it does”
DataFieldDefaultWhy
Telemetry spans + usage eventstelemetry_days30 daysPure observability — short-lived by design, unchanged from before this feature.
Audit log (privileged actions)audit_days730 daysDecoupled from telemetry on purpose — compliance/audit evidence (SOC2, ISO) typically needs to outlive perf data by a lot. Previously silently shared telemetry.retention_days, capped at 365 days max; that cap is gone.
Resolved low/medium-severity findingsfinding_low_medium_days180 daysLow ongoing audit value once resolved — kept just long enough to be useful, then purged to shrink the collection and reduce exposure.
Resolved high/critical-severity findingsfinding_high_critical_days730 daysKept much longer — “we found X and fixed it” is exactly the evidence an audit typically wants, so this trades storage for audit value on purpose.
Raw analyzed resource contentresource_content_days90 daysThe highest-exposure data (may contain secrets, account IDs, internal architecture) gets the shortest life of anything here — the raw value payload is redacted, along with every embedded Finding.evidence snippet on resource.analysis (verbatim code copied straight out of the same source), since evidence is the same exposure by another name. The finding’s other fields (severity, title, rule_id, …) and the analysis structure itself survive.

Open and needs-review findings are never purged, regardless of age or severity. This isn’t a config option — controllers/services/product_retention.py’s purge step only ever calls it with terminal statuses (acknowledged/suppressed/false_positive), so an active or un-triaged finding can’t silently disappear on a timer no matter how the other fields are set.

Two more fields control whether/how often this runs automatically rather than for how long anything is kept:

FieldDefaultBounds
sweep_enabledtrue
sweep_interval_hours241–168

Two enforcement mechanisms, two different “when it takes effect” answers

Section titled “Two enforcement mechanisms, two different “when it takes effect” answers”

telemetry_days/audit_days are TTL-index-backed (MongoDB only — ignored on the in-memory backend, same as every other Mongo-specific setting). Changing either via PATCH /admin/settings pushes the new value live onto the running TTL index with a collMod call, immediately — not just re-read on the next request, and not requiring a restart or re-prepare. This is a real fix, not a documentation nuance: before this feature, retention_days only affected a newly created index, so changing it after boot silently did nothing to an already-running deployment.

Everything else is sweep-based, because a Mongo TTL index can only expire “N seconds after one fixed date field” — it can’t express “delete this if status is X and severity is Y and it’s old enough.” Findings and resources need an actual application-level sweep (POST /admin/retention/run) that filters on multiple fields. This is why resource_content_days and the two finding_*_days fields only take effect the next time a sweep actually runs, not instantly on PATCH.

Core itself has no scheduler — see Task Scheduler for the standalone process that calls POST /admin/retention/run on a heartbeat. That endpoint is self-gating: called again before sweep_interval_hours has elapsed, it’s a no-op that returns the previous result (skipped: true) instead of redoing work, which is what makes it safe for the scheduler to call frequently without knowing the configured interval itself. The dashboard’s Platform Admin → Settings → Retention sweep → Run sweep now button (or POST /admin/retention/run?force=true) always bypasses the gate for an immediate run. See Platform Admin — Running a retention sweep manually for the full endpoint reference, including GET /admin/retention/status.

Terminal window
curl -X PATCH http://localhost:8000/admin/settings \
-H "X-API-Key: <admin-key>" \
-H "Content-Type: application/json" \
-d '{
"retention_audit_days": 1825,
"retention_finding_high_critical_days": 1825,
"retention_resource_content_days": 30
}'

Set a field to null to clear the override and fall back to the hardcoded default — same convention as every other /admin/settings field, see Configuration Reference — Platform settings.

One gap is known and deliberately out of scope for this feature, not silently dropped:

  • No purge for findings whose resource has fallen out of the current namespace state. A resource that’s been re-analyzed and no longer exists in any namespace doesn’t have its findings automatically staled out — only the severity/status/age-based purge above applies. Would need a “current namespace membership” concept this codebase doesn’t have yet.

Org-offboarding was the other gap listed here — closed: see Platform Admin — Deleting an organization for the cascade delete this data-retention work originally flagged as missing.