Skip to main content
GUIDE

How to protect sensitive actions without blocking legitimate agents

A practical model for keeping account changes and high-impact operations safe while still letting legitimate automated clients do useful work in your app.

Toshendra Sharma

By Toshendra SharmaFounder & CEO, AgentCredia · Updated November 13, 2026

Advanced · 10 min read

Why not just block agents from sensitive actions?

Some actions in your application matter more than others. Changing a password, adding a team member, exporting data, deleting a project, approving an order: if any of these happens without the right authority, the damage is real.

At the same time, more of the legitimate work in your app is done by automation. A user asks an assistant to update their settings. A partner integration creates records on a schedule. An internal agent files support tickets. Blocking every automated client from sensitive routes breaks that work and pushes people back to manual effort or to workarounds that are harder to secure.

The answer is not "block agents" or "trust agents". It is a model in which the risk of the action, the authority behind the request and the evidence about the actor each play a distinct role. This guide builds that model. For the broader thinking behind it, see designing app-level policies for humans and AI agents.

Step 1: Tier your routes by risk

Start by classifying actions, not clients. A simple three-tier scheme works for most applications:

Tier Examples Default requirement
Low Read public data, search, view own dashboard Normal authentication if any
Medium Create or edit own content, invite a viewer Authenticated, authorized for the resource
High Change credentials, change ownership, bulk export, delete, approve Authorized plus step-up or review

Write the tier into route configuration so it is visible in code review:

TS
type Tier = "low" | "medium" | "high";

export const routeTier: Record<string, Tier> = {
  "GET /projects": "low",
  "POST /projects": "medium",
  "POST /projects/:id/members": "medium",
  "POST /account/password": "high",
  "POST /exports": "high",
  "DELETE /projects/:id": "high",
};

Tiers change slowly and are easy to reason about. Everything else in this guide hangs off them.

Step 2: How do identity, credentials and authorization differ?

Three questions are often blurred together:

  1. Who or what is acting? This is actor evidence: an interactive person, a person with AI assistance, an agent, or Unknown, with a basis such as verified or declared.
  2. Which credential was presented? A session cookie, an API key, an OAuth access token.
  3. Is this credential allowed to do this action on this resource? This is authorization.

A verified agent signature (RFC 9421) answers the first question well. It says nothing about the third, a point covered in detail in what signed AI agent requests prove, and what they don't. An OAuth token with the right scope answers the third question. It says little about the first. Your decision for a high-tier action needs a good answer to the third question always, and uses the first to choose how much extra assurance to ask for.

TS
interface Context {
  actor: { actor: "human" | "human_ai" | "agent" | "unknown"; basis: "verified" | "declared" | "inferred" | "unknown" };
  credential: { type: "session" | "oauth" | "api_key"; scopes: string[]; subject: string };
}

function authorized(ctx: Context, action: string, resourceOwner: string): boolean {
  return ctx.credential.subject === resourceOwner && ctx.credential.scopes.includes(action);
}

Authorization is never relaxed because of a favorable actor result. Actor context only adds requirements or chooses between equally safe paths.

Step 3: Use delegated authority for agents acting for people

When an agent acts on behalf of a user, give it its own credential with limited authority instead of letting it reuse the user's session. OAuth 2.0 is the standard tool: the user approves specific scopes for a specific client, and the token records that client. RFC 9700, the OAuth 2.0 Security Best Current Practice, describes how to deploy it safely. MCP servers follow the same pattern: the MCP authorization specification treats a protected MCP server as an OAuth 2.1 resource server. Our post who is calling your MCP server? applies this to tool calls.

This produces exactly what you need for sensitive actions:

  • The token tells you which client is acting and for whom.
  • Scopes limit what it can do. A scheduling agent with calendar:write cannot change a password.
  • The user can revoke the grant without changing their own credentials, and OAuth 2.0 Token Revocation (RFC 7009) defines how a client tells the authorization server that a token should no longer be valid.
  • Audit logs show the agent, not the user, performed the action.

If your app does not yet support delegated credentials, prioritize it for the actions agents most often need. It is the single most effective control for automated access.

Step 4: When should you ask for a step-up check?

Step-up means asking for additional assurance at the moment of a sensitive action. For OAuth-protected APIs, RFC 9470 defines how a resource server tells a client that the current token's authentication is not strong or recent enough. The right step-up depends on who is acting:

  • Interactive person (Human, Human + AI): re-authentication (see the reauthentication requirements in NIST SP 800-63B), a passkey prompt built on WebAuthn, or a one-time code.
  • Agent with delegated credential: a scope dedicated to the high-tier action, granted explicitly by the user, or a confirmation sent to the user out of band.
  • Unknown: the route's existing strong check, unchanged.
TS
type Requirement = "none" | "reauth" | "explicit_scope" | "user_confirmation" | "review";

function stepUp(tier: Tier, ctx: Context): Requirement {
  if (tier !== "high") return "none";
  const { actor } = ctx.actor;
  if (actor === "human" || actor === "human_ai") return "reauth";
  if (actor === "agent" && ctx.credential.type === "oauth") return "explicit_scope";
  if (actor === "agent") return "user_confirmation";
  return "reauth";
}

Notice that a Human result does not skip step-up. It selects the step-up that suits an interactive person. Inferences can be wrong, so they should change the shape of the check, not remove it.

Step 5: Route to human review where judgment is needed

Some actions are rare, high-impact and hard to automate safely: bulk exports of customer data, account recovery after lost credentials, or deleting an organization. For these, a review queue is often better than any automated rule.

Actor context makes review faster and fairer by giving the reviewer the evidence: the class, the basis, the credential used, the history of similar requests. The reviewer decides. The classification informs.

TS
function shouldReview(route: string, ctx: Context): boolean {
  const reviewRoutes = new Set(["POST /exports", "POST /account/recover"]);
  if (!reviewRoutes.has(route)) return false;
  return !(ctx.credential.type === "oauth" && ctx.credential.scopes.includes("export:bulk"));
}

Step 6: Choose failure behavior per tier

Actor evaluation can time out or lack evidence. Decide in advance what happens.

  • Low tier: fail open. The request proceeds normally.
  • Medium tier: fail open, with normal authorization in place.
  • High tier: fail closed to the existing strong check. Missing actor evidence never weakens protection, and it never blocks a request that would otherwise have passed your normal checks.

The key property is symmetry. A failure in actor evaluation should leave the action exactly as safe, and exactly as available, as it was before you added actor context.

Step 7: Do not make consequential decisions about people from inferences

An inferred classification is evidence about an interaction, not a fact about a person. Do not deny someone a job application, a credit decision, an exam result, housing or access to an essential service because a session looked automated or AI-assisted. In those flows, use actor context to choose a process, such as human review or an alternative verification path, and make sure the person can complete it.

This is both a fairness principle and a practical one. Inferences have false positives, and the cost of a false positive in these flows falls on someone who did nothing wrong.

Step 8: Roll out with observation first

Before any of this affects real requests, run it in observation and preview mode:

  1. Record tier, actor class, basis, credential type and the step-up your policy would require.
  2. Review samples of high-tier requests by class. Are legitimate agents using delegated credentials, or reusing user sessions?
  3. Fix the gaps you find, often by offering scoped credentials, before you enforce.
  4. Enforce on a small slice of a single route, measure completion rates, then expand.

Step 9: Keep an audit trail that answers "who and on whose behalf"

For every high-tier action, record enough to reconstruct what happened later:

  • the actor class and basis at the time of the request,
  • the credential type, the client it was issued to and the subject it represents (OAuth 2.0 Token Exchange, RFC 8693, defines an act claim for recording exactly this kind of "acting for" relationship),
  • the scopes that authorized the action,
  • the step-up or review requirement applied and its result.
TS
audit.write({
  action: "project.export",
  actor: ctx.actor,
  client: ctx.credential.type === "oauth" ? clientIdFromToken(req) : undefined,
  subject: ctx.credential.subject,
  scopes: ctx.credential.scopes,
  requirement,
  outcome: "approved",
  at: new Date().toISOString(),
});

With this trail, a question like "did a person or an assistant delete this project, and who approved it" has a clear answer. That makes incident response faster and gives users confidence that automated help in your app is accountable.

A worked example

A project management app sees an assistant updating task due dates for users, and occasionally attempting to export entire projects.

  • Updating a task is medium tier. The assistant uses an OAuth token with tasks:write. Authorized, no step-up. Allowed.
  • Exporting a project is high tier. The assistant's token lacks export:bulk. The app sends the user a confirmation request. The user approves, and the app grants the scope for that client.
  • An Unknown client attempts the same export with a reused session cookie. The existing re-authentication prompt applies, exactly as it did before.

Nobody was blocked for being an agent. Every high-impact action carried explicit authority.

Where Proof of Actor fits

Proof of Actor supplies the actor side of this model: who or what is acting and on what basis. Your application keeps authentication, authorization and the final decision. Detect the actor, Understand how each class uses your sensitive routes, and Route each request to the right level of assurance.

To design this for your application with our team, apply for early access. For SaaS scenarios in more detail, see SaaS Apps & Websites.

Sources

Build it with us.

Join the Proof of Actor founding developer program.

Prefer to talk first? Talk to the founders