AI agents become useful when they can do more than generate text.
They can:
Read files
Query databases
Create tickets
Send emails
Call APIs
Modify repositories
Deploy applications
Execute workflowsThat capability is also the security problem.
An agent that can access every available tool effectively becomes an identity with broad privileges.
The traditional approach is often:
User
↓
AI Agent
↓
All Available ToolsA safer architecture is:
User
↓
AI Agent
↓
Capability Policy
↓
Allowed Tools
↓
Allowed Operations
↓
Allowed ResourcesThis article explores how to implement that model for AI tool plugins using capability-based security.
The central principle is simple:
An AI agent should receive only the capabilities required for the current task.
This is closely aligned with current security guidance from OWASP and Microsoft, which recommends least-privilege tool access, scoped permissions, server-side authorization, and explicit controls for sensitive operations.
What Is Capability-Based Security?
Capability-based security represents authority as an explicit capability.
Instead of saying:
Agent = Administratorwe define:
Agent
├── read_orders
├── create_ticket
└── read_customer_profileThe agent does not automatically receive everything associated with an administrator role.
Its authority is the collection of capabilities explicitly granted to it.
A useful model is:
Capability
=
Action
+
Resource
+
ConstraintsFor example:
read_customer
+
customer/123
+
read-onlyis significantly narrower than:
customer_database
+
full_accessWhy AI Agents Need Fine-Grained Capabilities
Traditional applications usually have deterministic control flow.
For example:
if (user.CanDeleteOrder)
{
DeleteOrder(orderId);
}An AI agent introduces another decision-maker:
User request
↓
LLM
↓
Tool selection
↓
Tool arguments
↓
ApplicationThe model may choose:
Which tool?
Which parameters?
Which sequence?
How many times?That makes tool authorization especially important.
OWASP recommends granting agents the minimum tools required for their specific task and implementing per-tool permission scoping, including distinctions such as read-only versus write operations.
Capability Security vs Role-Based Access Control
RBAC answers:
"What role does this identity have?"Capability security answers:
"What exact authority has been granted?"Both approaches can be useful.
| Model | Example | Granularity |
|---|---|---|
| RBAC | Agent = SupportAgent | Coarse |
| ABAC | Department = Support + Region = EU | Medium |
| Capability | Read ticket 123 | Fine |
| Resource capability | Read /reports/2026/q1 | Very fine |
In an AI system, capabilities can be derived from RBAC or ABAC policies.
The important point is that the final tool invocation should still be authorized server-side.
The Basic Architecture
A secure tool architecture can look like this:
User
|
v
AI Application
|
v
Agent/LLM
|
v
Capability Policy
|
+--------+--------+
| |
Allowed Denied
| |
v X
Tool Gateway
|
v
Tool Server
|
v
ResourceThe model should not be the final authority.
The authorization layer should be.
Never Use the System Prompt as Authorization
A weak implementation might say:
You are a support agent.
Never delete customer records.
Only read customer information.That is useful as behavioral guidance.
It is not authorization.
A prompt can be influenced by:
User input
Retrieved documents
Tool output
Web pages
Other agentsOWASP specifically recommends server-side enforcement because prompt instructions should not be relied upon to restrict tool access.
The secure architecture is:
LLM says:
"Call delete_customer"
↓
Authorization layer
↓
Does this agent have
delete_customer capability?
↓
NO
↓
RejectDefine Capabilities Explicitly
A capability can be represented as a strongly typed object.
For example, in C#:
public sealed record ToolCapability(
string Tool,
string Operation,
string Resource,
string Effect);Then define:
var capabilities = new[]
{
new ToolCapability(
"customer",
"read",
"customer:*",
"allow")
};This is only a starting point.
Production systems should normally use a structured authorization model rather than ad-hoc strings.
Add Constraints
A capability should be more specific than:
customer.readConsider:
customer.read
region = EU
department = supportor:
order.update
orderId = 12345
fields = statusThis creates a capability such as:
Update order status
for order 12345rather than:
Update any orderCapability Object
A more detailed C# model could be:
public sealed record Capability(
string Tool,
string Action,
string ResourceType,
IReadOnlySet<string> AllowedResources,
DateTimeOffset ExpiresAt);Example:
var capability = new Capability(
Tool: "orders",
Action: "read",
ResourceType: "order",
AllowedResources: new HashSet<string>
{
"order:12345",
"order:12346"
},
ExpiresAt: DateTimeOffset.UtcNow.AddMinutes(10));Now the authorization decision can consider:
Tool
Action
Resource
ExpirationUse Deny by Default
The safest starting policy is:
No capability
↓
No operationNot:
Everything allowed
↓
Try to block dangerous operationsOWASP's MCP security guidance explicitly recommends least privilege and deny-by-default authorization for agent/tool systems.
For example:
public bool IsAllowed(
Capability capability,
string tool,
string action,
string resource)
{
if (capability.ExpiresAt <= DateTimeOffset.UtcNow)
return false;
if (!string.Equals(
capability.Tool,
tool,
StringComparison.Ordinal))
return false;
if (!string.Equals(
capability.Action,
action,
StringComparison.Ordinal))
return false;
return capability.AllowedResources.Contains(resource);
}The important behavior is:
Unknown
→ Deny
Expired
→ Deny
Wrong tool
→ Deny
Wrong action
→ Deny
Wrong resource
→ DenySeparate Read and Write Capabilities
Do not combine:
customer.read
customer.write
customer.deleteinto:
customer.full_accessInstead:
customer.read
customer.update
customer.deleteshould be independent capabilities.
This allows:
Support Agent
↓
customer.readwhile:
Customer Administrator
↓
customer.read
customer.updateand:
Privileged Workflow
↓
customer.deletehas a separate authorization path.
Separate Sensitive Tools
High-risk tools should not sit in the same unrestricted tool collection as ordinary read operations.
For example:
General Agent
├── search_documents
├── get_customer
└── get_order
Privileged Agent
├── refund_order
├── delete_customer
└── deploy_applicationOWASP recommends separate tool sets for different trust levels and explicit authorization for sensitive operations.
Add Human Approval for High-Risk Capabilities
Some capabilities should require an additional control.
For example:
read_customer
↓
Automatic
update_customer
↓
Policy check
delete_customer
↓
Policy check
+
Human approvalSensitive actions can include:
Delete
Refund
Transfer money
Send external email
Change permissions
Deploy production
Rotate credentialsThe confirmation should happen outside the model's reasoning.
The user should see:
Action:
Delete customer
Resource:
Customer 12345
Reason:
Requested by current workflow
Approve?
[Yes] [No]OWASP recommends explicit user confirmation for destructive, financial, and data-sharing operations.
Do Not Ask the LLM to Approve Its Own Action
This is unsafe:
Agent:
"Should I delete the customer?"
LLM:
"Yes."
Agent:
"Delete customer."The same decision-maker is both requesting and approving the operation.
Instead:
Agent
↓
Authorization Service
↓
Human Approval
↓
ToolShort-Lived Capabilities
Capabilities should normally have limited lifetimes.
For example:
ExpiresAt =
DateTimeOffset.UtcNow.AddMinutes(10);This limits the impact of leaked or stale authority.
Microsoft's current guidance for least-privilege AI agents recommends scoped access and short-lived credentials, while OWASP's MCP guidance similarly recommends narrow scopes and ephemeral credentials where possible.
Avoid Permanent Agent Permissions
Avoid:
Agent A
↓
Permanent database.writePrefer:
Task starts
↓
Capability issued
↓
Task executes
↓
Capability expiresThis makes authority proportional to the task.
Bind Capabilities to an Identity
A capability should not be treated as an anonymous permission.
It should be associated with:
User
Agent
Application
Session/task
TenantFor example:
public sealed record CapabilityContext(
string UserId,
string AgentId,
string TenantId,
string TaskId);Then the authorization layer can evaluate:
Who?
Which agent?
Which tenant?
Which task?
Which capability?
Which resource?Multi-Tenant AI Systems Need Tenant Boundaries
Consider:
Tenant A
↓
Agent A
↓
Customer data
Tenant B
↓
Agent B
↓
Customer dataA capability such as:
customer.readis not sufficient.
It should be constrained by tenant:
customer.read
tenant = tenant-aThe authorization layer must never trust:
tenantIdbecause it came from the model.
The server should derive tenant identity from authenticated context.
Prevent Cross-Tenant Resource Access
An unsafe tool might accept:
{
"tenantId": "tenant-b",
"customerId": "123"
}and trust both fields.
A safer implementation derives the tenant from the authenticated principal:
var tenantId =
userContext.TenantId;
var customer =
await repository.GetCustomerAsync(
tenantId,
customerId,
cancellationToken);The agent supplies:
customerIdbut not the authoritative tenant identity.
Capability-Based Tool Contracts
Tool schemas should also reflect authorization boundaries.
Instead of:
{
"name": "execute_sql",
"description": "Execute SQL against the database"
}prefer domain-specific operations:
{
"name": "get_customer",
"description": "Read a customer record by ID"
}The second tool has a much smaller capability surface.
OWASP recommends avoiding unrestricted tools such as arbitrary shell access and instead exposing narrowly scoped tools with explicit allowed operations and resources.
Avoid Generic execute_command
This is dangerous:
execute_command(command)because the capability becomes:
Run arbitrary operating-system commandsA safer design might expose:
get_build_status
restart_test_service
read_deployment_logsEach operation can have its own authorization policy.
Avoid Generic Database Tools
This:
execute_sql(sql)creates a large capability.
Prefer:
get_customer
list_orders
get_order_statusIf a specialized reporting workflow truly requires SQL, isolate it behind:
Read-only database
+
Restricted schema
+
Query validation
+
Timeout
+
Resource limitsTool Authorization Middleware
Authorization should happen immediately before execution.
A simplified C# middleware might look like:
public async Task<ToolResult> ExecuteAsync(
ToolRequest request,
ToolContext context,
CancellationToken cancellationToken)
{
var decision =
await authorization.AuthorizeAsync(
context,
request,
cancellationToken);
if (!decision.Allowed)
{
return ToolResult.Denied(
decision.Reason);
}
return await toolExecutor.ExecuteAsync(
request,
cancellationToken);
}The critical property is:
Authorization
↓
Tool executionnot:
LLM
↓
Tool execution
↓
Authorization laterOnce the tool has executed, authorization is too late.
Authorization Must Be Server-Side
The client can send:
{
"tool": "refund_order",
"approved": true
}That does not mean the request is authorized.
The server should determine:
Authenticated identity
+
Current policy
+
Capability
+
Resource
+
Actionand make its own decision.
OWASP identifies reliance on client-provided identity or authorization context as a major MCP authorization weakness.
Policy Decision Point
For larger systems, centralize authorization.
Agent
↓
Tool Gateway
↓
Policy Decision Point
↓
Allow / Deny
↓
ToolThe policy engine can evaluate:
Subject
Action
Resource
EnvironmentFor example:
Subject:
agent-support-01
Action:
read
Resource:
customer:12345
Context:
tenant=tenant-aResult:
ALLOWCapability Gateway Example
A tool gateway can expose:
public interface ICapabilityAuthorizer
{
Task<AuthorizationDecision> AuthorizeAsync(
CapabilityRequest request,
CancellationToken cancellationToken);
}Then:
public sealed record CapabilityRequest(
string AgentId,
string UserId,
string TenantId,
string Tool,
string Action,
string Resource);This makes authorization explicit rather than embedding it inside every tool implementation.
Use Resource-Level Authorization
Do not stop at:
agent can call orders.readCheck:
agent can call orders.read
AND
agent can read this particular orderFor example:
orders.read
resource = order:12345This prevents an agent with legitimate read access from automatically gaining access to every record.
Attribute-Based Conditions
Capabilities can include conditions.
For example:
Action:
read_customer
Conditions:
tenant = tenant-a
region = EU
classification <= InternalAnother example:
Action:
refund_order
Conditions:
amount <= 100
currency = USD
humanApproval = trueThis is where capability-based authorization becomes more powerful than simple role checks.
Tool Result Security
Authorization does not end when the tool returns.
Tool output can contain:
Customer data
Secrets
Internal URLs
Prompt injection
Untrusted instructionsOWASP recommends treating tool responses as untrusted data because returned content can influence subsequent agent behavior.
Therefore:
Tool
↓
Output validation
↓
Redaction
↓
Agent contextis preferable to:
Tool
↓
Raw output
↓
LLMRedact Sensitive Fields
Suppose the database returns:
{
"name": "Alice",
"email": "[email protected]",
"passwordHash": "...",
"internalNotes": "..."
}The tool should return only what the agent needs:
{
"name": "Alice",
"email": "[email protected]"
}Do not rely on the LLM to ignore sensitive fields.
Data minimization belongs at the tool boundary.
Prevent Capability Leakage
Never expose internal authorization details unnecessarily.
Avoid returning:
Agent has:
database.admin
secrets.read
production.deployto the model unless the information is genuinely required.
A denied result can simply say:
{
"status": "denied",
"reason": "Operation is not permitted."
}Detailed authorization logs should go to the security telemetry system.
Capability Revocation
Capabilities should be revocable.
For example:
User account disabled
↓
Capabilities revoked
↓
Tool calls rejectedSimilarly:
Agent compromised
↓
Revoke agent capabilities
↓
No further privileged callsThis is especially important for long-running agents.
Token Revocation vs Capability Revocation
These are related but different.
Token
=
Authentication credentialwhile:
Capability
=
Authorization authorityRevoking a token may prevent authentication.
Revoking a capability can remove one specific authority while leaving other operations intact.
For example:
Agent
├── customer.read
├── order.read
└── order.refundIf refund behavior is compromised:
Revoke:
order.refund
Keep:
customer.read
order.readThis is a more targeted response.
Prevent Scope Creep
A common failure mode is gradual permission expansion.
It starts as:
read_customerThen someone adds:
update_customerThen:
delete_customerThen:
database.adminThe agent has accumulated privileges that are no longer related to its original purpose.
OWASP identifies privilege escalation through scope creep as a specific MCP security risk.
Add Capability Review
Treat capability changes like code changes.
For example:
Pull Request
↓
Capability diff
↓
Security review
↓
Approval
↓
DeploymentA useful review output could be:
Added:
orders.update
Removed:
orders.read
Changed:
orders.refund
maxAmount: 100 → 1000This makes authorization changes auditable.
Version Tool Definitions
Tool definitions themselves are security-sensitive.
OWASP recommends inspecting and pinning tool descriptions and schemas because changes can introduce unexpected behavior or create tool-poisoning risks.
For a controlled tool ecosystem:
Tool Definition
↓
Version
↓
Hash
↓
ApprovedThen detect changes:
Approved hash
≠
Current hash
↓
Review requiredCapability Versioning
Capabilities can also be versioned.
For example:
orders.refund:v1might allow:
amount <= 100while:
orders.refund:v2allows:
amount <= 500
+
human approvalThis creates a clear authorization migration path.
Audit Every Tool Call
A security audit record should answer:
Who?
Which agent?
Which tenant?
Which tool?
Which action?
Which resource?
Which capability?
Allowed or denied?
Why?
When?
What was the outcome?For example:
{
"timestamp": "2026-08-11T10:15:00Z",
"userId": "user-123",
"agentId": "support-agent",
"tenantId": "tenant-a",
"tool": "orders",
"action": "read",
"resource": "order:12345",
"decision": "allow"
}Avoid logging secrets or unnecessary sensitive payloads.
OWASP recommends centralized logging and auditing of authentication and authorization decisions.
Detect Suspicious Capability Usage
Authorization logs can also become security signals.
For example:
Normal:
orders.readSuddenly:
orders.refundor:
database.exportThe system can trigger:
Alert
+
Human review
+
Capability revocationThis is more effective than logging without analyzing the events.
Example Capability Policy
A simplified policy could be represented as:
{
"agent": "support-agent",
"capabilities": [
{
"tool": "customer",
"actions": ["read"],
"resource": "tenant/customer/*"
},
{
"tool": "orders",
"actions": ["read"],
"resource": "tenant/order/*"
}
]
}A privileged action remains outside the normal capability set:
orders.refundand therefore requires a separate workflow.
Capability Evaluation Flow
A complete request might look like:
User
↓
Agent
↓
Tool request
↓
Authenticate caller
↓
Resolve tenant
↓
Resolve capability
↓
Check expiration
↓
Check tool
↓
Check action
↓
Check resource
↓
Check policy conditions
↓
Human approval if required
↓
Execute tool
↓
Validate output
↓
Audit
↓
Return resultThis is the security boundary.
Example Secure Tool Flow in C#
public async Task<ToolResult> HandleAsync(
ToolRequest request,
RequestContext context,
CancellationToken cancellationToken)
{
var capabilityRequest =
new CapabilityRequest(
context.AgentId,
context.UserId,
context.TenantId,
request.Tool,
request.Action,
request.Resource);
var decision =
await authorizer.AuthorizeAsync(
capabilityRequest,
cancellationToken);
if (!decision.Allowed)
{
await audit.LogDeniedAsync(
capabilityRequest,
decision.Reason,
cancellationToken);
return ToolResult.Denied(
"Operation is not permitted.");
}
if (decision.RequiresApproval)
{
return ToolResult.PendingApproval(
request.Tool,
request.Action,
request.Resource);
}
var result =
await executor.ExecuteAsync(
request,
context,
cancellationToken);
var safeResult =
outputSanitizer.Sanitize(result);
await audit.LogAllowedAsync(
capabilityRequest,
cancellationToken);
return safeResult;
}The example demonstrates the sequence:
Authorize
↓
Approve if necessary
↓
Execute
↓
Sanitize
↓
AuditCommon Mistakes
Trusting the LLM
The model is not an authorization engine.
Trusting Client-Provided Roles
The server should derive identity and authorization context from trusted authentication infrastructure.
Giving Agents Full Database Access
Prefer domain-specific tools.
Using One Token for Every Tool
Use scoped identities and credentials.
Giving Every Agent the Same Tools
Tool access should reflect task requirements.
Ignoring Resource-Level Authorization
Being allowed to call orders.read does not necessarily mean the agent can read every order.
Allowing Arbitrary URLs
URL-fetching tools can create SSRF risks if the destination is not restricted. OWASP explicitly recommends strict allowlists for such tools.
Allowing Arbitrary Shell Commands
Avoid generic:
execute_command(command)for normal agent workflows.
Skipping Human Approval
High-impact actions need stronger controls.
Logging Secrets
Audit tool activity without storing sensitive credentials or unnecessary payloads.
Never Reviewing Capability Changes
Authorization policies can drift just like application code.
Troubleshooting
Agent Says It Has Permission but Tool Rejects It
This is often expected.
The model's context may contain:
"I can perform this action."but the server evaluates the actual authorization.
Check:
Identity
Tenant
Tool
Action
Resource
Capability
Expiration
PolicyTool Works for One User but Not Another
Compare the capability sets.
For example:
User A:
orders.read
User B:
orders.read
orders.updateThe difference should be visible in authorization telemetry.
Agent Can Read Data From Another Tenant
Treat this as a critical authorization defect.
Check whether tenant identity comes from:
Authenticated contextor:
LLM-supplied parameterThe latter should not be trusted.
A Tool Suddenly Has More Permissions
Compare the capability policy version and tool definition hash.
Possible causes include:
Policy change
Configuration drift
Tool update
New scope
Identity mapping changeSensitive Tool Executes Without Approval
Verify that approval is enforced server-side.
Do not rely solely on the agent's instructions to request confirmation.
Best Practices
Start with deny-by-default authorization.
Grant the minimum required capabilities.
Separate read, write, and destructive operations.
Enforce authorization server-side.
Never treat system prompts as access controls.
Bind capabilities to authenticated identity.
Bind permissions to tenant context.
Perform resource-level authorization.
Use short-lived capabilities where practical.
Support revocation.
Separate privileged workflows.
Require human approval for high-impact operations.
Avoid generic shell and unrestricted database tools.
Validate tool parameters.
Validate and minimize tool output.
Protect against SSRF and path traversal.
Audit every authorization decision.
Monitor capability changes and unusual tool usage.
Review capability changes like code changes.
Treat tool definitions and schemas as security-sensitive assets.
Capability Security Checklist
Before exposing an AI tool, ask:
| Question | Expected Answer |
|---|---|
| Who can call it? | Explicit identity |
| What action is allowed? | Explicit capability |
| Which resources? | Explicit scope |
| Which tenant? | Authenticated context |
| How long? | Limited lifetime |
| Can it be revoked? | Yes |
| Does it modify data? | Clearly classified |
| Does it require approval? | Defined by risk |
| Are parameters validated? | Yes |
| Is output sanitized? | Yes |
| Is the call audited? | Yes |
| Can the tool execute arbitrary code? | Preferably no |
| Can permissions drift? | Detect and review |
| Can tool definitions change silently? | Detect and review |
Frequently Asked Questions
What is capability-based security?
Capability-based security represents authority as an explicit permission to perform an action against a particular resource.
For AI agents, this can mean:
Tool
+
Action
+
Resource
+
Constraintsrather than simply assigning a broad role.
Is capability-based security the same as RBAC?
No.
RBAC assigns permissions through roles.
Capability-based security focuses on explicit authority.
The two can be combined. A role can determine which capabilities an agent receives.
Should the LLM decide whether it has permission?
No.
The LLM can select a tool, but the final authorization decision should be made by trusted server-side policy enforcement. OWASP specifically recommends server-side enforcement rather than relying on prompts or client-side controls.
Should every tool require human approval?
No.
Human approval should be proportional to risk.
A read-only search may not require approval.
A financial transfer or destructive operation may.
Why are short-lived capabilities useful?
They reduce the lifetime of authority.
If a capability is leaked or misused, its usefulness decreases after expiration.
Should each MCP server have a separate credential?
Where appropriate, yes.
OWASP recommends scoped, per-server credentials rather than sharing broad tokens across servers.
Can capability security prevent prompt injection?
Not by itself.
Prompt injection can influence model behavior.
Capability enforcement limits what the resulting tool call can actually do.
That is why server-side authorization remains important.
Can I use capability-based security without MCP?
Yes.
The model applies to any AI system that invokes tools:
AI Agent
+
Plugins
+
Function Calling
+
MCP
+
Internal APIsMCP is one implementation context, not a prerequisite.
Should tools expose generic database or shell access?
Generally, avoid this for ordinary agent workflows.
Narrow domain-specific tools produce smaller and more controllable capability surfaces.
How should I secure multi-tenant agents?
Bind authorization to the authenticated tenant context and perform resource-level authorization on every request.
Never trust a tenant identifier supplied solely by the model.
Conclusion
AI agents should not receive authority simply because a tool exists.
A secure design starts with:
Agent
↓
Capabilities
↓
Policy
↓
Authorized Tool
↓
Authorized Resourcerather than:
Agent
↓
All ToolsThe capability model gives developers a practical way to implement least privilege:
customer.readis better than:
customer.full_accessand:
order.refund
amount <= 100
human approval requiredis better than:
order.adminThe most important security boundary is the tool execution layer.
The model can request:
delete_customerbut that request should pass through:
Authentication
↓
Capability check
↓
Resource authorization
↓
Policy evaluation
↓
Human approval
↓
Tool executionbefore anything destructive happens.
Current OWASP guidance highlights excessive permissions, scope creep, insufficient authorization, tool poisoning, and cross-server interactions as important risks in MCP and agentic systems.
The practical objective is therefore not to make an AI agent incapable of doing dangerous things.
It is to make dangerous capabilities:
Explicit
+
Scoped
+
Time-limited
+
Auditable
+
Revocable
+
Policy-controlledThat is the foundation of capability-based security for AI tool plugins.
Give the agent enough authority to complete the task, but never more authority than the task requires.

Join the conversation! Your thoughts help the community grow.