# Data loss prevention

> Detect card numbers, secrets, personal data and your own code in prompts before they reach a model, and record, mask, re-route or block them.

Product: AI Cost Optimizer Gateway (ACOG). Web version: https://tessarac.com/docs/acog/govern/data-loss-prevention. Last updated: 2026-10-16.

Data loss prevention (DLP) scans prompts on their way to a model and acts on sensitive data it finds. **Nothing is scanned until you turn a data type on**, and every detector runs inside the ACOG deployment. Detection never sends the values it finds anywhere else, and its records never contain them (but see [Stored conversations and DLP](#stored-conversations-and-dlp)).

Open **Admin → Data loss prevention**. Viewing it requires **View invoices and billing status**; changing it requires **Manage data-loss-prevention rules and connect source code** (`admin:security`).

## What it detects

Detection runs in three layers.

| Layer | Detects | Default action when turned on |
|---|---|---|
| 1. Exact data | Credit card numbers, US Social Security numbers, bank routing numbers, IBANs, live secrets and API keys. Checked by format and checksum, so it is the most precise layer. | Block |
| 2. Free text | People's names, email addresses, phone numbers, mailing addresses, IP addresses, medical and health terms, and your own **custom dictionary** of terms | Audit |
| 3. Protected sources | Text that matches code or documents in repositories you connect | Audit |

### Custom dictionary

Add words or phrases specific to your organization, such as project code names. ACOG stores a one-way hash of each term, not the text. Terms are up to 128 characters, with options for whole-word and case-sensitive matching.

### Protected sources

Connect a GitHub or GitLab repository and ACOG indexes it as fingerprints. A prompt that contains text from the repository is detected even when it has been edited slightly. The repository content itself is not kept.

## Actions

| Action | Effect |
|---|---|
| **Audit** | The prompt goes through unchanged. ACOG records that it saw the data type, never the value. |
| **Tokenize** | (Exact data and free text, set through the API or a policy rule) The value is replaced with a deterministic token before the prompt leaves, and the real value is restored in the reply, so the workflow still works and the model never sees it. |
| **Mask** | The value is replaced with a placeholder before the prompt leaves. The model never sees it. |
| **Route** | (Protected sources and policy rules) The request may only be served by platforms you approve, for example your own cloud account. |
| **Block** | The request is refused and never reaches the model. |

Each detector has a confidence threshold from 50 to 100% (60 to 100% for protected sources). The recommended starting points are 85 for exact data, 50 for free text and 85 for protected sources.

## Policy rules

Policy rules apply different actions for one team, one person or one application, on top of the data types you turned on. Precedence is organization, then team, then the person or application. A rule marked **Off** is not applied.

## What a blocked request gets

```json
{
  "error": {
    "type": "dlp_blocked",
    "message": "…",
    "category": "credit_card",
    "reason": "layer1_baseline",
    "decision_id": "…"
  }
}
```

The status is `403`. Give the `decision_id` to an admin to find the event. `reason` is one of `layer1_baseline`, `layer2_detection`, `corpus_match`, `no_compliant_route`, `dlp_unavailable` or `input_too_large_to_scan`.

## If the scanner is unavailable

Exact-data checks run inside the gateway and are always available. Free-text and protected-source detection run in a separate service. If it cannot be reached, the setting **If the scanner is unavailable** decides:

| Choice | Effect |
|---|---|
| **Block the request** (default, recommended) | The request is refused with `403 dlp_blocked`, reason `dlp_unavailable` |
| **Let the request through** | The request continues without those layers |

## What is scanned

| | Scanned |
|---|---|
| Prompts on the compatible endpoints and native routes | Yes |
| Files sent inline in a prompt | As text |
| Requests whose body is not JSON | Exact-data types only |
| Embeddings requests | No |
| Model replies | In a successful non-streamed reply, tokenized values are restored, and any exact-data value (card, Social Security number, routing number, IBAN, secret) whose type is set to Mask or Block is masked before the reply reaches you. Free-text types are not masked in replies. In a streamed reply, tokenized values are restored; masking is not applied. |

## Seeing what was found

The **Overview** tab shows findings by data type and outcome. Blocks and masks are delivered as the `governance.dlp_blocked` [webhook](https://tessarac.com/docs/acog/administer/webhooks.md) event. A detection record holds the category, its confidence, a reference ID and the position in the text, never the value itself.

## Stored conversations and DLP

**Store prompts for review** (on by default, under **Admin → Gateway settings**) keeps each conversation as it was sent to the model, which is after DLP has acted:

| Action for the data type | In the stored conversation |
|---|---|
| Block | Nothing: the request never reached the model |
| Mask | The placeholder, not the value |
| Tokenize | The token, not the value |
| Audit | The value, because Audit lets the prompt through unchanged |

The same applies to prompt and completion text sent to your own Langfuse project. In the stored and exported reply, only exact-data types set to Mask or Block are masked; a free-text value the model writes into its reply is kept as written. If a data type must never be kept, set it to **Mask** or **Block** and, for free-text types, turn off prompt storage and Langfuse prompt and completion capture (see [Observability](https://tessarac.com/docs/acog/administer/observability.md)). See [Activity and audit log](https://tessarac.com/docs/acog/govern/activity-and-audit-log.md#stored-conversations).

## Self-hosted installations

Names and medical terms need a named-entity model. The chart deploys one by default; the DLP service reports `ner_ready` when it is loaded. See [Configuration](https://tessarac.com/docs/acog/self-hosted/configuration.md).
