Most agent security work today focuses on one problem: blocking bad inputs.
Prompt injection detection, jailbreak resistance, output filtering. Those things matter. But they're all solving for external threats — a malicious user trying to manipulate the agent.
There's a different problem that gets less attention: the agent acting outside its intended scope with no malicious input involved at all. No attack. No injection. Just an agent that can do more than it should, because nothing explicitly told it otherwise.
A guardrail that fires on bad inputs can't help you there.
The gap between what your agent can do and what it should do
Imagine an agent handling customer support. It can answer questions, search a knowledge base, open tickets. It also happens to have access to billing tools, because the same API key that grants it those three things grants it everything else too.
A user asks for a $50,000 refund.
The input isn't malicious. The agent isn't being attacked. Your prompt injection detector sees nothing wrong. The agent processes the refund — because it could, and nothing stopped it.
This is a scope problem, not a security problem in the traditional sense. The agent had more power than it needed, and there was no cryptographic enforcement of the boundary between what it was supposed to do and what it was capable of doing.
Scope enforcement isn't about blocking attacks. It's about making the agent's authorization explicit, signed, and enforced at runtime — so the boundary exists in the protocol, not just in the prompt.
What Mandate is
Mandate is a PQ-Sign feature — part of the FIPSign suite — that issues signed session credentials for agents, devices, and services. It defines what an agent is authorized to do, with what budget, for how long, and gives you real-time control over all of it.
It has two layers:
Immutable layer — covered by the ML-DSA signature at issuance: agentId, issuedBy, scopeOriginal, budgetTotal, expiresAt. These fields cannot be altered without invalidating the signature. They are the cryptographic record of what was authorized.
Mutable layer — stored separately from the token, updated in real time: scopeCurrent, budgetConsumed, status. You can narrow the scope, suspend the agent, resume it, or revoke it permanently — at any time, using only the mandate ID.
The agent cannot write to either layer. The operator controls both.
How it works in practice
Step 1 — Emit a mandate
When the agent session starts, emit a mandate with the exact scope it needs:
curl -s -X POST https://api.fipsign.dev/mandate \
-H "X-API-Key: pqa_your_key" \
-H "Content-Type: application/json" \
-d '{
"agentId": "agent-support-v2",
"issuedBy": "operator@empresa.com",
"scope": ["answer_questions", "search_kb", "open_ticket"],
"budgetTotal": 500,
"expiresInSeconds": 28800
}'
Response:
{
"success": true,
"mandate": {
"id": "mdt_1418d1072c503dd20ef9416f347d19b9",
"scope": ["answer_questions", "search_kb", "open_ticket"],
"budgetTotal": 500,
"expiresAt": 1785694539,
"status": "active",
"token": {
"payload": "eyJzdWIiOiJtZHRf...",
"signature": "RspqteGlIfLjtXx...",
"algorithm": "ML-DSA-65",
"issuedAt": 1785690939
}
}
}
The token goes to the agent. The mandate ID stays with the operator.
Step 2 — Agent verifies before acting
Before the agent executes any action, it calls POST /mandate/verify:
curl -s -X POST https://api.fipsign.dev/mandate/verify \
-H "X-API-Key: pqa_your_key" \
-H "Content-Type: application/json" \
-d '{
"token": { "payload": "eyJzdWIi...", "signature": "Rspqte...", "algorithm": "ML-DSA-65", "issuedAt": 1785690939 },
"action": "open_ticket",
"cost": 5
}'
Granted:
{
"success": true,
"result": "granted",
"actionMatched": "open_ticket",
"budgetRemaining": 495,
"expiresInSeconds": 3600
}
If the agent tries process_refund — which isn't in the scope — it gets denied immediately, before the action reaches any downstream system:
{
"success": false,
"result": "denied",
"reason": "scope_not_authorized",
"authorizedScope": ["answer_questions", "search_kb", "open_ticket"]
}
No malicious input. No attack. The agent simply can't act outside what the mandate signed for.
Important: denied responses don't consume tokens — billing only happens on granted verifications.
Step 3 — Control the mandate in real time
If the agent starts behaving unexpectedly, you don't need to kill the process or rotate API keys. You control the mandate directly using only its ID:
# Suspend immediately — agent can't act until resumed
curl -s -X PATCH https://api.fipsign.dev/mandate/mdt_1418d1072c503dd20ef9416f347d19b9 \
-H "X-API-Key: pqa_your_key" \
-H "Content-Type: application/json" \
-d '{"action": "suspend"}'
# Narrow the scope — permanently reduce what it can do
curl -s -X PATCH https://api.fipsign.dev/mandate/mdt_1418d1072c503dd20ef9416f347d19b9 \
-H "X-API-Key: pqa_your_key" \
-H "Content-Type: application/json" \
-d '{"action": "narrow", "scope": ["answer_questions"]}'
# Revoke permanently — irreversible
curl -s -X PATCH https://api.fipsign.dev/mandate/mdt_1418d1072c503dd20ef9416f347d19b9 \
-H "X-API-Key: pqa_your_key" \
-H "Content-Type: application/json" \
-d '{"action": "revoke"}'
After a narrow, POST /mandate/verify enforces the new scope immediately. The agent still holds the original token — but the token is only half the check. The live state is the other half, and you control that.
After a revoke, every subsequent verify call returns mandate_revoked — regardless of what the token says. The agent is permanently blocked without needing to touch the agent itself.
All PATCH operations are free — no token cost. Control operations shouldn't be penalized.
The verification logic
POST /mandate/verify runs two checks in order:
First — ML-DSA signature: the server verifies the token's cryptographic integrity. If the token was tampered with, belongs to a different project, or isn't a mandate token, it returns invalid_signature immediately. No billing, no state read.
Second — live KV state: if the signature passes, the server reads the current state — status, scopeCurrent, budgetConsumed. If the mandate is suspended, revoked, expired, or the requested action isn't in scopeCurrent, it returns denied with the specific reason.
This ordering matters: a tampered token is rejected before the system reads anything. A valid token against a revoked mandate is rejected at the state check. Both rejections are free.
Inspecting mandate state
At any point, you can read the current state of a mandate:
curl -s https://api.fipsign.dev/mandate/mdt_1418d1072c503dd20ef9416f347d19b9 \
-H "X-API-Key: pqa_your_key"
{
"success": true,
"mandate": {
"id": "mdt_1418d1072c503dd20ef9416f347d19b9",
"agentId": "agent-support-v2",
"issuedBy": "operator@empresa.com",
"scopeOriginal": ["answer_questions", "search_kb", "open_ticket"],
"scopeCurrent": ["answer_questions"],
"budgetTotal": 500,
"budgetConsumed": 45,
"budgetRemaining": 455,
"status": "active",
"expiresInSeconds": 3131,
"updatedAt": 1785691318
}
}
scopeOriginal is what was signed at issuance — the immutable record. scopeCurrent is what's enforced now. The difference between the two is the audit trail of narrows.
What this is not
Mandate is not a replacement for input security. Prompt injection detection, jailbreak resistance, and output filtering all still matter — they're solving for external attacks on the agent. Mandate solves for the agent's authorization envelope: what it's allowed to do by design, enforced at the protocol level.
The two layers are complementary. A guardrail that fires on bad inputs tells you the attack was blocked. A mandate tells you the agent couldn't have done the thing even if the guardrail had missed.
Limits and costs
| Endpoint | Cost |
|---|---|
POST /mandate |
1 token |
POST /mandate/verify — granted |
1 token |
POST /mandate/verify — denied |
0 tokens |
PATCH /mandate/:id |
0 tokens |
GET /mandate/:id |
0 tokens |
GET /mandate |
0 tokens |
Field limits: agentId max 128 chars, scope 1–20 items of max 64 chars each, expiresInSeconds between 60 and 2,592,000 (30 days).
Budget is abstract — units mean whatever you define. Dollars, API calls, operations, credits. Pass budgetTotal: 0 to disable budget enforcement entirely.
The post-quantum angle
Mandate signs with ML-DSA (NIST FIPS 204) — the same algorithm as the rest of PQ-Sign. RSA and ECDSA will be broken by quantum computers within this decade. Building agent authorization on algorithms that will hold is the same argument as building any other cryptographic infrastructure correctly from the start.
The migration cost later is higher than doing it right today.
Get started
Free tier at app.fipsign.dev — 10,000 operations per month, no credit card. Create a project, copy your API key, and emit your first mandate in one curl call.
Full reference at fipsign.dev/guide — Mandate tab.
Top comments (0)