Veritrellis
authorization ai-agents security

Why agents need permits, not prompts

Prompt-level constraints shape what an agent says. They do nothing at the moment it moves money. A permit is the authorization that runs before the action executes.

Veritrellis Team

Most teams begin agent safety at the prompt. They write careful system instructions, add a planning step, and filter tool calls. All of that improves behavior. None of it stops execution. The instant your agent calls a refund API, edits a subscription, or sends a signed contract, the prompt is already behind you and a real side effect is about to happen.

The question that matters at that instant is not "what did the model decide to do?" It is "was this specific action explicitly authorized?" Those are different questions, and only the second one is enforceable.

Intent is not authorization

A language model produces intent. Intent is probabilistic, context-dependent, and trivial to influence with the right input. Treating model output as permission to act means your authorization decision inherits every weakness of the model that produced it. A prompt injection, a confused tool description, or an unlucky sample is enough to turn "draft a refund" into "issue a refund."

Authorization has to live somewhere the model cannot talk its way past. That place is the execution boundary — the line between deciding to act and actually acting.

The execution boundary

Veritrellis sits on that line. Before a high-risk action runs, your application asks for authorization:

const result = await client.authorizeRequest({
  action_type: "issue_refund",
  resource_ref: "cus_123",
  payload: { amount: 350, currency: "USD" }
});

The response is one of three outcomes: allowed, denied, or pending approval. An allowed response carries a signed permit — an ES256 JWT scoped to that action, that resource, and a short expiry window. Denied and expired requests never produce a permit.

What a permit actually is

A permit is not a log line and not a status flag. It is a cryptographic statement, signed by the authorization boundary, that a named action on a named resource was allowed at a point in time. Because it is signed, your downstream service can verify it without trusting the caller, the network, or the model:

const claims = await verifyPermit({
  permitJwt: result.permit,
  workspaceId: process.env.VERITRELLIS_WORKSPACE_ID,
  issuer: "https://api.veritrellis.ai"
});
// only now do you move the money
await issueRefund(claims.resource_ref);

Verification checks the signature against the published JWKS, the issuer, the audience, and the expiry. If any check fails, you do not have authorization — you have a string.

No valid permit, no action

This is the whole model in five words. The risky operation is gated on a permit that verifies, and nothing else. The decision is fail-closed: absence of a valid permit is a denial, not a default-allow. Approvals, thresholds, and human review all resolve into the same artifact, so your execution code never needs to know why something was allowed — only that it was, provably.

The payoff is that your safety guarantee stops depending on the model behaving and starts depending on a signature verifying. One of those you can prove to an auditor. The other you can only hope for.