# Data scrubbing

Production telemetry can carry credentials and PII. LoopOps scrubs **at
ingest, before the row is written** — the lake never holds the raw value, so
there is nothing to scrub retroactively and nothing for a read surface to
leak. The model is Sentry's server-side data scrubbing, adopted deliberately.

## On by default

**Sensitive field names** (contains-match, Sentry's list): `password`,
`secret`, `passwd`, `api_key`, `apikey`, `auth`, `credentials`,
`mysql_pwd`, `privatekey`, `private_key`, `token`, `bearer` — the whole
value becomes `[Filtered]`, objects included.

**Value patterns**, masked inside every string value and message:

| Pattern | Becomes |
|---|---|
| API keys (`sk-…`), bearer tokens, JWTs | `[REDACTED:api_key]` etc. |
| AWS / GitHub / Slack tokens | `[REDACTED:aws_key]` etc. |
| Card numbers (13–19 digits, **Luhn-validated** — order ids don't false-positive) | `[REDACTED:card_number]` |

Coverage is the whole attribute bag, deep — span, resource, scope, log body,
exception detail — on both OTLP lanes **and** feedback comments.

One deliberate deviation from Sentry: the field-name filter applies only to
string/object values. LoopOps counts LLM tokens (`token_burn`,
`gen_ai.usage.input_tokens`) — a number is never a credential.

## Per-project rules

```http
GET  /v1/redaction              # any project key — the active config
POST /v1/redaction              # agent key — change it (reason REQUIRED)
```

```json
{ "mask_emails": true,
  "sensitive_fields": ["customer_ref"],
  "safe_fields": ["auth_mode"],
  "reason": "pilot data-handling review" }
```

Or from your agent: `loopops_redaction({ mask_emails: true, reason: "…" })`.

- `mask_emails` — opt-in email masking (`[REDACTED:email]`).
- `sensitive_fields` / `safe_fields` — add or exempt field names
  (contains-match, ≤50 entries).
- `defaults: false` — master switch, if you truly need raw values.

Every change requires a **reason** and is recorded as its own
`redaction.config_changed` event — the scrubbing policy has a queryable
history. Changes apply to new events within ~60 s and are **never
retroactive** (Sentry's rule too).

## Also in this layer

Producer-side capture flags (`recordInputs` / `recordOutputs` on the SDK)
stop values from ever leaving your process — server scrub is the second line,
not the only one. Raw feedback audio is transcribed at the edge and not
retained.

**Next:** [Limits, quotas & ingest health →](/docs/limits-and-health) ·
[Security →](/security)