Authorization for agent actions · MIT
Authorize the action.
Not the agent.
Enforce user-aware policy at the tool boundary, resolve credentials at execution time, and keep tamper-evident proof — open-source, framework-neutral core.
$ agent reads an email — injected instructions inside
→ model obeys: email.forward → [email protected]
✗ BLOCKED before the tool ran — deny:email.forward (exfiltration)
? email.delete? held for a human — denied
§ every decision sealed on the receipt chain
✓ chain verifies — doctored log detected
→ the model was compromised. the policy didn't care.
Works with the stack you already run
- Vercel AI SDK & Eve
- Cloudflare Agents & Workers
- Auth0 Token Vault & CIBA
- Supabase Token store
Run it yourself — no API keys, no network
An injected agent tries to exfiltrate your data. Policy says no.
The tools are wrapped with nominee.guard(). The model obeys the
injection anyway — models do — and the deny rule fires
before the tool runs. Every attempt, allowed or refused, is
sealed into a signed, tamper-evident receipt chain. One command:
2. the model obeys the injection and tries to exfiltrate
✓ BLOCKED before the tool ran — deny:email.forward (exfiltration)
5. the receipt chain (signed, tamper-evident)
#0 policy.decision email.read allow 5493c2c54cd5
#1 policy.decision email.forward deny ca6a069febdb
#2 policy.decision email.delete ask d2fe628a5202
#4 approval.resolved email.delete denied 2b0ac4aa3ad8
#5 policy.decision email.forward allow fd17436d92c0
✓ chain verifies: 6 receipts intact
✓ doctored log detected — broken at #1
cd examples/prompt-injection-blocked && node run.mjs —
source ↗
Tokens, the supporting act — settled live
Same agent. One outlives its token.
Two agents call a protected API across a session longer than the token
lives (~8s here). Left grabs a token once and reuses it. Right calls
nominee.run() so the strategy resolves a fresh token at
execution time. The real nominee
package runs in your browser — open the Network tab and watch the refresh.
That's the expiry half. Add refresh-token rotation + concurrency and naive code fails 7/8 while nominee gets 8/8 — a runnable proof, no mocks that cheat: examples/token-refresh-correctness.
Start on your DB → Auth0 → Nango. Your agent code never changes — only the strategy line does. The Passport.js of agent auth, scoped to the no-lock-in tail:
const nominee = new Nominee({
strategy: ({ user, connection }) => db.refreshToken(user, connection), // zero signup
})
// everything below is identical, whatever the strategy ↓
const token = await nominee.token({ user, connection: 'github' })
const nominee = new Nominee({
strategy: OAuth2({ connections: { github: { tokenEndpoint, clientId, refreshToken } } }), // bring your own OAuth
})
// everything below is identical, whatever the strategy ↓
const token = await nominee.token({ user, connection: 'github' })
const nominee = new Nominee({
strategy: Supabase({ url, key, connections: { github: { tokenEndpoint, clientId } } }), // tokens stored in Supabase
})
// everything below is identical, whatever the strategy ↓
const token = await nominee.token({ user, connection: 'github' })
const nominee = new Nominee({
strategy: Auth0({ domain, clientId, clientSecret, subjectToken, ciba }), // managed Token Vault + CIBA
})
// everything below is identical, whatever the strategy ↓
const token = await nominee.token({ user, connection: 'github' })
The race above runs the oauth2 line. Want the auth0 line for real? → watch a live agent act on your GitHub, after your approval.
How it works
Checked as the user. Bound to the call. On the record.
Your framework controls the run and approval UI. nominee enforces the application decision underneath it: who is acting, what exact input was authorized, which credential the tool receives, and what evidence remains.
-
¶
User-aware policy on every call
Declarative allow / deny / ask rules see the user, tool, arguments, and delegation chain. Async predicates can consult your existing RBAC, FGA, OPA, or application policy.
-
⏸
Exact-call enforcement
Policy and approval bind to the user, tenant, resource, exact arguments, policy version, and a single-use execution capability. Credentials arrive only after capability consumption.
-
§
On the register
Every decision, approval, capability, credential grant, and outcome is a hash-chained, optionally signed receipt. Mutated records and gaps break verification; externally anchored stream tips expose rollback.
Install and go
Keep your framework. Keep your IAM. Enforce them at the tool boundary.
Start with ordered allow / deny / ask rules, then connect async predicates and token strategies to the authorization and credential systems you already run.
import { Nominee, allow, deny, ask } from 'nominee'
// native approvals control the run; nominee enforces application policy:
const nominee = new Nominee({
policy: [
allow('email.read'),
deny('email.forward', { reason: 'exfiltration' }),
ask('email.delete'), // a human decides, every time
],
})
// one line — every call checked before the tool runs
const tools = nominee.guard({ 'email.read': readEmail, 'email.forward': forwardEmail }, { user: 'alice' })
import { guardTools } from 'nominee-ai'
// wrap your existing AI SDK tools — the key is the name your policy matches
const result = await generateText({
model,
tools: guardTools(nominee, { searchEmail, forwardEmail, mergePr }, {
user: session.userId,
}),
})
// per-tool config (connection tokens, approval: true) → nomineeTool()
import { Nominee } from 'nominee'
import { Auth0 } from 'nominee-auth0'
// same API — swap the strategy for managed Token Vault + phone approval
const nominee = new Nominee({
strategy: Auth0({
domain: process.env.AUTH0_DOMAIN!,
clientId: process.env.AUTH0_CLIENT_ID!,
clientSecret: process.env.AUTH0_CLIENT_SECRET!,
subjectToken: ({ user }) => store.getRefreshToken(user),
ciba: { bindingMessage: (req) => `Approve: ${req.action}` }, // phone approval
}),
})
Decision-bound capability
Approve once. Execute exactly that action.
An ask rule holds deletes, payments, or outbound mail for human review. When a framework pauses your tool run, Nominee enforces the underlying boundary: binding the approval to the exact input, generating a single-use execution capability, re-checking resource permission, and fetching fresh credentials only once the capability is consumed.
repo.delete
Delete repository alice/old-project
Where nominee sits
A thin, neutral seam — not another platform.
Your framework should run the agent and its approval UI. nominee enforces the application decision underneath: whether this user may take this exact action on this tenant's resource, which authority reaches execution, and what evidence remains.
run({ user, tenant, action, resource, input }, execute)execute(input)When you don't need nominee
- A read-only agent with no authority worth guarding.
- Your platform's native permission system covers you end-to-end and you're happy inside it.
- You want one fully-managed vendor for tools + auth + policy — use Arcade or Composio directly.
Reach for nominee when the action must inherit the real user's resource permissions, broad credentials must move behind the boundary, or several runtimes need one enforcement and evidence contract.
Packages
One authorization layer, wherever your agent runs.
nominee-ai
guardTools wraps your AI SDK tools in one line; nomineeTool for per-tool config. Runs on Cloudflare Agents too.
nominee-supabase
Store provider tokens in Supabase; nominee reads and refreshes them. Zero deps.
nominee-auth0
Auth0 Token Vault for federated tokens, and CIBA approvals on the user's phone.
nominee-postgres
Transactional actions, budgets, single-use capabilities, outcomes, journals, and receipt streams.
nominee-openai
Decision-bound tools with Nominee ask rules mapped to native resumable approvals.
nominee-mastra
Mastra tools with native or portable durable approval and execution-time credentials.
nominee-mcp
Register guarded tools on the official MCP server SDK with durable pending handles.
FAQ
The questions people actually ask.
Isn't this just an if-statement with extra steps?
For one low-risk tool, an if-statement may be enough. nominee is useful when the decision must use the real user and resource, stay bound to exact arguments, narrow delegated authority, control credential delivery, and produce one evidence stream across runtimes.
Can the model talk its way past a deny?
No. The model never sees the policy — it only ever gets the tools you handed it
via nominee.guard(). A deny rule throws
PolicyDeniedError inside the wrapper, before your tool's own code
runs. There's no prompt for the model to argue with; the call physically doesn't
happen.
Do receipts store my data?
Not by default. Tool inputs are recorded as inputHash — a SHA-256 of
the canonical JSON — so you can prove what an approver saw without writing user
data into the log. Pass receipts: { input: 'raw' } if you want the
full input on the record instead, or 'none' to skip it entirely.
Can receipts be forged?
Tamper-evident, not non-repudiation — say that plainly. Without a signing key,
the chain is publicly re-computable: anyone can verify it wasn't edited, but
anyone with write access to the whole log could also rewrite it consistently.
Pass an HMAC key and only key-holders can produce valid hashes —
that's the guarantee to reach for once receipts leave your own process.
How is this different from Arcade, Composio, or Vercel Connect?
Those products can manage connections and tools. nominee is the open-source enforcement layer that binds your application policy to the exact tool action. It can use a managed platform, your IAM, or your own token store underneath.
Does nominee replace my auth provider (Auth0, Clerk, WorkOS)?
No — authentication (who is the user) is a separate, solved problem. nominee is the layer above it, deciding what an already-authenticated agent may do as that user. It composes with whatever you already use; Auth0 is one optional strategy among several, not a requirement.
What happens if I don't configure a policy?
Everything is allowed — and still receipted. nominee doesn't restrict anything until you add rules, so you can adopt it incrementally: wrap your tools first, watch the receipt chain to see what your agent actually does, then tighten the policy once you know.
When don't I need nominee?
A read-only agent with no authority worth guarding. Your platform's native permission system already covers you end-to-end. Or you want one fully-managed vendor for tools, auth, and policy together — use Arcade or Composio directly.
Give your agent a nominee.
Open source, MIT, zero-dependency core. It works the moment you install it.
▶ Watch a real agent act on your GitHub, after your approval →