Data as of Sep 18, 2026 · Based on 48 AI responses from ChatGPT Search and Google AI Mode · See how Parse measures this
Your brand can be here too.
For a B2B SaaS product, I’d treat an AI agent as a **first-class actor with delegated authority**, not as a special kind of API client. The core design is: **User → Agent identity → Policy engine → Approval gate → Tool/API → Resource**, with an append-only audit trail across every step. That architecture prevents the…
For a B2B SaaS product, I’d treat an AI agent as a first-class actor with delegated authority, not as a special kind of API client. The core design is:
User → Agent identity → Policy engine → Approval gate → Tool/API → Resource, with an append-only audit trail across every step.
That architecture prevents the common failure mode where the model effectively becomes an over-privileged service account. Current OWASP and Microsoft guidance converges on least privilege, task-scoped authorization, deterministic policy enforcement outside the model, and explicit controls for high-impact actions.
Maintain distinct identities for:
user_123tenant_456agent_support_v3run_789crm.update_customercustomer_abcAn agent should never simply inherit an administrator's API token.
Instead, create a delegated authorization context such as:
principal:
user_id: 123
tenant_id: 456
agent:
id: support-agent
version: 3.4
task:
id: run_789
purpose: "Resolve support ticket 9821"
capabilities:
- ticket.read
- customer.read
- customer.update:contact_fields
constraints:
ticket_ids: [9821]
expires_at: 2026-09-17T09:00:00Z
The important property is that the agent's effective permissions are narrower than or equal to the authority delegated to it. For multi-agent systems, delegation should narrow permissions rather than propagate the entire parent's authority.
Traditional RBAC is useful for humans, but agents usually need something more granular.
A practical model is:
RBAC/ABAC → task-scoped capabilities → per-call authorization
For example:
Support Agent
├── ticket.read
├── customer.read
└── customer.update
└── only fields: phone, address
└── only customer associated with current ticket
I'd make permissions express at least:
action
resource
operation
scope
conditions
expiration
So instead of:
customer.write
you can have:
customer.update
scope = ticket.customer
fields = [phone, address]
expires = 30 minutes
For every tool call, the backend evaluates the current user, tenant, agent, tool, resource, task, and context. Never rely on the system prompt to enforce permissions. OWASP specifically recommends enforcing authorization at infrastructure/tool boundaries and using deny-by-default, task-scoped permissions.
Don't let the model call your internal APIs directly.
Instead:
┌─────────────────┐
│ AI Agent │
└────────┬────────┘
│ proposed tool call
▼
┌─────────────────┐
│ Policy Engine │
│ │
│ identity │
│ tenant │
│ capability │
│ resource │
│ risk │
│ approval state │
└────────┬────────┘
│
┌──────┴───────┐
│ │
DENY APPROVE
│
┌──────▼───────┐
│ Approval Gate│
└──────┬───────┘
│
┌──────▼───────┐
│ Tool/API │
└──────────────┘
The agent should receive something like:
{
"decision": "deny",
"reason": "approval_required"
}
rather than having access to the underlying policy logic.
This also gives you one place to enforce authorization consistently across web requests, background jobs, agents, and integrations. Microsoft similarly recommends a policy layer before tool execution that evaluates the user, tenant, agent, tool, resource, permissions, and approval requirement.
Don't make "AI agent = needs approval" your model. You'll either annoy users or encourage them to approve everything.
Instead classify actions, independently of the model.
For example:
| Tier | Examples | Approval |
|---|---|---|
| 0 | Search, read, summarize | None |
| 1 | Create draft, modify non-critical metadata | Usually none |
| 2 | Send customer message, change business record | Configurable |
| 3 | Delete data, issue refund, change permissions | Explicit |
| 4 | Financial transfer, security/admin changes, irreversible operation | Explicit + possibly dual approval |
The classification should be attached to the tool/action definition, not generated by the agent at runtime. OWASP specifically recommends declaring reversibility/risk characteristics on actions and using them to determine approval requirements.
A useful tool definition might look like:
name: invoice.refund
risk: high
reversible: false
authorization:
permission: invoice.refund
approval:
required: true
roles:
- billing_admin
expires_in: 10m
limits:
max_amount: 5000
This is probably the most important design detail.
Don't approve:
"Let the agent manage invoices." Approve:
"Refund invoice #18291 for $1,240 to Acme Corp." The approval should be cryptographically or otherwise strongly bound to:
tenant
user
agent
agent run
tool
operation
resource
normalized parameters
policy version
expiration
approver
Conceptually:
approval_token =
Sign(
tenant=456,
run=789,
tool=invoice.refund,
resource=invoice_18291,
amount=1240,
currency=USD,
expires=09:42,
policy_version=17
)
If the agent changes $1,240 to $12,400, the authorization must fail and require a new approval.
OWASP explicitly recommends parameter-bound, short-lived approvals and replay protection for high-impact operations.
Your approval UI should answer:
What exactly will happen if I approve this? For example:
Agent: Collections Assistant
Action: Send email
To:
[email protected]
Subject:
Outstanding invoice #18291
Changes:
- No database changes
- External email will be sent
Reason:
Invoice is 32 days overdue.
Risk:
External communication
[ Reject ] [ Approve ]
Avoid making the UI depend on an agent-generated statement such as "I think this is safe."
The authorization decision should come from deterministic policy; the UI can show relevant context. OWASP recommends action previews and clear review interfaces containing the action, rationale/context, impact, and reversibility.
Don't merely log:
agent sent email
You want an event chain that lets you reconstruct the transaction.
Something like:
{
"event_id": "evt_9821",
"timestamp": "...",
"tenant_id": "tenant_456",
"user_id": "user_123",
"agent_id": "support-agent",
"agent_version": "3.4",
"run_id": "run_789",
"event": "tool_call",
"tool": "email.send",
"resource": "customer_abc",
"requested_action": {...},
"policy": {
"version": "policy-17",
"decision": "allow"
},
"approval": {
"required": true,
"id": "approval_321",
"approver": "user_123",
"decision": "approved"
},
"execution": {
"status": "success",
"external_id": "msg_8842"
}
}
I'd capture at least:
Don't blindly log prompts, secrets, tokens, or entire customer records. OWASP specifically warns about sensitive information appearing in agent logs and recommends structured security metadata while avoiding credentials and unnecessary sensitive data.
This distinction becomes invaluable during incident investigation.
For example:
agent.action.proposed
↓
policy.evaluated
↓
approval.requested
↓
approval.granted
↓
agent.action.executed
↓
agent.action.completed
If an agent attempts an unauthorized operation, log the attempted call even when it is rejected.
That gives security teams visibility into:
OWASP recommends logging both agent decisions/tool calls and authorization outcomes for precisely this reason.
Agent permissions shouldn't live forever.
Use:
And have a kill switch that the agent cannot disable.
For example:
Customer disables agent
↓
Policy service marks agent revoked
↓
Existing capability tokens rejected
↓
Running tasks terminate at next enforcement point
↓
Event recorded in audit log
This matters particularly with long-running and asynchronous agents. OWASP recommends expiration, revocation, and an independently controlled kill switch.
Don't let the model maintain:
approved = true
in conversation memory.
Instead:
Agent memory
↓
"Approval requested"
Approval service
↓
approval_123 = APPROVED
Policy engine
↓
verify approval_123
↓
permit
The approval service is authoritative.
This prevents prompt injection or context manipulation from turning:
"The customer approved this." into actual authorization.
A particularly effective product pattern is to let agents be autonomous in preparation while gating commitment.
For example:
Autonomous:
research
calculate
draft
classify
propose changes
Approval:
send
publish
delete
refund
provision
change permissions
This lets you offer useful autonomy without requiring a human click for every read or draft operation. Microsoft explicitly describes this progression from drafting/summarizing/recommending toward gated writes, sends, deletes, and provisioning.
For a SaaS team, I'd start with these services:
┌───────────────┐
│ Web/API │
└───────┬───────┘
│
┌───────▼───────┐
│ Agent Runtime │
└───────┬───────┘
│
proposed action
│
┌───────▼───────┐
│ Authorization │
│ / Policy │
└───────┬───────┘
│
┌──────────┴──────────┐
│ │
denied approval
│ │
│ ┌──────▼──────┐
│ │ Approval │
│ │ Service │
│ └──────┬──────┘
│ │
└──────────┬──────────┘
│
┌──────▼──────┐
│ Tool Gateway│
└──────┬──────┘
│
┌──────▼──────┐
│ SaaS APIs │
└─────────────┘
┌─────────────────────────────┐
│ Append-only Audit/Event Log │
└─────────────────────────────┘
The Tool Gateway is particularly valuable: it becomes the choke point through which every agent action must pass.
Don't start by building a giant authorization framework.
Start with:
Then add sophisticated ABAC/ReBAC, multi-party approval, behavioral detection, and dynamic permission narrowing as your agent workflows become more complex.
The key architectural principle is:
The model proposes. Policy authorizes. Approval authorizes exceptional actions. The tool gateway enforces. The audit log remembers. That separation means a hallucination, prompt injection, compromised tool, or malicious document can influence what the agent asks to do without automatically acquiring the ability to make the underlying change. This is the central reason current agent-security guidance emphasizes enforcing authorization outside the model and treating agent identities and permissions as first-class security controls.
in conversation memory.
Instead:
Agent memory
↓
"Approval requested"
Approval service
↓
approval_123 = APPROVED
Policy engine
↓
verify approval_123
↓
permit
The approval service is authoritative.
This prevents prompt injection or context manipulation from turning:
"The customer approved this." into actual authorization.
A particularly effective product pattern is to let agents be autonomous in preparation while gating commitment.
For example:
Autonomous:
research
calculate
draft
classify
propose changes
Approval:
send
publish
delete
refund
provision
change permissions
This lets you offer useful autonomy without requiring a human click for every read or draft operation. Microsoft explicitly describes this progression from drafting/summarizing/recommending toward gated writes, sends, deletes, and provisioning.
For a SaaS team, I'd start with these services:
┌───────────────┐
│ Web/API │
└───────┬───────┘
│
┌───────▼───────┐
│ Agent Runtime │
└───────┬───────┘
│
proposed action
│
┌───────▼───────┐
│ Authorization │
│ / Policy │
└───────┬───────┘
│
┌──────────┴──────────┐
│ │
denied approval
│ │
│ ┌──────▼──────┐
│ │ Approval │
│ │ Service │
│ └──────┬──────┘
│ │
└──────────┬──────────┘
│
┌──────▼──────┐
│ Tool Gateway│
└──────┬──────┘
│
┌──────▼──────┐
│ SaaS APIs │
└─────────────┘
┌─────────────────────────────┐
│ Append-only Audit/Event Log │
└─────────────────────────────┘
The Tool Gateway is particularly valuable: it becomes the choke point through which every agent action must pass.
Don't start by building a giant authorization framework.
Start with:
Then add sophisticated ABAC/ReBAC, multi-party approval, behavioral detection, and dynamic permission narrowing as your agent workflows become more complex.
The key architectural principle is:
The model proposes. Policy authorizes. Approval authorizes exceptional actions. The tool gateway enforces. The audit log remembers. That separation means a hallucination, prompt injection, compromised tool, or malicious document can influence what the agent asks to do without automatically acquiring the ability to make the underlying change. This is the central reason current agent-security guidance emphasizes enforcing authorization outside the model and treating agent identities and permissions as first-class security controls.
Building trust, security, and governance for autonomous or semi-autonomous AI agents in a B2B SaaS environment requires a shift from human-centric Identity and Access Management (IAM) to agent-centric authorization and observability. Here is a structured, scannable blueprint for implementing permissions, audit logs,…
Building trust, security, and governance for autonomous or semi-autonomous AI agents in a B2B SaaS environment requires a shift from human-centric Identity and Access Management (IAM) to agent-centric authorization and observability.
Here is a structured, scannable blueprint for implementing permissions, audit logs, and approval flows for AI agents.
AI agents should never inherit full user access. They operate on delegated authority with strictly scoped boundaries.
When an AI agent takes an action, you need a clear paper trail showing why and how it happened.
Approval flows act as the safety brake between agent planning and external side effects.
If you'd like to dive deeper, let me know:
I can help map out a specific architectural sequence or data schema for your use case.
No sources cited
**B2B SaaS teams must build AI agent governance using delegated, task-scoped authorization, immutable execution audit logs tied to human intent, and deterministic approval gates for high-consequence actions** **.** [](https://www.scalekit.com/blog/audit-trail-agent-auth)…
B2B SaaS teams must build AI agent governance using delegated, task-scoped authorization, immutable execution audit logs tied to human intent, and deterministic approval gates for high-consequence actions .
Because AI agents operate non-deterministically and make autonomous multi-step decisions, inheriting a user's broad RBAC (Role-Based Access Control) or static service token creates severe security risks. Production-grade architecture requires a specialized stack separating identity, capability scope, and human supervision.
Do not give AI agents blanket user permissions. Instead, use an authorization framework built on explicit delegation and fine-grained limits.
read:crm-leads, write:email-draft, max_spend:50usd ) rather than generic resource verbs.Standard API audit logs are insufficient because they miss the reasoning and intent behind an agent's multi-step loop.
Autonomous operations require deterministic circuit breakers before any high-consequence system mutation occurs.
If you'd like, we can dive deeper into:
The key design principle is: **treat an AI agent as a distinct, delegated actor—not as a UI extension of the user.** The user supplies authority; the agent gets a narrower, explicitly bounded capability to exercise that authority. NIST is converging on essentially this model: agent identity, delegated authorization,…
The key design principle is: treat an AI agent as a distinct, delegated actor—not as a UI extension of the user. The user supplies authority; the agent gets a narrower, explicitly bounded capability to exercise that authority.
NIST is converging on essentially this model: agent identity, delegated authorization, least privilege, auditable actions, and human authorization for appropriate high-impact operations.
Every agent action should carry an authorization chain like:
Tenant
↓
Human principal
↓
Agent identity
↓
Task / delegation
↓
Tool + operation
↓
Resource
For example:
Acme Corp
→ Jane Smith
→ "Invoice Agent" v3
→ Task #84721: reconcile March invoices
→ Salesforce: read invoices
→ ERP: create adjustment
→ Email: send to vendor
Don't let the model itself determine whether something is authorized. Authorization belongs in a policy enforcement layer outside the model/agent runtime. OWASP specifically recommends backend enforcement, deny-by-default permissions, task-scoped grants, and binding delegated authority to the human, agent, tool, and resource.
Give each agent/application an identity such as:
agent_id
tenant_id
owner_user_id
agent_type
agent_version
credential_id
capabilities
status
Use short-lived credentials and avoid having agents simply reuse the user's OAuth/session credential. NIST and OpenID's recent work both emphasize distinct agent identities linked to the human or organization on whose behalf the agent operates.
Traditional SaaS RBAC is useful, but it's usually too coarse for agents.
Instead of:
Jane is an Admin → agent can do everything Jane can. Use:
Jane delegates these specific capabilities to this agent for this task. For example:
agent: invoice-agent
task: reconcile-invoices
permissions:
- invoices:read
- invoices:update
- vendors:read
resources:
- tenant:acme
- invoices:2026-Q1
limits:
max_invoice_value: 10000
expires: 2026-09-13T18:00Z
Layer RBAC + ABAC + resource-level permissions + task-scoped capabilities rather than trying to make one model do everything.
A particularly important rule:
Delegation can only narrow authority, never expand it. If Jane can read 10,000 records but gives an agent access to 100, the agent can delegate at most a subset of those 100.
This is one of the most valuable architectural decisions.
Have the agent produce an action proposal:
{
"action": "refund_customer",
"target": "customer_123",
"amount": 4820,
"reason": "duplicate charge",
"risk": "high"
}
Then send it through:
Agent
↓
Action normalizer
↓
Policy engine
↓
Risk classifier
↓
Approval service (if required)
↓
Execution gateway
↓
External system
The agent should never be able to bypass the execution gateway.
That gives you a clean security boundary:
OWASP similarly recommends independently validating high-impact actions rather than relying on the agent's own reasoning or risk assessment.
Don't make every action require a human click. You'll create approval fatigue—and eventually users will approve everything. NIST explicitly calls out consent fatigue as a concern with excessive human-in-the-loop controls.
A practical model:
| Risk | Examples | Control |
|---|---|---|
| L0 | Search, read, summarize | Autonomous |
| L1 | Create draft, update low-risk metadata | Autonomous + audit |
| L2 | Send external email, modify customer record | User approval or policy-based |
| L3 | Refund, contract change, bulk operation | Explicit approval |
| L4 | Delete data, change permissions, move money | Step-up auth + approval / dual control |
Make the classification deterministic and policy-driven, not something the LLM gets to choose.
For example:
if operation == DELETE:
risk = CRITICAL
if amount > $10,000:
risk = HIGH
if target.data_classification == "restricted":
risk += 1
if external_party == true:
risk += 1
Avoid:
"AI Agent wants permission to continue. Allow?" Instead show:
Approve refund
Customer: Acme Industries Amount: $4,820 Destination: original payment method Reason: duplicate invoice Agent: Invoice Agent v3 Requested by: Jane Smith Expires: 10 minutes Reversible: No And the approval should bind to the exact action:
approval_id
approver
agent_id
human_principal
tool
operation
normalized_parameters_hash
target_resource
timestamp
expiration
authentication_method
policy_version
If the agent subsequently changes $4,820 to $48,200, the approval becomes invalid.
OWASP recommends exactly this kind of parameter-bound, short-lived approval artifact for high-impact operations.
Don't just log:
Agent updated invoice.
Log the entire causal chain.
A useful event might look like:
{
"event_id": "evt_98231",
"timestamp": "...",
"tenant_id": "acme",
"human": {
"user_id": "usr_123"
},
"agent": {
"agent_id": "agt_invoice",
"version": "3.4.1"
},
"task": {
"task_id": "task_84721"
},
"action": {
"tool": "erp",
"operation": "create_adjustment",
"resource": "invoice_9912",
"parameters_hash": "sha256:..."
},
"authorization": {
"policy_id": "invoice-adjustment-v7",
"decision": "allow",
"capability_id": "cap_456"
},
"approval": {
"required": true,
"approval_id": "apr_789",
"approver": "usr_456"
},
"result": {
"status": "success",
"external_request_id": "..."
}
}
This lets you answer the important forensic question:
Who authorized what, which agent did it, under which policy, against which resource, with what parameters, and what actually happened? NIST specifically identifies tamper-resistant auditing and binding agent actions back to human authorization as core open problems.
You generally don't need—or want—to store private model reasoning.
Store:
This gives you accountability without turning your audit system into a repository of sensitive model context.
For multi-step agents, propagate something like:
trace_id
task_id
action_id
parent_action_id
delegation_id
Then an incident investigator can see:
Task 84721
├── Search invoices
├── Read vendor profile
├── Detect duplicate
├── Create adjustment
│ └── Approval #991
└── Send notification
This becomes especially important when agents call other agents or external services.
A common mistake is thinking:
"We'll prevent prompt injection with a better system prompt." Even if an attacker successfully manipulates the model, the attacker should still be unable to make it do anything outside its capability boundary.
For example, an agent reading a malicious customer email might be tricked into attempting:
send_email(...)
But if the current task only grants:
crm:read
invoice:read
the execution gateway rejects it.
That's why least privilege is so important: prompt injection should ideally turn into a denied tool call, not a security incident. OWASP specifically recommends per-tool/per-operation authorization and backend enforcement rather than relying on prompts.
Don't implement approvals as a Boolean:
approved = true
Use something closer to:
PROPOSED
↓
POLICY_CHECKED
↓
AWAITING_APPROVAL
↓
APPROVED
↓
EXECUTING
↓
SUCCEEDED
With terminal/error paths:
REJECTED
EXPIRED
CANCELLED
POLICY_CHANGED
PARAMETERS_CHANGED
EXECUTION_FAILED
Critically:
Changing the action after approval should send it back to approval.
And:
Changing the policy should invalidate pending approvals when appropriate.
Your SaaS admin console should eventually expose something like:
Agent: Customer Success Agent
Can:
✓ Read customer records
✓ Create internal notes
✓ Draft emails
Cannot:
✗ Delete customers
✗ Change permissions
✗ Issue refunds
Requires approval:
→ Send external email
→ Change subscription
→ Issue credit > $500
Limits:
Maximum credit/day: $5,000
Allowed domains: *.acme.com
Data: Customer Support only
Importantly, don't let the agent compose its own permission/consent UI. The permission manifest should come from trusted application policy. OWASP calls out this distinction explicitly.
I'd structure a B2B SaaS implementation approximately like this:
┌────────────────────┐
│ Human / Admin │
└─────────┬──────────┘
│
delegation
↓
┌───────────────┐ ┌────────────────────┐
│ Agent │─────→│ Agent Gateway │
│ / Model │ └─────────┬──────────┘
└───────────────┘ │
↓
┌───────────────────┐
│ Policy Decision │
│ Point │
└─────────┬─────────┘
│
┌────────────┴────────────┐
↓ ↓
┌─────────────┐ ┌─────────────┐
│ Approval │ │ Deny │
│ Service │ └─────────────┘
└──────┬──────┘
↓
┌───────────────┐
│ Execution │
│ Gateway │
└───────┬───────┘
↓
SaaS APIs / DBs
│
↓
┌───────────────┐
│ Audit/Event │
│ Log │
└───────────────┘
The policy engine and execution gateway are the security boundary, not the LLM.
For an early-stage B2B SaaS product, I wouldn't start with a huge enterprise IAM system. I'd ship these primitives:
Then make everything else—RBAC, ABAC, SSO, sophisticated policy languages, cross-tenant delegation, multi-agent authorization—build on those primitives.
The biggest architectural mistake to avoid is "the agent has the same permissions as the user, and we'll ask the user before dangerous things." That gives you a privileged, probabilistic actor with a thin confirmation layer. A much stronger model is "the agent has its own identity, receives a narrow delegation, every action passes an external policy check, and humans approve only actions whose risk warrants it." That's also broadly consistent with where NIST and OWASP's current agent-security guidance is heading.
Then an incident investigator can see:
Task 84721
├── Search invoices
├── Read vendor profile
├── Detect duplicate
├── Create adjustment
│ └── Approval #991
└── Send notification
This becomes especially important when agents call other agents or external services.
A common mistake is thinking:
"We'll prevent prompt injection with a better system prompt." Even if an attacker successfully manipulates the model, the attacker should still be unable to make it do anything outside its capability boundary.
For example, an agent reading a malicious customer email might be tricked into attempting:
send_email(...)
But if the current task only grants:
crm:read
invoice:read
the execution gateway rejects it.
That's why least privilege is so important: prompt injection should ideally turn into a denied tool call, not a security incident. OWASP specifically recommends per-tool/per-operation authorization and backend enforcement rather than relying on prompts.
Don't implement approvals as a Boolean:
approved = true
Use something closer to:
PROPOSED
↓
POLICY_CHECKED
↓
AWAITING_APPROVAL
↓
APPROVED
↓
EXECUTING
↓
SUCCEEDED
With terminal/error paths:
REJECTED
EXPIRED
CANCELLED
POLICY_CHANGED
PARAMETERS_CHANGED
EXECUTION_FAILED
Critically:
Changing the action after approval should send it back to approval.
And:
Changing the policy should invalidate pending approvals when appropriate.
Your SaaS admin console should eventually expose something like:
Agent: Customer Success Agent
Can:
✓ Read customer records
✓ Create internal notes
✓ Draft emails
Cannot:
✗ Delete customers
✗ Change permissions
✗ Issue refunds
Requires approval:
→ Send external email
→ Change subscription
→ Issue credit > $500
Limits:
Maximum credit/day: $5,000
Allowed domains: *.acme.com
Data: Customer Support only
Importantly, don't let the agent compose its own permission/consent UI. The permission manifest should come from trusted application policy. OWASP calls out this distinction explicitly.
I'd structure a B2B SaaS implementation approximately like this:
┌────────────────────┐
│ Human / Admin │
└─────────┬──────────┘
│
delegation
↓
┌───────────────┐ ┌────────────────────┐
│ Agent │─────→│ Agent Gateway │
│ / Model │ └─────────┬──────────┘
└───────────────┘ │
↓
┌───────────────────┐
│ Policy Decision │
│ Point │
└─────────┬─────────┘
│
┌────────────┴────────────┐
↓ ↓
┌─────────────┐ ┌─────────────┐
│ Approval │ │ Deny │
│ Service │ └─────────────┘
└──────┬──────┘
↓
┌───────────────┐
│ Execution │
│ Gateway │
└───────┬───────┘
↓
SaaS APIs / DBs
│
↓
┌───────────────┐
│ Audit/Event │
│ Log │
└───────────────┘
The policy engine and execution gateway are the security boundary, not the LLM.
For an early-stage B2B SaaS product, I wouldn't start with a huge enterprise IAM system. I'd ship these primitives:
Then make everything else—RBAC, ABAC, SSO, sophisticated policy languages, cross-tenant delegation, multi-agent authorization—build on those primitives.
The biggest architectural mistake to avoid is "the agent has the same permissions as the user, and we'll ask the user before dangerous things." That gives you a privileged, probabilistic actor with a thin confirmation layer. A much stronger model is "the agent has its own identity, receives a narrow delegation, every action passes an external policy check, and humans approve only actions whose risk warrants it." That's also broadly consistent with where NIST and OWASP's current agent-security guidance is heading.
A B2B SaaS team should treat an AI agent as a **new type of actor with delegated authority**, not as a clever feature inside the existing user session. The core design goal is: > Every agent action should be explainable: **who requested it, which agent performed it, what permissions allowed it, what policy allowed…
A B2B SaaS team should treat an AI agent as a new type of actor with delegated authority, not as a clever feature inside the existing user session. The core design goal is:
Every agent action should be explainable: who requested it, which agent performed it, what permissions allowed it, what policy allowed it, who approved it (if needed), and what changed. A good architecture usually has five layers: identity → permissions → policy → approvals → auditability. Enterprise guidance increasingly converges on dedicated agent identities, least-privilege scopes, separate read/write tools, approval gates, and centralized audit events.
Avoid:
Instead create an identity model like:
Human User
|
| requested task
v
AI Agent Instance
|
| authorized by
v
Agent Policy Engine
|
| calls
v
Tools / APIs / Resources
Your audit system should distinguish:
user_123agent_customer_success_assistantrun_456crm.update_contactcustomer_record_789This prevents the classic failure mode where an investigation cannot tell whether a person or an automation changed something.
A useful agent registry:
| Field | Example |
|---|---|
| Agent ID | agent.billing-assistant |
| Owner | VP Finance |
| Purpose | "Resolve invoice disputes" |
| Allowed systems | Stripe, CRM |
| Risk tier | Medium |
| Expiration/review date | Quarterly |
| Emergency disable | Yes |
Traditional SaaS RBAC often answers:
"Can this user access invoices?" Agents need a more granular question:
"Can this agent perform this exact action on this exact resource under this condition?" Model permissions across several dimensions:
Permission =
actor
+ tenant
+ resource
+ action
+ environment
+ conditions
Example:
{
"agent": "invoice-agent",
"tenant": "acme",
"resource": "invoice",
"actions": [
"read",
"draft_reply"
],
"conditions": {
"amount_less_than": 5000,
"customer_region": "US"
}
}
Avoid broad permissions like:
invoice:*
customer:*
admin:*
Prefer:
invoice.read
invoice.create_draft
invoice.send_after_approval
Microsoft's guidance for agent systems similarly recommends scoping permissions by tool, separating read/write actions, and enforcing policy checks before execution.
The model should never be the final authorization layer.
Bad:
User request
↓
LLM decides
↓
API call
Better:
User request
↓
LLM proposes action
↓
Policy engine evaluates
↓
Approval check
↓
Tool executes
↓
Audit event written
The LLM can suggest:
"Refund customer $800" The policy engine decides:
Do not make autonomy binary. Define action classes.
Example:
| Risk | Agent capability | Approval |
|---|---|---|
| Low | Read docs, summarize tickets | None |
| Medium | Draft emails, create records | Optional review |
| High | Send external messages, modify customer data | Required |
| Critical | Delete data, change permissions, move money | Multi-step approval |
A practical rollout path:
Agent can:
Agent can:
Human submits.
Agent can:
Only after you have:
Human checkpoints should be placed according to risk, such as before execution, during workflows, after execution but before integration, or through sampling reviews.
An approval should not mean:
"Approve whatever the agent wants." It should approve a specific intent.
Example:
Approval Request
Agent:
Renewal Assistant
Requested action:
Extend contract for Customer A
Changes:
Term: +12 months
Discount: 5%
Reason:
Customer renewal workflow
Evidence:
Usage history
Contract status
Expires:
30 minutes
Approve / Reject
Important properties:
If the agent changes the action after approval, require a new approval.
Do not allow stale approvals to execute days later.
Record:
Application logs answer:
"Did the API fail?" Agent audit logs need to answer:
"Why did the system do this?" A good event schema:
{
"event_id": "evt_123",
"timestamp": "2026-09-09T08:10:00Z",
"actor": {
"user_id": "user_42",
"agent_id": "sales-agent"
},
"request": {
"goal": "update opportunity status",
"conversation_id": "conv_99"
},
"authorization": {
"policy": "sales_update_policy",
"decision": "allowed"
},
"tool": {
"name": "crm.update_opportunity",
"arguments_hash": "abc123"
},
"approval": {
"required": true,
"approved_by": "manager_7"
},
"result": {
"status": "success",
"resource_changed": "opp_555"
}
}
Log:
Avoid storing:
Production agents need:
Ability to immediately:
Regularly check:
You should be able to reconstruct:
User request
→ Agent reasoning trace
→ Tool calls
→ Policy decisions
→ Approvals
→ Final outcome
A common production pattern:
Customer User
|
v
Agent Orchestrator
|
+-------------+-------------+
| |
v v
Policy Engine Audit Service
|
v
Approval Service
|
v
Tool Gateway
|
+------+------+------+
CRM Billing Database
Key principle:
The agent should ask for permission. The platform should grant permission.
You do not need a giant governance platform on day one. Start with:
That foundation will scale much better than adding controls after customers start asking "why did your AI change my data?"
Avoid storing:
Production agents need:
Ability to immediately:
Regularly check:
You should be able to reconstruct:
User request
→ Agent reasoning trace
→ Tool calls
→ Policy decisions
→ Approvals
→ Final outcome
A common production pattern:
Customer User
|
v
Agent Orchestrator
|
+-------------+-------------+
| |
v v
Policy Engine Audit Service
|
v
Approval Service
|
v
Tool Gateway
|
+------+------+------+
CRM Billing Database
Key principle:
The agent should ask for permission. The platform should grant permission.
You do not need a giant governance platform on day one. Start with:
That foundation will scale much better than adding controls after customers start asking "why did your AI change my data?"
Building trust, security, and governance for AI agents acting on behalf of users requires `a shift from static, human-centric access control to dynamic, agent-aware authorization` . Because AI agents can chain actions autonomously across multiple systems, a single compromised or hallucinated instruction can cause…
Building trust, security, and governance for AI agents acting on behalf of users requires a shift from static, human-centric access control to dynamic, agent-aware authorization . Because AI agents can chain actions autonomously across multiple systems, a single compromised or hallucinated instruction can cause massive damage in seconds.
Here is how a B2B SaaS team should architect permissions, audit logs, and approval flows for AI agents.
AI agents shouldn't just "inherit" the user's full permissions—that violates the principle of least privilege. Instead, you need delegated authorization models similar to OAuth 2.0 scopes, but with fine-grained guardrails.
read, suggest, execute ) and where they can do it (e.g., restricted to a specific CRM pipeline or data partition, not the whole database).Traditional audit logs record who (User ID) and what (API endpoint). AI audit logs must capture intent, reasoning provenance, and non-determinism.
You need a risk-tiered approach to approvals. Not every action requires a human click, but high-impact actions demand strict asynchronous or real-time verification.
If you'd like to dive deeper, let me know:
I can help you design a concrete data schema or a step-by-step implementation strategy for your architecture.
No sources cited
Building enterprise-grade controls for autonomous or semi-autonomous AI agents requires shifting from human-centric Identity and Access Management (IAM) to **agentic governance** . Because AI agents operate at scale and make decisions via probabilistic reasoning rather than deterministic scripts, a failure in security…
Building enterprise-grade controls for autonomous or semi-autonomous AI agents requires shifting from human-centric Identity and Access Management (IAM) to agentic governance . Because AI agents operate at scale and make decisions via probabilistic reasoning rather than deterministic scripts, a failure in security or logic can cascade instantly.
Here is how a B2B SaaS team should architect permissions, audit logs , and approval flows for AI agents.
Traditional Role-Based Access Control (RBAC) assumes a human is clicking the buttons. For AI agents, you need Delegated Authorization —the agent acts on behalf of a user, but with strict scoping.
jira:get_ticket, salesforce:update_stage ), rejecting all unlisted tool calls by default.Standard application logs (User X updated Record Y ) are insufficient for AI. You need to capture the cognitive chain that led to the action so you can debug hallucinations, security exploits (like prompt injection), and compliance failures.
agent-support-v2.1 using gpt-4o).Deciding when an agent needs human validation is a sliding scale based on the reversibility and impact of the action.
If you'd like, let me know:
I can help tailor a specific security architecture or compliance checklist for your stack.
No sources cited
For a B2B SaaS product, I’d treat an AI agent as a **delegated principal**, not as a magical extension of the logged-in user. The core architecture should be: **Human → Delegation → Agent identity → Policy engine → Tool/API → Resource**
For a B2B SaaS product, I’d treat an AI agent as a delegated principal, not as a magical extension of the logged-in user.
The core architecture should be:
Human → Delegation → Agent identity → Policy engine → Tool/API → Resource
with an independent audit/event stream spanning the whole chain.
This aligns with current guidance from NIST, OWASP, and AWS: separate agent and human identities, propagate user context without handing the agent the user's credentials, enforce authorization outside the model, and use risk-tiered human approval for consequential actions.
Don't put everything into user_id.
Every agent action should have at least:
tenant_id
user_id # who initiated/delegated
agent_id # which agent performed it
agent_run_id # this execution
delegation_id # why the agent is allowed to act
For example:
Alice
└── delegates "Support Agent"
└── run 8f31...
└── update_ticket(ticket=123)
The agent gets its own service identity and narrowly scoped credentials. It should never simply assume Alice's complete role or credentials. AWS specifically recommends separating human and agent permissions while propagating the user's context as signed claims.
This distinction becomes extremely valuable during incident response:
"Alice authorized the support agent to work on tickets in Acme Corp" is very different from:
"Alice personally changed ticket #123." Your audit system should make that distinction impossible to lose.
Traditional SaaS RBAC is still useful:
User
├── Admin
├── Manager
└── Member
But agents need an additional authorization layer.
I'd use something like:
Can agent X
perform action Y
on resource Z
for tenant T
on behalf of user U
under delegation D?
So instead of:
if user.role == "admin":
allow()
you evaluate:
authorize(
principal = agent_id,
actor = user_id,
tenant = tenant_id,
action = "invoice.send",
resource = invoice_id,
delegation = delegation_id,
context = {...}
)
The policy engine should enforce things such as:
Most importantly: the LLM doesn't make this decision.
The model can request invoice.send; an authorization service decides whether that operation is permitted. OWASP and AWS both explicitly recommend authorization at the tool/API boundary rather than relying on the agent to police itself.
Think in terms of capabilities.
Bad:
SupportAgent:
access = "all CRM APIs"
Better:
SupportAgent:
ticket.read
ticket.comment
customer.read
ticket.delete ❌
customer.export ❌
billing.refund ❌
And make permissions parameterized where possible:
ticket.update
tenant = acme
fields = [status, priority, comment]
rather than:
ticket.update(*)
Short-lived credentials and dynamically scoped permissions are preferable to permanent, broad agent credentials.
I'd also make permissions versioned. An agent run should know which policy version authorized each action.
Don't ask the LLM:
"Is this action risky?" That's circular: the same potentially compromised model that wants to perform the action shouldn't be the final authority deciding whether it needs approval.
Instead, classify actions using deterministic policy.
A practical initial model:
| Tier | Examples | Control |
|---|---|---|
| 0 — Read | Search docs, read ticket | Autonomous |
| 1 — Reversible write | Add comment, update status | Autonomous or notify |
| 2 — Consequential | Send external email, change contract | Approval |
| 3 — High impact | Refund $10k, delete data, change permissions | Strong approval / step-up auth |
| 4 — Prohibited | Cross-tenant access, bypass security controls | Always deny |
Risk should depend on both action and context.
For example:
send_email
+ external recipient
+ 5,000 recipients
+ confidential attachment
= high risk
while:
send_email
+ internal recipient
+ preapproved template
+ no sensitive data
= low risk
OWASP recommends explicit approval for high-impact/irreversible actions, while AWS recommends tiered autonomy rather than forcing humans to approve everything.
This is one of the most important design details.
Don't implement:
user approved agent
→ let agent continue for 30 minutes
Instead approve a specific operation or tightly bounded scope.
For example:
{
"approval_id": "apr_8291",
"tenant_id": "acme",
"user_id": "alice",
"agent_id": "billing-agent",
"action": "refund",
"resource": "invoice_123",
"parameters_hash": "sha256:...",
"amount": 850,
"policy_version": "42",
"expires_at": "...",
"status": "approved"
}
Then the execution service checks:
Is this exact action
+ exact resource
+ exact parameters
+ same tenant
+ same delegation
+ still within expiry
+ still authorized
+ associated with this approval?
If not: deny.
That prevents a classic failure mode:
User approves "refund $50 for invoice A" → agent mutates the request into "refund $5,000 for invoice B." OWASP specifically recommends binding approvals to the actor, tool, target, normalized parameters, timestamp and expiry, with replay protection for irreversible actions.
The approval screen should show the real proposed operation, not:
"The AI wants to do something. Approve?" Show:
Support Agent wants to:
Refund $850 to Acme Corp
Invoice: INV-123
Reason: Duplicate charge
Changes:
- Refund amount: $850
- Payment method: Visa •••• 4242
External effects:
- Stripe refund
- Customer notification email
Agent:
Billing Agent v7
Requested by:
Alice Smith
[Reject] [Approve]
For particularly sensitive actions, require step-up authentication.
And importantly, don't blindly expose arbitrary agent-generated HTML/text in an approval dialog. Approval context itself can be an injection surface.
AWS recommends giving reviewers sufficient context, logging their identity and decision, and using timeouts/escalation rather than allowing approvals to remain indefinitely valid.
Don't make audit logs an afterthought attached to database mutations.
Create a first-class agent event stream.
I'd capture events like:
agent.run.started
agent.tool.requested
authorization.allowed
authorization.denied
approval.requested
approval.approved
approval.rejected
approval.expired
tool.executed
tool.failed
agent.run.completed
agent.run.cancelled
Each event should carry:
event_id
timestamp
tenant_id
user_id
agent_id
agent_run_id
delegation_id
approval_id
action
resource_type
resource_id
policy_version
authorization_decision
risk_tier
tool_name
normalized_parameters_hash
result
error_code
request_id
parent_event_id
That gives you a reconstructable chain:
User request
↓
Agent run
↓
Tool request
↓
Policy evaluation
↓
Approval
↓
Execution
↓
External side effect
NIST's current agent-identity work explicitly calls out the need for auditable agent identity, authorization, non-repudiation, and binding agent actions back to human authorization.
There's an important distinction between auditability and logging the entire LLM context.
Full prompts/responses can contain:
Instead, maintain:
Audit log
"Agent requested customer.export"
"Policy denied"
"Reason: export requires admin + approval"
and separately, where appropriate:
Trace/debug store
prompt
retrieved documents
tool responses
model output
reasoning metadata
with substantially tighter access controls and retention.
AWS likewise notes that lineage needs to be comprehensive enough to reconstruct execution while balancing privacy, storage, and performance.
The architecture I would actually deploy looks roughly like this:
┌─────────────────┐
│ User │
└────────┬────────┘
│
delegation
│
┌────────▼────────┐
│ Agent runtime │
└────────┬────────┘
│
tool request
│
┌────────▼────────┐
│ Policy Gateway │
│ │
│ identity │
│ tenant │
│ RBAC │
│ agent scopes │
│ risk │
│ approval │
└──────┬───┬──────┘
│ │
deny │ │ approve
│ │
│ ┌▼──────────┐
│ │ Approval │
│ │ workflow │
│ └────┬─────┘
│ │
└───────┼───────► Tool/API
│
Audit events
│
┌──────▼──────┐
│ Audit/Event │
│ Ledger │
└─────────────┘
Never make the policy gateway optional.
If an agent can call one tool directly around the gateway, you've created a privilege-escalation path.
I'd introduce a database model roughly like:
delegations
------------
id
tenant_id
user_id
agent_id
allowed_actions
resource_scope
max_risk_tier
spending_limit
requires_approval
expires_at
created_at
revoked_at
This lets your product support things like:
"Let the recruiting agent manage interview scheduling for 30 days, but don't let it contact candidates without approval." Or:
"Let the finance agent process refunds up to $500 autonomously." That's considerably more powerful than merely saying:
"This user has access to Finance."
You need at least three emergency controls:
User-level
Revoke Alice's delegation
Agent-level
Disable Billing Agent globally
Tenant-level
Disable autonomous actions for Acme
And ideally:
Pause all agent runs
Revoke outstanding approvals
Invalidate agent credentials
These should work independently of the LLM runtime.
For runaway behavior, add:
AWS's current agent guidance specifically recommends rate controls, circuit breakers, runtime guardrails and monitoring for abnormal behavior.
If you're a SaaS team building this now, I wouldn't start with a gigantic authorization platform.
Build these six primitives:
AgentIdentityagent_id
tenant_id
agent_type
version
status
Delegationdelegation_id
user_id
agent_id
scopes
expires_at
PolicyDecisionallow | deny | require_approval
Approvalapproval_id
action_hash
approver
expires_at
decision
AgentEventwho
did_what
to_what
on_whose_behalf
under_which_policy
with_which_approval
what_happened
Policy GatewayEvery tool invocation goes through it.
With those primitives, you can evolve from:
agent → tool
to:
user
→ delegation
→ agent
→ policy
→ approval
→ tool
→ audit
without rewriting your entire agent architecture.
The biggest conceptual shift is:
Don't ask "What can this AI do?" Ask "Under what delegation, identity, policy, and approval is this specific action allowed?" That gives you a clean separation:
That separation is what makes an agent system governable rather than merely observable. OWASP's newly published Agent Control Standard is also moving toward this model of runtime-enforced, inspectable controls rather than relying on agent behavior alone.
If you're a SaaS team building this now, I wouldn't start with a gigantic authorization platform.
Build these six primitives:
AgentIdentityagent_id
tenant_id
agent_type
version
status
Delegationdelegation_id
user_id
agent_id
scopes
expires_at
PolicyDecisionallow | deny | require_approval
Approvalapproval_id
action_hash
approver
expires_at
decision
AgentEventwho
did_what
to_what
on_whose_behalf
under_which_policy
with_which_approval
what_happened
Policy GatewayEvery tool invocation goes through it.
With those primitives, you can evolve from:
agent → tool
to:
user
→ delegation
→ agent
→ policy
→ approval
→ tool
→ audit
without rewriting your entire agent architecture.
The biggest conceptual shift is:
Don't ask "What can this AI do?" Ask "Under what delegation, identity, policy, and approval is this specific action allowed?" That gives you a clean separation:
Think in terms of capabilities.
Bad:
To build production-ready governance for AI agents, B2B SaaS teams must implement **delegated authorization, dual-identity audit trails, and human-in-the-loop approval gates**.[](https://geekpython.medium.com/how-to-authenticate-ai-agents-in-b2b-saas-delegated-auth-scoped-tokens-and-audit-trails-3b7b31f97878)…
To build production-ready governance for AI agents, B2B SaaS teams must implement delegated authorization, dual-identity audit trails, and human-in-the-loop approval gates.
Permissions: Delegated and Task-Scoped Access
Do not give AI agents broad user credentials or static service accounts. Instead, use runtime boundaries that limit what an agent can touch.
Audit Logs: Dual-Identity and Chain of Custody
Standard system logs that record only a generic service principal fail enterprise compliance checks like SOC 2.
Approval Flows: Just-in-Time and Step-Up Gates
Autonomous loops need hard boundaries before executing high-impact actions (e.g., mass data deletions, financial transactions, or external communications).
If you'd like, let me know:
I can help map out a precise data schema or step-by-step implementation guide.
For a B2B SaaS product, I’d treat an AI agent as a **new type of principal**—not as “the user, but automated.” The core architecture should be: > **Human identity + agent identity + delegated scope + policy decision + approval + immutable audit trail** That separation is the difference between an agent system you can…
For a B2B SaaS product, I’d treat an AI agent as a new type of principal—not as “the user, but automated.” The core architecture should be:
Human identity + agent identity + delegated scope + policy decision + approval + immutable audit trail That separation is the difference between an agent system you can govern and one that eventually becomes an untraceable service account with a chat interface.
Every action should carry at least:
tenant_id
user_id # human who initiated/delegated
agent_id # which agent performed it
session_id # particular agent run
request_id # particular user request
tool_id # capability invoked
authorization_context # effective permissions/scope
approval_id # if approval was required
Don't simply have the agent "be" the user.
A good model is:
Alice
│
│ delegates
▼
Agent: "Sales Assistant"
│
│ requests tool
▼
Authorization Service
│
├── Alice's permissions
├── Agent's allowed capabilities
├── Tenant policies
├── Resource-level constraints
└── Approval requirements
│
▼
Tool/API
The agent gets a short-lived delegated credential/context, rather than Alice's long-lived credentials or an unrestricted impersonation role. AWS's current agent-security guidance makes the same distinction: agent and human identities should remain separate, while user context is propagated for downstream authorization.
Don't put permissions into the system prompt:
"You are allowed to access Salesforce but not billing." That is guidance, not security.
Instead, put a policy enforcement point between the model and every consequential tool:
Agent → Tool Gateway → Authorization Policy → Tool
For example:
{
"actor": {
"user": "u_123",
"agent": "agent_sales_01",
"tenant": "acme"
},
"action": "invoice.refund",
"resource": "invoice_987",
"amount": 8500
}
The policy engine evaluates:
Can this user perform this action?
AND
Can this agent perform this action?
AND
Is this resource in scope?
AND
Is this action allowed for this agent mode?
AND
Does it require approval?
Critically, authorize every tool invocation, not just when the agent session starts. OWASP specifically recommends per-request authorization for agent data access and tool use.
Traditional RBAC is a useful starting point, but agents usually need finer constraints.
Think in terms of:
Effective access =
User permissions
∩ Agent capabilities
∩ Tenant policy
∩ Resource constraints
∩ Runtime conditions
For example:
User: Finance Manager Agent: Accounts Payable Agent Agent capability: Create payments Runtime limit: ≤ $5,000 Resource: Company's US bank account Approval: Required above $1,000
So the agent cannot conclude:
"I'm acting for a Finance Manager, therefore I can make any payment." Its capability is deliberately narrower.
This is essentially least privilege applied to an autonomous principal; AWS recommends dynamic boundaries and explicit ceilings precisely because agent behavior can otherwise turn broad permissions into unexpectedly large blast radii.
A common mistake is granting:
salesforce.write = true
Instead define business actions:
crm.customer.read
crm.customer.update
crm.customer.delete
invoice.create
invoice.send
invoice.refund
contract.create
contract.execute
user.invite
user.delete
Then attach metadata:
Action: invoice.refund
Risk: HIGH
Reversible: NO
External_effect: YES
Approval: REQUIRED
Max_amount: $5,000
Allowed_agent_types:
- accounts_payable
This gives you a clean place to implement policy and approval logic.
It also means the model cannot manufacture a novel API call that happens to circumvent your intended permission model.
I'd start with three or four levels:
| Tier | Example | Handling |
|---|---|---|
| L0 — Read | Search CRM, retrieve document | Autonomous |
| L1 — Low-impact write | Update CRM field | Autonomous or notify |
| L2 — Material action | Send customer email, create ticket | Approval depending on policy |
| L3 — High-impact | Refund money, delete data, change permissions | Explicit approval |
Don't make humans approve everything. That produces approval fatigue and eventually rubber-stamping. Current agent-security guidance similarly recommends risk-tiered oversight rather than either "approve everything" or "approve nothing."
Also make risk classification deterministic. Don't ask the same LLM that wants to perform an action whether its action is safe.
This is one of the most important design details.
Bad:
"Alice approved the agent to send emails." Good:
Alice approved this exact email, to these recipients, with this content, using this agent, until 14:30. An approval record might contain:
{
"approval_id": "apr_123",
"tenant_id": "acme",
"user_id": "u_123",
"agent_id": "agent_7",
"action": "invoice.refund",
"resource": "invoice_987",
"parameters_hash": "sha256:...",
"risk_tier": "L3",
"approved_by": "u_123",
"approved_at": "...",
"expires_at": "...",
"policy_version": "policy_42"
}
The executor then verifies:
approval exists
AND not expired
AND correct user
AND correct agent
AND correct action
AND correct resource
AND parameters still match
AND current policy still permits it
This prevents the classic vulnerability where an agent gets approval for one action and subsequently mutates the parameters.
OWASP's agent guidance explicitly recommends binding approval to the actor, tool, target, normalized parameters, timestamp, and expiry, with replay protection for high-impact actions.
Don't implement:
Agent: "Can I refund $500?"
User: "yes"
Agent: *later refunds $900*
Instead, the approval should mint something equivalent to a short-lived, narrowly scoped capability:
approval:
action = invoice.refund
invoice = 987
amount = 500
expires = 5 minutes
The executor refuses anything outside that capability.
This also makes approvals composable for multi-step agents.
Your audit trail should be authoritative infrastructure, not something the agent writes.
The agent should not have:
INSERT audit_log
UPDATE audit_log
DELETE audit_log
Instead:
Agent
↓
Execution Gateway
↓
Audit Event Pipeline
↓
Append-only Audit Store
OWASP's current auditability guidance makes this point strongly: the agent runtime should not be able to modify or delete the authoritative audit trail.
A useful audit event looks like:
{
"event_id": "evt_123",
"timestamp": "...",
"tenant_id": "acme",
"user_id": "u_123",
"agent_id": "agent_sales",
"session_id": "sess_456",
"request_id": "req_789",
"event_type": "tool_execution",
"tool": "invoice.refund",
"resource": "invoice_987",
"requested_parameters_hash": "...",
"policy": {
"decision": "allow",
"policy_version": "v42"
},
"approval": {
"required": true,
"approval_id": "apr_123",
"approved_by": "u_123"
},
"execution": {
"status": "success",
"external_request_id": "stripe_456"
}
}
I'd additionally capture:
Don't necessarily dump the model's entire chain-of-thought into the audit system. What you need is decision-relevant provenance, not private reasoning traces.
Your UI should let an administrator answer:
What did the agent do to my tenant last Tuesday? and drill down:
Alice asked:
"Clean up the overdue accounts."
↓
Sales Agent started session
↓
Read 137 accounts
✓ authorized
↓
Updated 23 CRM records
✓ authorized
↓
Attempted to send 7 emails
⚠ approval required
↓
Alice approved 6
Alice denied 1
↓
6 emails sent
1 blocked
This is far more useful than a log saying:
POST /crm/update 200
The reviewer shouldn't have to understand what the model was "thinking."
Show:
What will happen
Send $8,500 refund to Acme Corp. Why
Agent determined invoice 123 was incorrectly charged. Scope
Invoice 123 only. Side effects
Customer receives refund notification. Risk
High — financial transaction. Expiration
Approval valid for 5 minutes. Policy
Finance policy FP-17. Then:
AWS similarly recommends giving reviewers sufficient action context and logging the reviewer, decision, timestamp, and escalation information.
For high-impact operations:
Policy unavailable → DENY
Approval service unavailable → DENY
Audit service unavailable → DENY
Risk classification unavailable → DENY
Approval expired → DENY
Parameters changed → DENY
Agent identity invalid → DENY
Don't let:
authorization_service_timeout
turn into:
¯\_(ツ)_/¯ → execute()
OWASP explicitly recommends failing closed when policy, approval validation, risk classification, or audit logging fails for high-impact operations.
Permissions answer what an agent can do. You also need how much/how often.
Examples:
max_refund_amount = $5,000
max_emails_per_hour = 100
max_records_modified_per_run = 500
max_api_cost = $20
max_runtime = 10 minutes
And circuit breakers:
> 50 deletes in 60 seconds → stop
> 3 authorization failures → pause
> unusual geography → require approval
> unusual volume → require approval
This gives you protection against both malicious behavior and ordinary model failure.
If:
User
↓
Manager Agent
↓
Research Agent
↓
Billing Agent
don't let the downstream agent inherit unrestricted authority.
Represent:
delegated_by = Manager Agent
parent_session = sess_123
original_user = Alice
effective_scope = intersection(parent_scope, child_scope)
The child can only become less privileged, never more privileged.
That's particularly important because otherwise an agent can effectively turn:
"I need help completing this task" into:
"Give my sub-agent access to everything." Current guidance explicitly calls out preventing privilege escalation through agent-to-agent delegation.
For B2B SaaS, I'd expose something like:
Accounts Payable Agent
Allowed capabilities
Restricted
Limits
$10,000 requires two approvers
Data scope
Credentials
Audit
That turns agent governance into something your customers' security teams can actually understand and manage.
Agent permissions will drift as your product evolves.
Build reports such as:
Agent permissions:
47 granted
12 used
35 unused
New permissions:
3 added this month
High-risk capabilities:
4
Last review:
21 days ago
Then automatically flag:
Regular automated analysis plus risk-based human reviews is also recommended in current agent security guidance.
If I were designing the platform from scratch, I'd make these five primitives first:
┌────────────────────────────────────────────┐
│ Agent Runtime │
└───────────────────┬────────────────────────┘
│ tool request
▼
┌────────────────────────────────────────────┐
│ Tool / Action Gateway │
│ │
│ 1. Authenticate agent │
│ 2. Resolve user delegation │
│ 3. Check resource authorization │
│ 4. Evaluate risk │
│ 5. Check approval │
│ 6. Enforce rate/amount limits │
└───────┬──────────────────────┬─────────────┘
│ │
│ ▼
│ ┌─────────────────┐
│ │ Approval Service│
│ └─────────────────┘
│
▼
┌───────────────────┐
│ External / SaaS │
│ API or DB │
└───────────────────┘
All decisions/events
│
▼
┌─────────────────────┐
│ Immutable Audit Log │
└─────────────────────┘
The key architectural principle is:
The LLM proposes actions. Your authorization and execution layers decide whether those actions happen. That distinction should survive even if you replace the model, change prompts, introduce autonomous scheduling, or add multi-agent orchestration.
I'd prioritize in this order:
If those ten are solid, you have the beginnings of an agent authorization platform, rather than bolting a chatbot onto your existing RBAC system.
If I were designing the platform from scratch, I'd make these five primitives first:
┌────────────────────────────────────────────┐
│ Agent Runtime │
└───────────────────┬────────────────────────┘
│ tool request
▼
┌────────────────────────────────────────────┐
│ Tool / Action Gateway │
│ │
│ 1. Authenticate agent │
│ 2. Resolve user delegation │
│ 3. Check resource authorization │
│ 4. Evaluate risk │
│ 5. Check approval │
│ 6. Enforce rate/amount limits │
└───────┬──────────────────────┬─────────────┘
│ │
│ ▼
│ ┌─────────────────┐
│ │ Approval Service│
│ └─────────────────┘
│
▼
┌───────────────────┐
│ External / SaaS │
│ API or DB │
└───────────────────┘
All decisions/events
│
▼
┌─────────────────────┐
│ Immutable Audit Log │
└─────────────────────┘
The key architectural principle is:
The LLM proposes actions. Your authorization and execution layers decide whether those actions happen. That distinction should survive even if you replace the model, change prompts, introduce autonomous scheduling, or add multi-agent orchestration.
I'd prioritize in this order:
If those ten are solid, you have the beginnings of an agent authorization platform, rather than bolting a chatbot onto your existing RBAC system.
delegated_by = Manager Agent
parent_session = sess_123
original_user = Alice
effective_scope = intersection(parent_scope, child_scope)
The child can only become less privileged, never more privileged.
That's particularly important because otherwise an agent can effectively turn:
"I need help completing this task" into:
"Give my sub-agent access to everything." Current guidance explicitly calls out preventing privilege escalation through agent-to-agent delegation.
A common mistake is granting:
salesforce.write = true
Instead define business actions:
crm.customer.read
crm.customer.update
crm.customer.delete
invoice.create
invoice.send
invoice.refund
contract.create
contract.execute
user.invite
user.delete
Then attach metadata:
Action: invoice.refund
Risk: HIGH
Reversible: NO
External_effect: YES
Approval: REQUIRED
Max_amount: $5,000
Allowed_agent_types:
- accounts_payable
This gives you a clean place to implement policy and approval logic.
It also means the model cannot manufacture a novel API call that happens to circumvent your intended permission model.
I'd start with three or four levels:
| Tier | Example | Handling |
|---|---|---|
| L0 — Read | Search CRM, retrieve document | Autonomous |
| L1 — Low-impact write | Update CRM field | Autonomous or notify |
| L2 — Material action | Send customer email, create ticket | Approval depending on policy |
| L3 — High-impact | Refund money, delete data, change permissions | Explicit approval |
Don't make humans approve everything. That produces approval fatigue and eventually rubber-stamping. Current agent-security guidance similarly recommends risk-tiered oversight rather than either "approve everything" or "approve nothing."
Also make risk classification deterministic. Don't ask the same LLM that wants to perform an action whether its action is safe.
This is one of the most important design details.
Bad:
"Alice approved the agent to send emails." Good:
Alice approved this exact email, to these recipients, with this content, using this agent, until 14:30. An approval record might contain:
{
"approval_id": "apr_123",
"tenant_id": "acme",
"user_id": "u_123",
"agent_id": "agent_7",
"action": "invoice.refund",
"resource": "invoice_987",
"parameters_hash": "sha256:...",
"risk_tier": "L3",
"approved_by": "u_123",
"approved_at": "...",
"expires_at": "...",
"policy_version": "policy_42"
}
The executor then verifies:
approval exists
AND not expired
AND correct user
AND correct agent
AND correct action
AND correct resource
AND parameters still match
AND current policy still permits it
This prevents the classic vulnerability where an agent gets approval for one action and subsequently mutates the parameters.
OWASP's agent guidance explicitly recommends binding approval to the actor, tool, target, normalized parameters, timestamp, and expiry, with replay protection for high-impact actions.
Don't implement:
Agent: "Can I refund $500?"
User: "yes"
Agent: *later refunds $900*
Instead, the approval should mint something equivalent to a short-lived, narrowly scoped capability:
approval:
action = invoice.refund
invoice = 987
amount = 500
expires = 5 minutes
The executor refuses anything outside that capability.
This also makes approvals composable for multi-step agents.
Your audit trail should be authoritative infrastructure, not something the agent writes.
The agent should not have:
INSERT audit_log
UPDATE audit_log
DELETE audit_log
Instead:
Agent
↓
Execution Gateway
↓
Audit Event Pipeline
↓
Append-only Audit Store
OWASP's current auditability guidance makes this point strongly: the agent runtime should not be able to modify or delete the authoritative audit trail.
A useful audit event looks like:
{
"event_id": "evt_123",
"timestamp": "...",
"tenant_id": "acme",
"user_id": "u_123",
"agent_id": "agent_sales",
"session_id": "sess_456",
"request_id": "req_789",
"event_type": "tool_execution",
"tool": "invoice.refund",
"resource": "invoice_987",
"requested_parameters_hash": "...",
"policy": {
"decision": "allow",
"policy_version": "v42"
},
"approval": {
"required": true,
"approval_id": "apr_123",
"approved_by": "u_123"
},
"execution": {
"status": "success",
"external_request_id": "stripe_456"
}
}
I'd additionally capture:
Don't necessarily dump the model's entire chain-of-thought into the audit system. What you need is decision-relevant provenance, not private reasoning traces.
Your UI should let an administrator answer:
What did the agent do to my tenant last Tuesday? and drill down:
Alice asked:
"Clean up the overdue accounts."
↓
Sales Agent started session
↓
Read 137 accounts
✓ authorized
↓
Updated 23 CRM records
✓ authorized
↓
Attempted to send 7 emails
⚠ approval required
↓
Alice approved 6
Alice denied 1
↓
6 emails sent
1 blocked
This is far more useful than a log saying:
POST /crm/update 200
The reviewer shouldn't have to understand what the model was "thinking."
Show:
What will happen
Send $8,500 refund to Acme Corp. Why
Agent determined invoice 123 was incorrectly charged. Scope
Invoice 123 only. Side effects
Customer receives refund notification. Risk
High — financial transaction. Expiration
Approval valid for 5 minutes. Policy
Finance policy FP-17. Then:
AWS similarly recommends giving reviewers sufficient action context and logging the reviewer, decision, timestamp, and escalation information.
For high-impact operations: