Last week, we saw a pattern that should make every team shipping AI agents a little nervous:
A low-privilege webchat identity asked a backend agent to do something “just this once,” and somehow that request ended up running with elevated exec permissions.
No jailbreak. No exotic zero-day. Just a boring, familiar auth bug:
the system checked who executed the action, not who originally asked for it.
That’s how RBAC bypass happens in agent systems.
If your architecture has:
- a user or webchat-facing agent,
- an orchestration layer,
- and a privileged execution worker,
…then you need to think about identity propagation, not just auth at the edge.
The bug in one sentence
A webchat session with limited permissions triggers a workflow, and somewhere in the handoff, the original caller identity gets replaced by a more privileged service identity.
The exec layer sees:
“request comes from orchestrator-service”
instead of:
“request originated from webchat-user, delegated through orchestrator-service”
That difference is everything.
What this looks like in practice
A common flow looks like this:
[Webchat User]
|
v
[Chat/API Layer] -- authenticated as --> webchat_identity
|
v
[Orchestrator] -- calls --> [Exec Worker]
as worker_service
If the exec worker only evaluates the immediate caller (worker_service), then any privileges attached to that service account can accidentally apply to the webchat request.
Here’s the safer mental model:
webchat_identity
-> delegated to orchestrator
-> delegated to exec worker
-> policy checks full chain
ASCII version:
BAD:
webchat_user ---> orchestrator ---> exec_worker
policy checks only ^
|
service account
GOOD:
webchat_user ---> orchestrator ---> exec_worker
policy checks: [origin=user] + [delegates=orchestrator, worker]
Why this keeps happening
Because service-to-service auth is usually implemented before agent auth.
A lot of stacks already do:
- mTLS between services
- JWT validation at each hop
- RBAC on service accounts
That’s fine for microservices.
It’s not enough for agent systems, because agents regularly act on behalf of someone else.
If you drop the “on behalf of” context, you’ve effectively built a privilege escalation path.
The fix: evaluate delegated identity, not just transport identity
What you want is:
- A cryptographic identity for the original actor
- A delegation chain for each hop
- Policy evaluation at execution time against the original actor + chain
- Audit logs that preserve who asked, who approved, and who executed
This is the same idea behind token exchange and delegated authorization flows like RFC 8693.
Even if you’re not using a full identity platform, the rule is simple:
Never let a privileged worker “forget” the initiating identity.
A tiny runnable example
Here’s a minimal Node.js pattern showing the check you actually want.
npm install jsonwebtoken
const jwt = require("jsonwebtoken");
const token = jwt.sign(
{
sub: "webchat:user_123",
delegates: ["orchestrator-service"],
permissions: ["read:repo"]
},
"dev-secret"
);
const claims = jwt.verify(token, "dev-secret");
const wantsExec = "exec:shell";
const allowed =
claims.permissions.includes(wantsExec) ||
claims.sub.startsWith("admin:");
if (!allowed) {
console.log(`DENY: ${claims.sub} cannot request ${wantsExec}`);
} else {
console.log("ALLOW");
}
This is intentionally simple, but the point is important:
the decision is based on sub (the origin identity), not just the service currently holding the token.
In production, you’d also want:
- signed delegation chains
- audience restrictions
- expiry and replay protection
- policy rules for which services may delegate which scopes
- approval gates for dangerous actions like shell exec
If you already use OPA, this is a great fit. Pass the full chain into policy input and make exec conditional on the original principal plus delegation constraints.
A practical policy rule
For elevated exec, a decent default is:
- allow only if the origin identity has
execpermission - require the delegation chain to be intact and signed
- require a human approval step for risky commands
- log the full chain for audit
In other words:
service privilege != user privilege
delegation != impersonation
That distinction saves you.
What to check in your own stack
If you’re worried you might have this bug, start here:
- Does your exec layer know the original caller, or only the last service hop?
- Can a webchat/session identity indirectly inherit worker privileges?
- Are you using delegation, or accidental impersonation?
- Do audit logs show originator + delegates + executor?
- Are dangerous tools gated separately from normal read/write tools?
If any of those answers are fuzzy, it’s worth tightening now—before an “innocent” chat request turns into production shell access.
Try it yourself
If you want to poke at your own setup:
- Want to check your MCP server? Try https://tools.authora.dev
- Run
npx @authora/agent-auditto scan your codebase - Add a verified badge to your agent: https://passport.authora.dev
- Check out https://github.com/authora-dev/awesome-agent-security for more resources
The takeaway
RBAC bypass in agent systems usually isn’t dramatic. It’s a chain-of-custody problem.
A webchat identity should never gain elevated exec just because a privileged orchestrator or worker touched the request.
Preserve the original identity. Verify delegation. Evaluate policy at the point of execution.
That’s the difference between “agents that can help” and “agents that can accidentally become root.”
How are you handling agent identity and delegated permissions today? Drop your approach below.
-- Authora team
This post was created with AI assistance.
Top comments (0)