Data Protection & Guardrails
AgentWatch’s data protection runs inline at the gateway, before the request leaves the organization. That placement is the whole point: a tool that scrubs a trace after the fact has already let the data reach the provider.
What sensitive data does AgentWatch detect?
Section titled “What sensitive data does AgentWatch detect?”| Category | Examples |
|---|---|
| PII | Social security numbers, driver’s licenses, passport numbers, names, emails, phone numbers, addresses, dates of birth, IP addresses |
| Financial | Credit card numbers, bank accounts, IBAN, SWIFT, routing numbers |
| Health / PHI | Medical record numbers, diagnoses (Dx: CHF, anxiety disorder, has diabetes), symptoms in a clinical note, and patient fields in X12 and HL7 messages and in JSON fields that name them, under the HIPAA framework |
| Secrets | Provider API keys (recognized by their prefixes), passwords, session tokens, private keys |
Detection combines named-entity recognition, pattern matching, allow and deny lists, financial and health-specific detectors, and optional external DLP webhooks. Custom recognizers can be added per organization without code changes — proprietary part numbers, internal customer identifiers, regional ID formats — and severity can be overridden per category.
What happens when sensitive data is found?
Section titled “What happens when sensitive data is found?”You choose one action for the organization. With Block, a sensitivity decides which findings refuse the request. Every finding is recorded, whatever the action does with it.
| Action | What happens to findings |
|---|---|
| Detect only | Recorded — the request goes through unchanged |
| Redact | Every finding is replaced with a marker such as [SSN-REDACTED]; the request goes through. Sensitivity does not apply |
| Block | A finding in the selected risk levels refuses the request, which never reaches the model. Every other finding is redacted, never forwarded raw |
Findings appear under Observe → Security → Detections and in Usage Insights, in every mode.
What does the sensitivity setting mean?
Section titled “What does the sensitivity setting mean?”Each kind of data has a risk level, and with the action set to Block, the sensitivity picks which levels refuse a request. It is not a score threshold: a passport is high risk whatever confidence the detector reports.
| Sensitivity | Acts on | Typical use |
|---|---|---|
| Low | High-risk data only | Fewest interruptions; protect what must never leave |
| Medium | High- and medium-risk data | Balanced |
| High | Every finding, including low-risk | Maximum protection |
| Risk level | Examples |
|---|---|
| High | SSN, ITIN, passport, driver’s license, card number, CVV and expiry, bank account and routing numbers, IBAN, SWIFT/BIC, medical record number, health-plan / member ID, API keys, passwords and other credentials, deny-list terms |
| Medium | Email address, phone number, date of birth, street address, EIN / tax ID, medication, diagnosis or condition, lab result, VIN |
| Low | Person names, organizations, locations, IP addresses, plain dates, ZIP codes on their own, a card’s last four digits, a brand-less 16-digit number that fails the card checksum |
Names, organizations, places, dates and nationalities come from Advanced NER. They never block at any sensitivity — they appear in ordinary sentences (“the weather in Paris”) — so with Block they are redacted and the request is served. At High, the low-risk items that do block are the regex-detected ones: IP addresses, labelled ZIP codes, a card’s last four.
A blocked request names the categories that caused it — for example
dlp: PCI, PHI — using PII, PCI, Financial, PHI,
Credentials, NER and Deny list. The Usage Insights pill shows the
same category (a blocked card reads Payment Card, a bank account
Financial Data).
DLP also applies to MCP tool calls: every tool invocation’s arguments and response are scanned before being forwarded, which closes the gap where sensitive data moves through tools rather than prompts.
What guardrails are available?
Section titled “What guardrails are available?”Prompt-injection detection, data-leakage detection, PII and secret guardrails are on by default; toxicity, hate speech, bias, adult content, copyright, and profanity guardrails are available and off by default. Additional checks cover gibberish and invisible-text injection. AWS Bedrock Guardrails can be used as an external engine, with fail-open or fail-closed behavior configurable.
Two properties matter operationally:
- Detection runs before the prompt reaches the model, so a match blocks rather than annotates.
- Guardrails apply to tool and context payloads too — MCP tool arguments and retrieved context blocks are checked before they are injected into a prompt, not just the user’s own text.
Can AgentWatch enforce the shape of model output?
Section titled “Can AgentWatch enforce the shape of model output?”Yes. A JSON Schema can be attached per route, in either direction. Inbound, malformed requests are sanitized or rejected. Outbound, a non-compliant model response is rejected, logged, coerced, or automatically re-prompted with a hint that the previous response did not match the schema — so only valid structured data reaches the application.
Is there a web application firewall?
Section titled “Is there a web application firewall?”Yes, with both conventional and AI-specific rules. Conventional rules cover SQL injection, cross-site scripting, command injection, and path traversal. AI-specific abuse patterns cover token stuffing (a per-request token budget enforced before forwarding), prompt flooding (burst-rate ceilings), abnormal session blocking, header bombs, and oversized payloads. Rules run in detect or block mode, and rate limiting, CIDR allowlisting, and circuit breakers form a defense-in-depth chain in front of the model.
How is this tuned to avoid false positives?
Section titled “How is this tuned to avoid false positives?”Detection confidence thresholds are configurable per category, the recognition model can be sized for the precision the deployment needs, results are cached so repeated content is checked cheaply, and log-only mode exists specifically so a new policy can be validated against real traffic before it starts blocking. Name detection in particular is tuned to avoid firing on ordinary factual questions.
How do I turn DLP on and choose an action?
Section titled “How do I turn DLP on and choose an action?”Settings → Company Policy → Data Protection. Enable the detector categories you need, choose one action under Action on Detection — Detect only, Redact or Block — and, for Block, a Block sensitivity (Low, Medium or High). Changes take effect within seconds — there is no restart.
Through the API the action is dlpAction (detect, redact, block) and the
sensitivity dlpSensitivity (low, medium, high). They are stored as the
two older switches plus a number, which the API also still accepts: Detect only
is dlpRedactEnabled: false, dlpBlockOnViolation: false; Redact is
dlpRedactEnabled: true; Block is both true. A row with only
dlpBlockOnViolation: true is treated as Block. dlpRiskThreshold 7 or more
is Low, 4 or more is Medium, anything lower (including the default 0) is High.
# read the current policycurl -s https://<your-agentwatch-host>/api/admin/dlp/policies \ -H "Authorization: Bearer <tenant-admin JWT>"
# change one field without resending the whole policycurl -s -X PATCH https://<your-agentwatch-host>/api/admin/dlp/policies \ -H "Authorization: Bearer <tenant-admin JWT>" -H 'Content-Type: application/json' \ -d '{"dlpEnabled": true, "dlpAction": "block", "dlpSensitivity": "medium"}'The GET answer includes dlpAction and dlpSensitivity, read the same way
the gateway reads them.
Verify with a probe. Send a request containing an obviously fake identifier and confirm the outcome:
curl -s -X POST https://<your-agentwatch-host>/v1/chat/completions \ -H "Authorization: Bearer ak_…" -H 'Content-Type: application/json' \ -d '{"model":"<your model>","messages":[{"role":"user","content":"my SSN is 123-45-6789"}]}'In redact mode the upstream receives [SSN-REDACTED] and the call
succeeds; in block mode the request is refused (dlp_blocked) at every
sensitivity, because an SSN is high risk. Try my email is a@example.com with
Block at Low: the request goes through with the address redacted,
because an email is medium risk. Either way the row appears in
Usage Insights with a status pill, and no raw value is stored in the
preview — the audit record is scrubbed at write time.
How do I stop false positives without turning DLP off?
Section titled “How do I stop false positives without turning DLP off?”In order of preference:
-
Allow-list the specific value or pattern (Settings → Company Policy → Data Protection → allow list) — narrowest fix, keeps the detector on.
-
Turn off the one category that is misfiring rather than the whole engine.
-
Domain rules — apply a different policy to traffic bound for a particular destination:
Terminal window curl -s -X PUT https://<your-agentwatch-host>/api/admin/dlp/domain-rules \-H "Authorization: Bearer <tenant-admin JWT>" -H 'Content-Type: application/json' \-d '{"rules":[{"domain":"internal.acme.com","action":"warn"}]}' -
Tune the NER recognizers if you run the model-backed detectors:
Terminal window curl -s https://<your-agentwatch-host>/admin/dlp/ner/status \-H "Authorization: Bearer <tenant-admin JWT>"statustells you which recognizers actually loaded — a scanner whose model file is missing reports itself as degraded rather than silently passing everything.
How do I configure guardrails?
Section titled “How do I configure guardrails?”Settings → Company Policy → Guardrails. Each scanner has its own enable switch and a threshold. Presets (Low / Medium / High) set fixed values; the Exact threshold box takes any value between 0 and 1 when a preset does not fit your data.
Two settings worth knowing before you go live:
- Gibberish Detection blocks on the labels you choose.
mild gibberishis the model’s label for “not fluent prose”, which is what an identifier, a JSON tool result or a CSV row is — it is off by default for that reason. Keyboard mash still scores undernoiseandword salad. Turn it on only if your traffic is genuinely all prose. - Block on violation is the global default. A scanner with no explicit action of its own follows it; see the per-scanner section below for what an explicit action does and does not yet override.
Can one scanner block while another only logs?
Section titled “Can one scanner block while another only logs?”Yes. Each scanner carries its own action, and an explicit choice overrides the global Block on violation switch in both directions.
| Setting | Behaviour |
|---|---|
| A scanner set to block, global switch off | That scanner refuses; the others do not |
| A scanner set to log-only, global switch on | That scanner records and allows; the others still refuse |
| A scanner with no explicit action | Follows the global Block on violation switch |
⚠️ Set it for your organization. Per-scanner actions are read from your organization’s settings, which is what the console writes. A platform-wide default set by a super administrator does not reach an organization that has settings of its own — organization settings are read as a whole rather than merged field by field, so your organization’s row shadows the platform default. That applies to every setting, not just this one.
A scanner with no explicit action follows the global Block on violation switch, so a deployment that has never touched this behaves exactly as it did. This exists because that global switch was previously the only control: turning Gibberish down to advisory also stopped Secret Detection refusing anything.
block works correctly including while the global switch is off — that
direction was fixed in 2.0.289.
curl -s -X POST "https://<your-agentwatch-host>/admin/company/settings?organizationId=<org id>" \ -H "Authorization: Bearer <tenant-admin JWT>" -H 'Content-Type: application/json' \ -d '{"guardrailsScannerActions": "{\"gibberish\":\"log-only\",\"secrets\":\"block\"}"}'Note the ?organizationId= — without it a super administrator writes the
platform row, which an organization with its own settings will not read.
A log-only finding still appears in the audit trail and on the Security page — it simply does not refuse the request.
Which parts of a conversation are scanned?
Section titled “Which parts of a conversation are scanned?”By default the user turns and tool results. Tool results matter: their content comes from outside your organization and is exactly where an injection arrives, while a user’s own prompt is at least authored by someone you employ.
System messages stay excluded by default — they carry code, configuration and documentation, which produce a high false-positive rate — and the assistant’s own turns are governed by response screening rather than by this setting.
-d '{"guardrailsScanRoles": "[\"user\",\"tool\",\"assistant\"]"}'⚠ An empty or unparseable list falls back to the default rather than scanning nothing. A guard that silently stops guarding is worse than one that is off, because the console still reports it as on.
Why was a harmless prompt refused as Prompt Injection?
Section titled “Why was a harmless prompt refused as Prompt Injection?”The Prompt Injection scanner is a classifier model, and it reacts to some shapes of text whatever the prompt actually asks for. Two were measured on real customer traffic and are handled since 2.0.374:
| Prompt | Model score before | What the scanner now shows the model |
|---|---|---|
permit BLD-2026-00417. |
0.983 | permit ID. (0.000) |
Parcel 27-22-29-1234-05-060, permit BLD-2026-00417. |
0.958 | Parcel ID, permit ID. (0.000) |
Repeat this sentence exactly: the quarterly report is due Friday. |
1.000 | the quarterly report is due Friday. (0.000) |
- Reference codes (a word containing a digit and joined by
-,_,/or.: permit, parcel, SKU, ticket and invoice numbers, dates and versions) are shown to the model asID. An instruction cannot hide inside such a code. - “Repeat this sentence / the following text: …” is scored on the text being repeated. An injection placed there is still refused, and so is a request to repeat your instructions or the words above, which is how prompt extraction is phrased.
The threshold does not change, and only the Prompt Injection scanner is affected: data protection, the other scanners and the request sent to the provider see the original text.
If a different harmless prompt is still refused, the request’s row in Usage Insights shows the scanner and its score. To loosen the check for one kind of content without turning it off, use the per-role controls in the next section (for example Log only for tool results), or raise that scanner’s Exact threshold in Settings → Company Settings → Prompt Guards.
Can a tool result be judged differently from what a user typed?
Section titled “Can a tool result be judged differently from what a user typed?”Yes, and for an agent that calls tools this is usually the setting that matters. Each role is screened separately, and each scanner can be given its own action and its own threshold for tool content.
Why it exists: a customer running a ReAct agent measured Prompt Injection blocking 5 of 33 calls made right after a tool result — a list of hospitals, a database query result, a style guide — scoring 0.914 to 0.997 against a 0.8 threshold, while the same payloads passed 3 out of 3 when sent on their own. A classifier scores the text it is handed, so a tool result judged as part of the turn that preceded it is not being judged as a tool result at all.
Both controls live in Settings → Company Settings → Prompt Guards, on each scanner’s card:
| Control | What it does | Blank / unset means |
|---|---|---|
| Tool results | The action for tool content — Block or Log only | Same as Above — follows the scanner’s Action just above it, with the resulting action in brackets |
| Tool threshold | The sensitivity for tool content, 0.00–1.00 |
same as above — uses the scanner’s Exact threshold (the hint quotes the number) |
Each card’s header shows two selectors under the scanner name: Action — what the scanner does for all content (Global follows the Block on violation switch) — and Tool results, an exception for content a tool returned.
The two remedies are different and you can use either, or both. Set Prompt
Injection to Log only for tool results when you want the finding recorded
without the request being refused; raise its Tool threshold to 0.97 when
you want it still blocking, just at a higher bar for content a tool returned.
Secret Detection can stay at Block for both roles throughout — before this
existed, the only way to stop injection false positives on tool output was to
stop scanning tool content entirely, which took Secret Detection with it.
Over the API, if you prefer:
curl -s -X POST https://<your-agentwatch-host>/api/admin/company/settings \ -H "Authorization: Bearer <tenant-admin JWT>" \ -H "Content-Type: application/json" \ -d '{ "guardrailsRoleActions": "{\"tool\":{\"prompt-injection\":\"log-only\"}}", "guardrailsRoleThresholds": "{\"tool\":{\"prompt-injection\":0.97}}" }'Use the scanner names — prompt-injection, toxicity, gibberish,
invisible-text, bias, secrets, ban-topics — not the labels on screen.
You should then see a blocked request’s finding name the role that produced it, so “Prompt Injection blocked this request” tells you whether a person typed an injection or a tool returned something that merely reads like one.
⚠ Nothing changes until you set it. With no per-role entries every scanner
resolves exactly as it does today, so upgrading cannot alter your enforcement.
Leaving Tool threshold blank is how you say “no override” — it is not the
same as 0, which would match everything that scanner sees.
| Symptom | Cause | Fix |
|---|---|---|
| Tool results blocked as injection, same text passes alone | roles were scored as one joined string | upgrade to 2.0.330 or later; each role is scored on its own |
/v1/responses refuses an ordinary prompt as prompt-injection (score near 1.000), the same text is served on chat |
the guardrail judged the DLP-redacted text, and the classifier reads a […-REDACTED] marker as an injection |
upgrade to 2.0.372 or later; guardrails judge the original text |
| A per-role setting saves but changes nothing | the key used a console label (injection, topics) instead of a scanner name |
use prompt-injection, ban-topics |
| Every tool result refused right after setting a threshold | 0 was stored as the tool threshold; 0 matches everything |
clear the box to inherit, or set the bar you actually want |
| The setting applies to one tenant only | settings resolve per organization | repeat it per tenant, or use Apply to all organizations |
Can different users or agents have different DLP and guardrail policies?
Section titled “Can different users or agents have different DLP and guardrail policies?”Yes — give each policy group its own tenant (organization), and give the
agents in that group a service-account API key from that tenant. DLP and
guardrail settings belong to an organization, and every request is screened
with the settings of the organization its API key belongs to. A key can never
reach another organization’s policy, and delegated identity (the
X-AgentWatch-User header) attributes requests only to employees inside the
key’s own organization — so the boundary holds even when one service key acts
for many employees.
A typical split:
| Group | Tenant | Policy |
|---|---|---|
| Customer-support agents | acme-support |
DLP Block, all guardrails blocking |
| Engineering coding agents | acme-engineering |
DLP Redact, Gibberish off, Prompt Injection blocking |
| Data-science notebooks | acme-research |
DLP Detect only, guardrails log-only |
Steps
- Platform Admin → Tenants → New Organization — one per policy group (platform administrator).
- Sign in to the new organization and open Settings → Company Policy → Data Protection and Settings → Company Policy → Guardrails. Choose the policy and click Save — even if you keep the defaults. ⚠ Until a tenant has saved its own settings it uses the platform defaults, so saving once is what gives it a policy of its own.
- Employee Management → Add Service Account Key in that organization —
for example
support-agents@acme.com(see Delegated identity). Add the employees the agents act for, or set the key type to Service — Auto Create Users with your domain. - Point that group’s agents at the gateway with that key. Each request may name the employee it acts for:
curl https://<your-agentwatch-host>/v1/chat/completions \ -H "Authorization: Bearer ak_<support-service-key>" \ -H "X-AgentWatch-User: jane.doe@acme.com" \ -H "Content-Type: application/json" \ -d '{"model":"gpt-4o","messages":[{"role":"user","content":"Customer SSN is 123-45-6789"}]}'Verify: with acme-support set to Block, the request above is refused with
403; the same request with the acme-research key is served and appears in
that tenant’s Usage Insights as Detected. Each tenant’s Usage Insights,
budgets and audit log show only its own traffic.
What is shared and what is not. Model catalog, providers, gateway rules and tenant routing are configured per organization too, so set them up in each tenant (a platform administrator can push platform defaults to every tenant with Apply to all organizations — see Configuration). Organization-wide custom checks — Custom Regex Guardrails, the External Guardrail Webhook, Guardrail Plugins and allow/deny lists — are likewise per tenant, so each group can have its own.
How do I check what the WAF is catching?
Section titled “How do I check what the WAF is catching?”curl -s https://<your-agentwatch-host>/api/admin/waf/stats \ -H "Authorization: Bearer <tenant-admin JWT>"curl -s https://<your-agentwatch-host>/api/admin/waf/violations \ -H "Authorization: Bearer <tenant-admin JWT>"Which endpoints are screened?
Section titled “Which endpoints are screened?”Every endpoint that carries caller-supplied text to a model. The text lives in a different field depending on the endpoint, so the answer is worth stating explicitly rather than assuming:
| Endpoint | Screened field(s) |
|---|---|
/v1/chat/completions, /v1/messages |
the full messages[] conversation |
/v1/responses |
input |
/v1/embeddings |
input |
/v1/moderations |
input |
/v1/images/generations, /edits, /variations |
prompt |
/v1/audio/transcriptions, /translations |
prompt (the optional decoding hint) |
/v1/rerank |
query and documents |
Both data protection and the content guardrails run on all of these, through the same engine and the same thresholds — a prompt that chat refuses is refused here too.
Two limits worth knowing:
- Uploaded file content is not scanned. On the multipart endpoints the audio or image file is forwarded to the provider byte-for-byte. It is the accompanying text fields that are screened, not the media itself.
- Model output is screened wherever it is text, if you turn it on — see the next section.
⚠ Before 2.0.273 the two audio endpoints and /v1/images/edits and
/variations were not screened at all, because their multipart bodies were
never parsed into the fields the scanners read. If you rely on screening for
those endpoints, 2.0.273 is the release that gives it to you.
Is the model’s response screened, or only my prompt?
Section titled “Is the model’s response screened, or only my prompt?”Both, but response screening is opt-in. Enable Scan responses under
Settings → Company Policy → Data Protection (dlpScanResponses). It is off
by default because screening a streamed response means buffering it, which
changes latency for every caller on that path — that should be your decision,
not ours.
With it on, the same engine and the same policy that inspect your prompt also inspect what comes back:
| Response | What is screened |
|---|---|
/v1/chat/completions, /v1/messages |
the message content and outbound tool-call arguments — streaming included |
/v1/responses |
the generated output |
/v1/audio/transcriptions, /translations |
the transcribed text |
/v1/images/* |
the provider’s rewritten prompt, where it returns one |
| MCP tool calls | the tool’s result, not just its arguments |
Tool-call arguments matter as much as prose: a model can carry data out by emitting a tool call whose arguments hold it. When a response is blocked, those outbound tool calls are dropped as well, so the client cannot execute the very call the block existed to stop.
A blocked response keeps its normal shape — the text field is replaced with a withheld-by-policy notice rather than the call failing — so an application parsing that field does not need to special-case a policy refusal. The row appears in Usage Insights as blocked or redacted, never as a plain success.
⚠ Not screened: the uploaded file on a multipart request (audio and image bytes are forwarded verbatim), embedding vectors, and image bytes. There is no text in them to police, and scanning base64 would produce coincidental matches rather than findings.
⚠ Response screening on the extended endpoints — transcription, translation and
image prompts — arrived in 2.0.275. Before that the setting governed chat,
/v1/responses and MCP results only.
Why was my request blocked?
Section titled “Why was my request blocked?”| Response | Cause | Fix |
|---|---|---|
403 dlp_blocked |
A finding in the selected risk levels, and the action is Block | Read the categories in the reason (dlp: PCI); allow-list the value, lower the sensitivity, switch to Redact, or remove the data |
| An IP address or ZIP code refused | Block sensitivity is High (stored threshold below 4, including the default 0), which blocks low-risk regex findings | Choose Medium or Low |
A name or place shows as [NAME-REDACTED] / [LOCATION-REDACTED] in a Block org |
Names and places never block; Block redacts them instead | Expected. Turn Advanced NER off if you do not want them masked |
403 guardrail message |
A scanner scored over its threshold | Raise that scanner’s threshold, or disable the scanner |
| Legitimate text blocked as gibberish | mild gibberish is enabled |
Turn it off — structured text is not fluent prose |
| Nothing blocked when you expect it | DLP is off, or the category is disabled | Check dlpEnabled and the category switches; confirm with the probe above |
| A scanner reports “degraded” | Its model file did not load | Check /admin/dlp/ner/status; model-backed scanners download at runtime and need egress |
My application gets invalid JSON when a request is blocked. Why?
Section titled “My application gets invalid JSON when a request is blocked. Why?”Because a block is not an error — it is a successful HTTP 200 carrying a refusal. That is deliberate: the request was handled, and a chat client should show the user why it was refused rather than a stack trace.
For a program that expects JSON, prose in the content field is indistinguishable from the model emitting garbage, and the 200 means your error handling never runs. So a refusal now honours the format you asked for.
Send response_format and the refusal comes back parseable:
{ "error": { "code": "dlp_blocked", "type": "policy_violation", "message": "Your request was blocked by a data-protection policy. Detected: secrets.", "categories": ["secrets"], "blocked_by": "agentwatch" }}Callers that do not send response_format keep the exact prose they have
always received — nothing changes for an existing chat integration.
How to tell a block from a model problem, using the response you already have:
finish_reason |
What happened | What to do |
|---|---|---|
content_filter |
AgentWatch blocked it. The content is a refusal, not model output | Check Settings → Audit Logs and the Security page for the matching event |
length |
The answer was truncated — it hit an output cap | See below |
stop |
The model finished normally | The content is genuinely what the model produced |
The response also carries guardrail_blocked, dlp_blocked or the equivalent
flag, so a client can branch on a refusal without parsing text.
A long answer is being cut off. What is capping it?
Section titled “A long answer is being cut off. What is capping it?”AgentWatch runs a context-fit preflight on every request that lowers
max_tokens to what actually fits:
granted = min(your max_tokens, context window − input − safety margin, the model's output ceiling)It only ever lowers the value, never raises it, and it never invents one —
a request that sends no max_tokens is untouched.
The figure most likely to be wrong is the model’s output ceiling, which comes from the catalog. If that is stale you can be capped with a large context window and a short prompt, and nothing looks wrong: a JSON answer simply stops mid-object and fails to parse.
Check it:
# What the gateway would grant for this exact payloadcurl -s -X POST "https://<your-agentwatch-host>/v1/messages/count_tokens" \ -H "Authorization: Bearer <your ak_ key>" -H 'Content-Type: application/json' \ -d '{"model":"<your-model>","messages":[{"role":"user","content":"..."}]}'Compare granted_max_tokens with what you send. A clamp is also logged as
action: "context-fit-clamped" (with requested vs granted) and tagged on the
usage row as max_tokens_clamped, so you can find affected requests in Usage
Insights.
Fix it by correcting the model’s limits under Settings → Models → Edit,
or switch the preflight off entirely with AW_CTX_FIT=0 on the gateway, which
forwards your max_tokens exactly as sent.
Which separators does detection understand?
Section titled “Which separators does detection understand?”Identifiers are written many ways, and until 2.0.289 only two of them were
recognised. 123-45-6789 and 123 45 6789 were detected; 123.45.6789 and
123,45,6789 were not — the same nine digits in the same sentence.
Grouped-digit identifiers now accept hyphen, space, dot, comma and underscore:
| Identifier | Separators accepted |
|---|---|
| Credit card numbers | hyphen, space, dot, comma, underscore, slash |
| SSN, ITIN, EIN | hyphen, space, dot, comma, underscore — not slash |
Why cards take a slash and the others do not. A card number is verified with
the Luhn checksum, so a wide separator set costs nothing: anything that is not
really a card is re-rated automatically. SSN, ITIN and EIN have no checksum, so
whatever the pattern accepts is refused outright at critical severity — and
slash-grouped part numbers and SKUs (Part number 452/12/8899,
SKU 100/25/4321) are ordinary business text. We chose not to refuse those.
The consequence, stated plainly: an SSN written with slashes
(123/45/6789) is not detected. If your data genuinely contains SSNs in that
form, add a custom pattern for it.
Why was ordinary business text being blocked?
Section titled “Why was ordinary business text being blocked?”Three patterns were too broad before 2.0.289, and in Block mode at the default (High) sensitivity a single finding refuses the request:
| Text | Was matched as | Now |
|---|---|---|
The build number is 20260923. |
US passport (critical) and a phone number | Not matched — passport detection requires the word passport nearby |
Invoice 44219 was paid on Tuesday. |
ZIP code | Not matched — ZIP needs a label, ZIP+4, or a state abbreviation |
Serial 100234567 |
a phone number | Not matched — a phone number is no longer found inside a longer run of digits |
If you had raised the sensitivity setting or disabled patterns to work around this, you can return to the defaults after upgrading.
What changed in 2.0.366?
Section titled “What changed in 2.0.366?”A health-care word that describes a product or business (patient portal, clinic software, hospital analytics, medical billing platform) no longer makes a prompt clinical, so “the fever chart in the patient portal” is left alone. Clinical notes are still masked, as is a symptom reported through such a product — complains of, presented with and I have had always count.
What changed in 2.0.365?
Section titled “What changed in 2.0.365?”| Text | Before | Now |
|---|---|---|
Dx: CHF, anxiety disorder, chest pain in a clinical note |
sent | masked. Symptoms and named conditions count only when the text is clinical (a patient, Dr., a dose, an MRN…), so the deploy was a real headache is left alone |
Order reference 4111 1111 1111 1112 |
masked as a card | left alone: it fails the card checksum and follows a reference label. Either alone still counts as card data |
NM1*IL*1*DOE*JANE****MI*W123456789~DMG*D8*19800101*F~ |
refused by the Gibberish guard | name, member ID and DOB masked field by field. The Gibberish guard skips a prompt that is entirely X12 or JSON |
Remind Olivia Brown |
[NAME-REDACTED] swallowed Remind |
Remind [NAME-REDACTED] |
Tom has diabetes and asthma, treated for pneumonia |
sent | masked — common conditions count when a person is said to have them, or in a clinical text. Cancer research funding and a diabetes awareness walk are left alone |
HL7 PID segment; JSON "dob": "…", "first_name": "…" |
sent | masked field by field. A name field must look like a person’s name, and a bare "name" key is not used at all (it names services, clusters and streets as often as people), so "name": "Production Cluster" and "customer_name": "Acme Corp" are left alone |
Joy and Rich, Rose, Ivy and Daisy, sarah and raj (Advanced NER on) |
only some names masked | every name in the list masked |
a DNS fault, Python uses indentation |
read as organizations | left alone |
Grace and Hope, Contoso uses …, manager at Fabrikam (Advanced NER on) |
names missed | masked as names / organizations |
System messages are now screened for high-risk data. Before 2.0.365 the
system prompt was skipped entirely, so system: "Customer SSN 123-45-6789 …"
reached the provider unscreened. It is now scanned, but only high-tier
findings count there (SSNs, card and bank numbers, medical IDs, credentials).
Names, emails, dates and the rest stay out of scope in a system prompt, because
it routinely carries code, configuration and documentation. If your system
prompt embeds a live card number or credential, that request is now handled by
your DLP policy like any user message.
Settings apply on the next request. Every settings save now clears the
gateway’s cached copy (before, only the main Company Settings save did), and
PATCH /api/admin/dlp/policies writes your own organization — before 2.0.365 it
wrote the platform row, so re-check your policy if you used it.
Platform settings are visible. An API the platform administrator disabled
answers disabled_by: "platform" with a message that says so; the semantic
cache, which runs from the platform’s setting for every organization, now shows
the platform’s value; rate-limit panels show your organization’s own numbers.
Sources and references
Section titled “Sources and references”- Compliance framework activation: Security and compliance.
- Governing agent behavior: Agent fleet and insider risk.