Skip to main content
When an agent is hijacked — prompt injection, a poisoned tool result, a runaway loop — what it can actually do is bounded by exactly one thing: what its API key was allowed to do. A key with no limits and no policy turns one compromised agent into a workspace-wide incident. This page is the hardening pass you run against every key before it ships, and again on a cadence after. It is deliberately short: one checklist, one worked example, then links to the depth for each field. For the mental model behind it, start with Scope & keys; for the field-by-field reference, see the keys overview.

1. The least agency checklist

Walk every key — new or existing — through these six gates in the key editor (/console/token). Setting any of them requires the Developer role or above; the two policy planes (§5–6) are authored separately and bound here.
Set model_limits to the exact list this agent needs (and enable model_limits_enabled). A call to any model outside the list is rejected before it leaves the gateway, so a hijacked agent can’t escalate to a pricier or more capable model. Check: is the list as short as the job allows — ideally one model? Depth: Model limits.
Set allow_ips to the source addresses or CIDR the agent actually calls from. A leaked key presented from anywhere else is rejected at the auth layer. Empty means all IPs are allowed. Check: for a fixed-host or scheduled agent, is the list non-empty and scoped to that egress? Depth: IP allow-list.
Set credit_limit_usd to a ceiling the agent should never cross in its lifetime. The gateway enforces it against the key’s spend. 0 means unlimited — a runaway loop can drain your whole balance. Check: is the cap a real budget, not 0? Depth: Quota, cap & expiry.
Set expired_time to an absolute expiry — the end of the sprint, the deployment, or the CI run. -1 means never expires. A short-lived key can’t linger as forgotten attack surface. Check: does an ephemeral or contractor key have a real expiry, not -1? Depth: Expiring keys.
Attach a guardrail via guardrail_id so the request (and, where supported, the response) text is screened for PII, secrets, and injection intent before it reaches the model. Check: does a key that handles sensitive prompts have a guardrail bound, or inherit a workspace default? See §5.
Attach a firewall policy via firewall_policy_id so every tool call, MCP dispatch, and egress this key issues is evaluated against an allow-list of what the agent legitimately needs. Check: does an agent that calls tools have a firewall policy bound, or inherit the workspace default? See §6.
The fields above are the only customer-configurable levers on a key — set them all in the console; nothing here requires an agent-code change. Reading back a key shows it masked; the plaintext is shown once at creation. See Key masking.

2. What / how often / where

Three questions turn the checklist from a one-time chore into a posture.

What

The six gates above, in order: model_limitsallow_ipscredit_limit_usdexpired_timeguardrail_idfirewall_policy_id.

How often

On every key at creation, and on a recurring review — when an agent’s scope changes, when you rotate a key, and on a fixed cadence for long-lived keys.

Where

In the console key editor (/console/token), as a Developer+. The two policies are authored in their own consoles, then bound on the key.

3. One concrete least-agency key

A scheduled agent that summarizes support tickets with one cheap model, from one host, needs almost no agency. A fully hardened key:
FieldValueWhy
model_limitsone summarization modelcan’t escalate to a frontier model
allow_ipsthe scheduler’s egress CIDRa leaked key is useless elsewhere
credit_limit_usda weekly ceilinga runaway loop can’t drain the balance
expired_timeend of the deploymentauto-expires, can’t linger
guardrail_ida PII-masking guardrailrequest text is screened
firewall_policy_idallow-lists only its toolsno surprise tool calls
If this agent is hijacked, it can still only call one model, only from one IP range, only up to its cap, and only the tools its firewall policy permits. The rest of the workspace is untouched — and the firewall audit trail shows exactly what it was authorized to do.
A key with no model_limits, no allow_ips, credit_limit_usd: 0, expired_time: -1, and no policy attachment has maximum agency. If it leaks, the holder gets your full workspace. Treat that combination as a finding, not a default — see unlimited vs bounded.

4. The /v1 relay call vs the console

The checklist is configured in the console with your session (a Developer+ user). Your agent never touches those config routes — it presents its scoped relay key (sk-orca-…) on the /v1/* inference calls, and the limits and bound policies above are enforced on each one.
# The agent's runtime call — the relay key, scoped by the checklist above.
curl https://api.orcarouter.ai/v1/chat/completions \
  -H "Authorization: Bearer sk-orca-..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-4o-mini",
    "messages": [{"role": "user", "content": "Summarize this ticket..."}]
  }'
If the key’s model_limits doesn’t include openai/gpt-4o-mini, this call is rejected before it leaves the gateway. If the caller’s IP isn’t in allow_ips, it’s rejected at the auth layer. The agent code stays the same; the key decides the blast radius.

5. Gate 5 — the bound guardrail

guardrail_id binds a workspace-scoped, ordered content policy to the key. Resolution is the key’s explicit guardrail (if it exists and is enabled), else the workspace default, else none.
Guardrails are a strict off-switch when disabled: a disabled or deleted guardrail_id means the key gets no guardrail — it does not fall back to the workspace default. This is the opposite of the firewall plane (§6), so verify the bound guardrail is enabled, not just attached.
A guardrail’s rules run before the model (input stage) and, where supported, on the response (output stage), with actions block, mask, or flag. The PII Shield preset, for instance, masks PII in the request before it ever reaches the model. Author and attach guardrails as Developer+ — see Guardrails and Bind policies.

6. Gate 6 — the bound firewall policy + gateway scope

firewall_policy_id binds a workspace-scoped tool-call policy. It governs the actions an agent takes — advertised tools, model-emitted tool_calls, MCP dispatches, and outbound egress — against an ordered rule list whose verdicts are allow, audit, deny, sanitize, pending_approval, or cap_cost.
The firewall plane resolves differently from guardrails: a disabled attached firewall policy falls back to the workspace default, it does not turn enforcement off. So binding a policy and disabling it reverts the key to the workspace default — it never silently goes unprotected.
The fastest way to set both planes at once is an autonomy level — a single switch that atomically configures your workspace’s firewall and guardrail posture (tight / balanced / permissive), with one-click undo. See Firewall §8.
is_firewall_gateway is a separate kind of key — minted only for the Firewall MCP and evaluate-hook routes (/api/v1/firewall/*), never for inference. A regular key gets 403 on those routes, and a gateway key on the inference path is over-scoped. Enabling the flag, and reading a gateway key’s plaintext, requires Admin+. One key, one purpose.

7. After the checklist

Secure-agents baseline

The recommended starting posture — one autonomy switch, then tune from real traffic.

Bind policies

How guardrail_id and firewall_policy_id attach and resolve.

Excessive agency

The threat this checklist is built to contain.

Leaked key

What to do the moment a scoped key is exposed.
The narrower each key, the smaller the blast radius if any one agent is compromised — and the clearer the record of what each agent was authorized to do. Run the least agency checklist on every key, and keep running it.