> ## Documentation Index
> Fetch the complete documentation index at: https://docs.frostwolf.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Detection

> The attack categories, the two stages, the semantic pass, and how to read a verdict.

## Categories

A detection belongs to one of these families. `categories` lists the distinct families that fired, ordered by severity.

| Category | What it covers |
| - | - |
| `direct_injection` | Instruction override, system prompt extraction |
| `persona_hijacking` | Jailbreak modes, unrestricted personas, role override |
| `data_exfiltration` | Attempts to move data, secrets, or records out |
| `indirect_injection` | Forged role delimiters and instructions in retrieved content |
| `financial_fraud` | Payment and approval bypass |
| `multi_turn_escalation` | Privilege escalation and fabricated rapport over several turns |
| `tool_injection` | Secrets and hostile destinations in tool arguments |
| `forced_tool_use` | Forced invocation, consent bypass, schema override |
| `persuasion_amplifier` | Framing that lowers a model's guard |

Severity is `critical`, `high`, `medium`, or `low`.

## Input normalisation

Literal matching alone is easy to evade, so every payload is normalised before the static pass. Encoding tricks, invisible characters, look-alike characters, and spacing games are folded away first. Offsets are mapped back to your original text, so redaction stays exact.

## The two stages

The static pass answers in about 2 ms. When it finds nothing, a semantic classifier reads the payload and names the strategy, in about 100 ms round trip. It reports what it said in `modelCheck`.

| Field | Meaning |
| - | - |
| `ran` | Whether the classifier answered. |
| `verdict` | `safe`, `unsafe`, or null when it did not answer. |
| `reasons` | The classifier's own `task=label` pairs. |
| `categories` | The families those labels map onto. |
| `severity` | Severity of the strongest label. |
| `latencyMs` | Wall time of the classifier call. |
| `truncated` | Whether the text was cut to fit its input window. |
| `error` | Why it did not answer: `disabled`, `empty`, `timeout`, `unavailable`, `malformed_response`, `quota_exhausted`, or `disabled_by_request`. |

A block with empty `matches` and a `modelCheck` that says `unsafe` came from the semantic pass. That is the one case where `reason` is not a rule id: it is the classifier's own label.

<Warning>
  A `modelCheck` that did not run is not a clearance. Read `ran` before treating an allow as fully checked.
</Warning>

## The classifier fails open

The static pass has already run and found nothing, so a classifier that is unreachable or slow leaves the payload where it was: allowed, with `modelCheck.ran` false and an error token saying why. The alternative would make a separately hosted model a single point of failure for every clean request.

## Tuning the semantic pass

You can change what the semantic pass may do to a verdict. This exists for callers that remove personal data with [Cloak](/cloak/with-attack-check) before the request reaches a model, because the classifier treats personal data itself as unsafe.

| Mode | Behaviour |
| - | - |
| `enforce` | Default. The classifier can block. |
| `report` | It runs and returns its verdict in `modelCheck`, but cannot block. |
| `off` | It does not run. |

Signature matches block in every mode.

## Plan allowances

Every plan runs the static pass without a limit. The semantic pass is metered: a monthly number of scans and a monthly number of blocks it may produce. Each verdict carries a `quota` block with your usage. Once the block allowance is spent, a semantic detection is still reported but comes back as an allow, with `quota.enforcementWithheld` true and `quota.overflow` counting how many went that way. A signature block is never withheld.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.