Security Guardrails
Every chat, message and Responses request is scanned before it is proxied. The scanners run inside the gateway, in parallel, and they run before the cache, before HiveState and before request logging — so a prompt that must not reach a provider is stopped before anything else has seen it, and a cached answer can never be returned in place of a refusal.
The policies
| Policy | Name in the dashboard | What it does |
|---|---|---|
prompt-injection | Prompt Injection | Refuses jailbreaks and attempts to override the system prompt |
toxicity | Toxicity Check | Refuses toxic or harmful content |
sensitive-data | PII Redaction | Replaces PII with [ENTITY] placeholders and forwards the request. Still refuses on secrets |
sensitive-data-block | PII Blocking | Refuses with 403 and forwards nothing |
abuse-protection | Cost & Abuse Protection | Blocks flooding, oversized prompts and nonsense |
Redaction and blocking are two policies, not one setting
They read the same PII. They differ only in what they do with it.
PII Redaction rewrites the value and lets the request through. The model
sees [EMAIL_ADDRESS] where the address was:
Scrivimi a [email protected] → Scrivimi a [EMAIL_ADDRESS]
PII Blocking refuses the request outright:
{"error": {"message": "PII detected: EMAIL_ADDRESS",
"type": "content_policy_violation",
"code": "guardrail_blocked"}}
Both can be on at once, and that is not a contradiction: blocking is evaluated first, so it wins and nothing is redacted-and-sent behind it. The dashboard says so on the card when both are active.
A tenant that has never chosen it does not get it, even though the gateway offers it. Redaction is the default; moving from rewriting a prompt to refusing it is a decision a tenant makes deliberately, not one that arrives with a deployment.
Secrets are always refused, never redacted
sensitive-data is called PII Redaction because that is what it does to PII.
An API key is not a PII entity: replacing it with a placeholder is not what
anyone means by protecting it. So the same policy still returns 403 when it
finds an AWS key, a GitHub token, an OpenAI key or a private key block.
Which entities are looked for
The selection is shared by both PII policies and is configured in the dashboard under Security → PII Redaction → Configure:
| Entity | Recognises |
|---|---|
EMAIL_ADDRESS | Email addresses |
PHONE_NUMBER | International (+39, 0039) and Italian mobile numbers |
PERSON | A name introduced by a phrase — mi chiamo…, my name is…, Sig., Dr. |
CREDIT_CARD | Card numbers, validated with Luhn |
CRYPTO | Bitcoin and Ethereum wallet addresses |
IBAN_CODE | Italian and other European IBANs |
IP_ADDRESS | IPv4 and IPv6 |
US_SSN | US social security numbers |
US_BANK_NUMBER | US account numbers, introduced by a phrase |
LOCATION | A place introduced by a phrase — residente a…, based in… |
Two more are always on and are not in the list, because they predate it and are not selectable: codice fiscale (checksum-validated) and partita IVA. Unticking every box does not switch those off.
A tenant that has never opened the dialog has no selection stored, and every entity applies. An empty selection means the opposite — nothing is redacted — so the two states are not the same.
What the scanner reads
Whatever the model can see, on every request shape the gateway serves:
- chat messages, as plain strings or content blocks;
- Anthropic's top-level
systemfield; - the Responses API
instructionsandinput; - the legacy Completions
prompt; - tool results —
tool_resultblocks andfunction_call_outputitems.
That last one matters more than it looks. A document does not usually arrive as a typed user message: an agent reads the file and its contents come back as a tool result. Pasting a file into the chat and letting an agent read it are the same exposure, and both are covered.
Redaction rewrites exactly what the scanner reads, and nothing else: the
envelope of a tool result — tool_use_id, call_id, block types — survives
untouched, so the provider can still match a result to its call.
Where it runs
PII and secrets are handled first, before any component can observe or persist the prompt. The remaining scanners sit under the rate limiter and the concurrency semaphore — the toxicity scanner calls a model, and that call has to be counted and bounded like any other work — but still ahead of the cache.
Per-request override
x-hive-guard selects the policies for one request, ignoring the tenant's
configuration. It can narrow what runs; it cannot widen which entities the PII
policies cover.
# no guardrail at all
curl https://api.modelhive.ai/v1/chat/completions \
-H "Authorization: Bearer sk-mh-..." \
-H "x-hive-guard: none" \
-d '{"model": "gpt-4o", "messages": [...]}'
# only these two, whatever the tenant has enabled
curl https://api.modelhive.ai/v1/chat/completions \
-H "Authorization: Bearer sk-mh-..." \
-H "x-hive-guard: prompt-injection,sensitive-data-block" \
-d '{"model": "gpt-4o", "messages": [...]}'
| Value | Effect |
|---|---|
none | Every policy off for this request |
prompt-injection | Injection only |
toxicity | Toxicity only |
sensitive-data | PII redacted, secrets refused |
sensitive-data-block | PII refused |
abuse-protection | Abuse limits only |
Send x-hive-cache: false alongside x-hive-guard when you are checking
whether a policy fires. A cached answer is a legitimate response, and on a
prompt you have already sent it will arrive without the request needing to be
scanned again — which reads like the guardrail failing when it is the cache
succeeding.
Latency
The scanners run in parallel and add roughly 25–35 ms, against LLM responses
measured in seconds. The exception is toxicity, which calls a small model: it is
billed to the calling key as @hiveguard spend and is visible in analytics.