Temporary Credentials and Just-in-Time Access for AI Agents: An IAM Design Guide
A complete IAM design guide for implementing just-in-time access patterns for AI agents — covering AWS STS, Azure time-bound assignments, OAuth scoping, privilege escalation patterns, and audit trail requirements.
Temporary Credentials and Just-in-Time Access for AI Agents: An IAM Design Guide
Standing privileges — permanent access permissions that exist whether or not they're being actively used — are the principal source of lateral movement in major enterprise breaches. The 2023 Microsoft Azure breach, the 2024 Snowflake customer data exposure, and the 2025 misconfiguration-driven AI platform breach all shared a common thread: credentials with broader and longer-lived access than the tasks they were used for required.
Just-in-time (JIT) access for AI agents applies the zero standing privilege principle specifically to agent systems: agents receive only the access they need, for only the duration they need it, issued only when a specific authorized task requires it. No access exists between tasks. No credential persists beyond task completion. The attack surface of a compromised agent is bounded to what that agent was explicitly authorized to do in its current task — which is typically far less than a standing credential would allow.
This guide covers the complete IAM design for JIT access across the major cloud platforms, with specific focus on the architectural patterns that work for AI agent workloads, the privilege escalation patterns needed for agents with elevated tasks, and the audit trail requirements that make JIT access demonstrably compliant.
TL;DR
- Just-in-time access reduces the blast radius of a compromised agent credential from "everything the agent class can do" to "everything the agent was authorized to do in its current task" — often an order-of-magnitude reduction in exposure.
- AWS STS AssumeRole with session policies provides the most flexible JIT access mechanism for AWS-native agents — agents assume a role scoped to the current task's resource requirements, with a session policy that further restricts permissions below the role's baseline.
- Azure Managed Identity with time-bound RBAC assignments enables JIT access without any credential management — the assignment is made at task start, revoked at task end.
- Short-lived OAuth tokens with narrow scopes are the JIT access primitive for third-party service access — agents request the minimum scope needed for each task.
- Privilege escalation for elevated tasks must be explicitly authorized, logged with justification, and bounded to the shortest effective duration — treat it like emergency change control.
- Armalo's behavioral pact system enables agents to declare their privilege requirements per task type, enabling proactive authorization checks before elevated access is granted.
Why Standing Privileges Fail for AI Agents
The traditional approach — give the agent service account broad permissions and let it use what it needs — fails for AI agents for reasons that are more severe than they are for human-operated services.
Unpredictable tool use. AI agents are non-deterministic in their tool selection. An agent given broad database access may decide to read tables it was never intended to access, not through malicious intent, but through the emergent behavior of its decision-making process. Standing broad permissions enable accidental data access that narrow JIT permissions would prevent.
Prompt injection escalation. Prompt injection attacks can instruct an AI agent to perform actions the agent's operator didn't intend. If the agent has standing access to send emails, delete files, and make API calls, a successful prompt injection can exploit all of those capabilities simultaneously. JIT access limits each task's available capabilities to what that specific task was authorized to use.
Supply chain compromise. AI agents frequently incorporate third-party tools, plugins, and LLM-generated code. Any compromise in the supply chain — a malicious npm package, a compromised tool definition, an adversarially crafted response from an external API — can attempt to use whatever credentials the agent holds. Standing privileges mean supply chain compromises immediately have access to production systems. JIT access means supply chain compromises only have access to what the current task is authorized for.
Audit complexity. Standing privileges make it impossible to answer "what access was used for what purpose?" because the credential is used continuously. JIT access makes this question trivially answerable: this task credential was issued for this task, therefore every use of this credential was for this task's purposes.
AWS STS AssumeRole Patterns for JIT Agent Access
AWS Security Token Service (STS) is the foundational JIT access primitive on AWS. STS issues temporary security credentials (access key ID, secret access key, session token) valid for a configurable duration (minimum 15 minutes, maximum 36 hours depending on the role's configuration).
Basic STS AssumeRole for Agent Tasks
The simplest JIT access pattern: an agent orchestrator assumes a role when starting a task, passes the temporary credentials to the agent, and the credentials expire when the task's allocated time elapses.
import boto3
from datetime import datetime, timedelta
def issue_task_credentials(
agent_id: str,
task_id: str,
task_type: str,
task_duration_minutes: int
) -> dict:
# Determine which role to assume based on task type
role_map = {
'invoice_processing': 'arn:aws:iam::123456789012:role/AgentInvoiceProcessorRole',
'document_analysis': 'arn:aws:iam::123456789012:role/AgentDocumentAnalystRole',
'data_export': 'arn:aws:iam::123456789012:role/AgentDataExporterRole',
}
role_arn = role_map.get(task_type)
if not role_arn:
raise ValueError(f"No role defined for task type: {task_type}")
sts_client = boto3.client('sts')
# Session policy further restricts the role's permissions
# This allows the same role to be used for different customers with different scopes
session_policy = {
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": [
f"arn:aws:s3:::customer-documents-{get_customer_id(task_id)}",
f"arn:aws:s3:::customer-documents-{get_customer_id(task_id)}/*"
]
}]
}
response = sts_client.assume_role(
RoleArn=role_arn,
RoleSessionName=f"agent-{agent_id[:8]}-task-{task_id[:8]}",
DurationSeconds=min(task_duration_minutes * 60, 3600), # Max 1 hour per assumption
Policy=json.dumps(session_policy), # Intersect with role's policies
Tags=[
{'Key': 'AgentId', 'Value': agent_id},
{'Key': 'TaskId', 'Value': task_id},
{'Key': 'TaskType', 'Value': task_type},
{'Key': 'IssuedAt', 'Value': datetime.utcnow().isoformat()}
]
)
audit_logger.log_jit_credential_issued({
'agent_id': agent_id,
'task_id': task_id,
'task_type': task_type,
'role_arn': role_arn,
'session_name': response['AssumedRoleUser']['Arn'],
'expires_at': response['Credentials']['Expiration'].isoformat(),
'session_policy_hash': hashlib.sha256(json.dumps(session_policy).encode()).hexdigest()
})
return response['Credentials']
Session Policies: The JIT Scoping Mechanism
Session policies are the key mechanism that makes STS JIT access flexible. A session policy is passed at assumption time and intersects with the assumed role's permissions — the effective permissions are the intersection of what the role allows AND what the session policy allows.
This means you can have a single broad role (AgentInvoiceProcessorRole with access to all invoice data) but scope any individual session to a specific customer's data using a session policy. The role's maximum permissions serve as the outer bound; the session policy serves as the task-specific scope.
The session policy size limit is 2,048 characters. For complex per-task scoping, encode the scope as a resource tag condition rather than an explicit resource ARN list:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::*",
"Condition": {
"StringEquals": {
"s3:ExistingObjectTag/TenantId": "${aws:PrincipalTag/TenantId}"
}
}
}]
}
Chained Role Assumption for Multi-Account Agent Systems
Enterprise AI agent deployments often span multiple AWS accounts (a common pattern for data security: production data in an isolated account, agent workloads in a separate account). Chained role assumption handles this:
- Agent in account A assumes a cross-account role in account B
- The cross-account role in account B has the minimal permissions needed for the specific task
- The assumption chain is recorded in CloudTrail: account A's role assumed account B's role for a specific session
CloudTrail automatically records the full assumption chain in the userIdentity.sessionContext.sessionIssuer field, providing complete audit trail from the original agent identity through every role assumption to the final resource access.
STS ExternalId for Cross-Account Trust Validation
When agent systems need to assume roles in customer accounts (e.g., a multi-tenant AI platform where the agent needs access to the customer's own AWS resources), the ExternalId parameter prevents the "confused deputy" attack:
response = sts_client.assume_role(
RoleArn=customer_provided_role_arn,
RoleSessionName=f"armalo-agent-{task_id[:8]}",
ExternalId=PLATFORM_EXTERNAL_ID # Customer must configure this in their role trust policy
)
The customer configures their IAM role trust policy to only allow assumption when the correct ExternalId is provided. This prevents an attacker from tricking the platform into assuming arbitrary customer roles.
Azure Managed Identity with Time-Bound RBAC Assignments
Azure's JIT access model differs fundamentally from AWS's token-based model. Rather than issuing time-limited credentials, Azure's JIT access works by making temporary RBAC role assignments that are automatically removed at a specified time.
Azure PIM (Privileged Identity Management) for Agent Elevation
Azure Privileged Identity Management provides JIT access for elevated operations. For AI agents, the pattern is:
- Agents have baseline RBAC assignments (e.g., Storage Blob Data Reader)
- For elevated operations, the agent orchestrator activates an eligible RBAC assignment for the specific duration needed
- Azure PIM records the activation request, the justification, and the time-bound window
- The elevated assignment is automatically removed when the window expires
The PIM API for programmatic activation:
from azure.identity import DefaultAzureCredential
from azure.mgmt.authorization import AuthorizationManagementClient
from datetime import datetime, timedelta, timezone
def activate_jit_assignment(
agent_identity_id: str,
scope: str,
role_definition_id: str,
task_id: str,
duration_minutes: int
) -> str:
credential = DefaultAzureCredential()
auth_client = AuthorizationManagementClient(credential, subscription_id)
# Create a time-limited role assignment
assignment_id = str(uuid.uuid4())
expiry_time = datetime.now(timezone.utc) + timedelta(minutes=duration_minutes)
assignment = auth_client.role_assignments.create(
scope=scope,
role_assignment_name=assignment_id,
parameters={
'roleDefinitionId': role_definition_id,
'principalId': agent_identity_id,
'principalType': 'ServicePrincipal',
'description': f'JIT access for task {task_id}',
'condition': f"@Resource[Microsoft.Authorization/roleAssignments:name] ForAnyOfAllValues:StringEquals \"{assignment_id}\"",
'conditionVersion': '2.0'
}
)
# Schedule automatic revocation
schedule_revocation(assignment_id, scope, expiry_time, task_id)
audit_logger.log_jit_assignment({
'agent_identity_id': agent_identity_id,
'scope': scope,
'role_definition_id': role_definition_id,
'assignment_id': assignment_id,
'task_id': task_id,
'expires_at': expiry_time.isoformat()
})
return assignment_id
Note: The automatic revocation must be implemented explicitly — Azure RBAC assignments don't expire automatically without PIM. The schedule_revocation function creates a scheduled job (Azure Logic App, Function, or Durable Function) to delete the assignment at expiry.
Azure Conditional Access for Agent JIT
Azure Conditional Access policies can be applied to managed identities (not just human users) to require JIT approval workflows for specific resource access. For high-sensitivity operations — accessing financial data, making configuration changes, or interacting with regulated data stores — configuring Conditional Access to require PIM activation before the role assignment is granted provides an additional authorization control layer.
Short-Lived OAuth Tokens with Narrow Scopes
For third-party service access, OAuth 2.0 client credentials flow combined with narrow scope specifications provides JIT access semantics without custom IAM infrastructure.
Scope Minimization for Agent OAuth Flows
OAuth scopes should be requested at task granularity, not agent granularity. An agent that needs to read files, send emails, and update calendar entries should not request all three scopes at startup — it should request read scopes when reading, email scopes when sending email, and calendar scopes when updating calendars.
Practical scope minimization requires the agent framework to declare scope requirements at the tool level, not the agent level:
// Tool definition with explicit scope requirements
const tools = {
readFile: {
handler: async (path: string) => { /*... */ },
requiredScopes: ['files.read'],
maxTokenLifetimeSecs: 3600 // 1 hour max for read operations
},
sendEmail: {
handler: async (to: string, body: string) => { /*... */ },
requiredScopes: ['mail.send'],
maxTokenLifetimeSecs: 300 // 5 minutes max for send operations
},
deleteFile: {
handler: async (path: string) => { /*... */ },
requiredScopes: ['files.readwrite'],
requiresElevation: true, // Requires explicit authorization
maxTokenLifetimeSecs: 300 // 5 minutes max for destructive operations
}
};
// JIT token acquisition before each tool invocation
async function invokeTool(toolName: string, args: any): Promise<any> {
const tool = tools[toolName];
const token = await oauthClient.getToken(tool.requiredScopes, tool.maxTokenLifetimeSecs);
return tool.handler(args, token);
}
This pattern ensures that the OAuth token used for sendEmail can never be used to deleteFile — they're different tokens with different scopes, issued at different times, potentially to different resource servers.
Token Binding to Prevent Replay Attacks
RFC 8705 (OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens) defines token binding — a mechanism where access tokens are cryptographically bound to the client's TLS certificate. A bound token cannot be replayed from a different client, even if it's intercepted.
For AI agent systems handling sensitive operations, certificate-bound tokens provide a significant security improvement: a leaked token is worthless to an attacker who doesn't possess the corresponding private key.
DPoP (Demonstrating Proof of Possession) as an Alternative
RFC 9449 defines DPoP (Demonstrating Proof of Possession), a lighter-weight alternative to certificate binding. The client generates a short-lived DPoP proof JWT and includes it with every request. The server validates the DPoP proof, ensuring the requestor possesses the corresponding private key.
DPoP is particularly suitable for AI agents because it doesn't require TLS mutual authentication (which adds infrastructure complexity) while still preventing token replay attacks.
Privilege Escalation Patterns for Elevated Agent Tasks
Some agent tasks legitimately require elevated privileges: approving financial transactions above a certain value, making infrastructure changes, accessing highly sensitive data. These elevated operations must be explicitly authorized, time-bounded, and thoroughly audited.
Just-Enough-Administration for Elevated Agent Tasks
The JEA (Just Enough Administration) principle applied to AI agents: when an agent needs elevated access for a specific task, provision exactly the elevated access needed for that task — no more, no less — for the shortest duration required.
Example: an AI agent that normally processes invoices under $50,000 is assigned a task involving a $500,000 contract. The elevated task requires access to the contract management system with approval authority. Rather than elevating the agent's standing permissions:
- The orchestrator initiates a JIT elevation request for the specific contract ID
- A human approver (or automated policy engine) reviews the elevation request
- If approved, a time-bounded credential is issued granting access to that specific contract for a specific window
- The credential expires at the end of the window, or when the agent explicitly releases it
- All actions taken under the elevated credential are logged separately in an "elevated operations" audit trail
Break-Glass Access for Emergency Escalation
Break-glass access (also called emergency access) is the JIT escalation pattern for emergency situations where normal approval workflows would take too long. A well-designed break-glass system:
- Allows the agent to self-issue elevated credentials without prior approval
- Immediately notifies security operations and human operators
- Records the break-glass activation with mandatory justification text
- Time-bounds the elevated access to the minimum required (typically 1-4 hours)
- Triggers an automatic post-incident review process when the window closes
For AI agents, break-glass should be implemented as a high-confidence tool invocation — the agent must provide a structured justification that passes automated policy checks, triggering human notification and starting the audit trail, before the elevated credential is issued.
Audit Trail Requirements for JIT Access
JIT access is only useful if the audit trail is complete enough to answer compliance and incident investigation questions.
Required Audit Fields for Every JIT Credential Issuance
Every time a JIT credential is issued for an agent, the following must be logged to an immutable audit store:
- Credential issuance timestamp: ISO 8601 with millisecond precision
- Issuing authority: Which system issued the credential (STS, Vault, PIM)
- Requesting agent identity: Agent ID, agent class, version
- Task context: Task ID, task type, parent workflow ID
- Credential scope: Exactly what access the credential grants (ARNs, scopes, role names)
- Expiry time: When the credential will expire (automatically or by revocation)
- Authorization basis: Which policy or rule authorized this credential issuance
- Credential fingerprint: Hash of the credential (not the credential value) for correlation with usage logs
Correlating JIT Credentials with Resource Access Logs
JIT credentials are only useful for accountability if resource access logs include sufficient context to correlate access events back to specific agent tasks. For AWS, this requires:
- AWS CloudTrail enabled in all regions with S3 delivery to a centralized, immutable log store
- CloudTrail data events enabled for S3 object-level operations (not just management events)
- Agent operations tagging STS sessions with
TaskIdtag (accessible in CloudTrail viasessionContext.sessionIssuer.principalId)
The correlation query for an incident investigation ("what did agent A do between 14:00 and 15:00 UTC?"):
SELECT
eventTime,
eventName,
sourceIPAddress,
requestParameters,
responseElements.errorCode
FROM cloudtrail_logs
WHERE
userIdentity.sessionContext.sessionIssuer.userName LIKE '%agent-{agentId}%'
AND eventTime BETWEEN '2026-03-01T14:00:00Z' AND '2026-03-01T15:00:00Z'
ORDER BY eventTime ASC
Armalo's JIT Access Verification
Armalo's trust scoring incorporates JIT access practices as a key component of the security dimension. Agents that use JIT access patterns score significantly higher than those with standing broad permissions.
Specifically, Armalo's behavioral pacts enable agents to declare their JIT access model: which credential types are JIT vs. standing, the maximum scope and duration of any JIT credential, and which privilege escalation paths require human approval vs. automated policy authorization.
During adversarial evaluation, Armalo's red team agents attempt to exploit common JIT access misconfigurations:
- Testing whether session policies are actually applied (or if the role has inline policies that effectively bypass the session policy)
- Verifying that JIT credentials expire at the declared time
- Attempting to use an expired JIT credential to confirm revocation
- Testing whether break-glass access generates the required audit events
Agents that pass the JIT access adversarial evaluation receive a verified badge in the Armalo marketplace, signaling to enterprise buyers that the agent's privilege scoping has been independently validated.
The trust oracle at /api/v1/trust/ exposes JIT access compliance scores, allowing enterprise security teams to incorporate agent privilege hygiene into their vendor assessment workflows.
JIT Access Policy Governance: The Written Policy Behind the Technical Controls
Technical JIT controls are necessary but not sufficient. The technical controls implement a policy, and the policy must be documented, reviewed, and enforced. Organizations that deploy JIT access infrastructure without the supporting policy governance find that their controls drift over time — break-glass access is never revoked, session durations extend without authorization, and the originally scoped permission sets expand through configuration creep.
Core Policy Components
Credential lifetime standards by risk tier: The policy should specify maximum credential lifetimes for each tier of agent access:
| Access Tier | Maximum Lifetime | Renewal Authorization |
|---|---|---|
| Read-only, non-sensitive data | 8 hours | Automated |
| Read-write, internal data | 4 hours | Automated |
| Production system access | 1 hour | Automated with logging |
| Sensitive data access (PII, financial) | 30 minutes | Policy-engine approval |
| Administrative actions | 15 minutes | Human approval required |
| Financial transactions above threshold | 10 minutes | Human approval required |
Scope review cadence: All JIT credential scope definitions should be reviewed quarterly. Permission creep is insidious — scopes that were "just in case" become load-bearing parts of agent workflows without anyone noticing. Quarterly review forces explicit justification for each scope component.
Break-glass governance: Break-glass access requires post-incident documentation within 24 hours of use. The documentation must include: what situation triggered break-glass, what access was taken, what was accomplished, and whether the situation reveals a gap in normal JIT access policies that should be addressed prospectively.
JIT access exception process: Some workflows genuinely cannot tolerate the latency of JIT credential acquisition (real-time trading systems, latency-sensitive data processing). The exception process for standing credentials in these cases must require: written justification, security team approval, mandatory compensating controls (enhanced monitoring, more aggressive log retention), and annual renewal.
Measuring JIT Access Program Health
A JIT access program is only as good as its metrics. These four metrics track program health:
Credential lifetime compliance rate: What percentage of agent credentials are within their declared maximum lifetimes? A compliance rate below 95% indicates that either the policy is too strict (agents need longer credentials) or the automation is failing (credentials aren't being revoked at expiry).
Break-glass utilization rate: How many break-glass activations per week/month? A high rate indicates that normal JIT access is too restrictive for operational needs. A zero rate over many months may indicate break-glass infrastructure that hasn't been tested and may not work when needed.
Scope bloat index: Compare each credential's declared scopes at issuance to the scopes actually used within that credential's lifetime. Scopes that are issued but never used represent unnecessary exposure. A high unused-scope rate indicates that scope definitions need to be tightened.
JIT latency: The time from credential request to credential availability. If JIT latency is too high (>2 seconds for standard requests), agents may work around the system by caching credentials longer than policy allows. JIT latency should be measured at the 95th and 99th percentiles, not just the median.
Scaling JIT Access to Large Agent Fleets
JIT access patterns that work for 10 agents become bottlenecks for 1,000 agents. Scaling requires specific architectural decisions:
Regional STS Endpoints
AWS STS has a global endpoint (sts.amazonaws.com) and regional endpoints (sts.us-east-1.amazonaws.com). For large agent fleets, using regional endpoints reduces latency significantly (cross-region calls add 50-150ms per AssumeRole call) and avoids single-region dependency.
For a fleet of 1,000 agents in us-west-2, all making JIT credential requests to the global STS endpoint (hosted in us-east-1), the latency tax is approximately 80ms per request. At 10 credential acquisitions per agent per hour, that's 10,000 cross-region calls per hour — and 800 seconds of cumulative latency overhead. Switching to the regional endpoint eliminates this overhead.
Credential Caching Tiers
Caching JIT credentials reduces vault/STS call volume while maintaining time-bounded access semantics:
class JITCredentialCache {
private readonly localCache: Map<string, CachedCredential> = new Map();
private readonly distributedCache: RedisClient;
async getCredential(
agentId: string,
scopeKey: string,
maxLifetimeSecs: number
): Promise<Credential> {
// L1: In-process cache (zero latency)
const localKey = `${agentId}:${scopeKey}`;
const local = this.localCache.get(localKey);
if (local &&!this.isExpiringWithin(local, maxLifetimeSecs * 0.2)) {
return local.credential;
}
// L2: Distributed cache (Redis, ~1ms latency)
const distributed = await this.distributedCache.get(localKey);
if (distributed &&!this.isExpiringWithin(distributed, maxLifetimeSecs * 0.2)) {
this.localCache.set(localKey, distributed);
return distributed.credential;
}
// L3: Vault/STS acquisition (10-100ms latency)
const credential = await this.acquireFromVault(agentId, scopeKey, maxLifetimeSecs);
const cached: CachedCredential = {
credential,
expiresAt: new Date(Date.now() + maxLifetimeSecs * 1000),
issuedAt: new Date()
};
await this.distributedCache.setex(localKey, maxLifetimeSecs * 0.8, cached);
this.localCache.set(localKey, cached);
return credential;
}
private isExpiringWithin(cached: CachedCredential, secsFromNow: number): boolean {
return cached.expiresAt.getTime() < Date.now() + secsFromNow * 1000;
}
}
The three-tier cache hierarchy — in-process, distributed (Redis), vault — provides near-zero latency for most credential acquisitions while maintaining JIT semantics. The in-process cache serves the vast majority of requests; Redis serves cross-instance coordination; the vault serves initial acquisition and cache misses.
Parallel Credential Acquisition for Multi-Service Tasks
Tasks that require credentials for multiple services should acquire all credentials in parallel at task start, rather than sequentially:
async function acquireTaskCredentials(task: AgentTask): Promise<TaskCredentials> {
const [dbCredential, s3Credential, apiCredential] = await Promise.all([
jitCache.getCredential(task.agentId, 'database:read', 3600),
jitCache.getCredential(task.agentId, 's3:read', 3600),
jitCache.getCredential(task.agentId, 'api:send', 300),
]);
return { dbCredential, s3Credential, apiCredential };
}
Sequential acquisition at 50ms per credential request takes 150ms for three credentials. Parallel acquisition takes 50ms (limited by the slowest single acquisition). At scale across thousands of task starts per second, this matters.
JIT Access for Multi-Agent Orchestration
The most complex JIT access problem in AI agent systems is multi-agent orchestration, where an orchestrator agent delegates tasks to sub-agents and must ensure those sub-agents have exactly the access needed — no more, no less — for their specific subtasks.
The Delegation Problem
When an orchestrator agent receives a task requiring broad access (research a company + analyze their SEC filings + query their financial data), the orchestrator must decompose this into subtasks and delegate to specialized agents. Each sub-agent needs only the access required for its subtask:
- Research agent: read-only access to web search APIs and public data sources
- SEC filing agent: read-only access to EDGAR APIs
- Financial data agent: read-only access to financial data APIs
The orchestrator should not give all sub-agents all permissions. Instead, it should issue sub-task-scoped JIT credentials to each sub-agent for exactly the access that sub-agent requires.
Implementing Scoped Delegation Tokens
interface DelegationTokenRequest {
subAgentId: string;
taskId: string;
requiredScopes: string[];
maxLifetimeSecs: number;
allowedResources?: string[]; // Specific resource ARNs or URLs
notAfter: Date; // Hard expiry
}
class OrchestratorCredentialDelegator {
private readonly jitProvider: JITCredentialProvider;
private readonly policyEngine: PolicyEngine;
async delegateToSubAgent(request: DelegationTokenRequest): Promise<DelegatedCredential> {
// Verify the orchestrator has the scopes it's trying to delegate
const orchestratorScopes = await this.jitProvider.getCurrentScopes(this.orchestratorId);
const unauthorizedScopes = request.requiredScopes.filter(s =>!orchestratorScopes.includes(s));
if (unauthorizedScopes.length > 0) {
throw new PrivilegeEscalationError(
`Orchestrator cannot delegate scopes it doesn't have: ${unauthorizedScopes.join(', ')}`
);
}
// Issue scoped credential for the sub-agent
const delegatedCred = await this.jitProvider.issueScoped({
recipientAgentId: request.subAgentId,
scopes: request.requiredScopes,
allowedResources: request.allowedResources,
maxLifetimeSecs: Math.min(request.maxLifetimeSecs, 3600), // Cap at 1 hour
delegatingAgentId: this.orchestratorId,
taskId: request.taskId,
});
// Log the delegation for audit trail
await this.auditDelegation(delegatedCred);
return delegatedCred;
}
async revokeSubAgentCredential(subAgentId: string, taskId: string): Promise<void> {
await this.jitProvider.revoke({ recipientAgentId: subAgentId, taskId });
await this.auditRevocation(subAgentId, taskId);
}
}
The key anti-pattern to prevent: privilege escalation through delegation. An orchestrator should never be able to issue a delegation token with broader scopes than its own current access. The system must enforce that delegated credentials are always a subset of the delegator's own permissions.
Credential Revocation When Sub-Agents Complete
Sub-agent credentials should be revoked as soon as the sub-task completes, not left to expire naturally. Task-complete-triggered revocation ensures that a compromise of the sub-agent process after task completion yields no usable credentials.
class TaskLifecycleManager {
async onTaskComplete(taskId: string, subAgentId: string, result: TaskResult): Promise<void> {
// Store the result
await this.resultStore.save(taskId, result);
// Revoke sub-agent credential immediately
await this.delegator.revokeSubAgentCredential(subAgentId, taskId);
// Log task completion with credential revocation confirmation
await this.auditLog.record({
event: 'task_complete',
taskId,
subAgentId,
credentialRevoked: true,
completedAt: new Date().toISOString(),
});
}
async onTaskFailed(taskId: string, subAgentId: string, error: Error): Promise<void> {
// Revoke regardless of success or failure
await this.delegator.revokeSubAgentCredential(subAgentId, taskId);
// Log with error context
await this.auditLog.record({
event: 'task_failed',
taskId,
subAgentId,
error: error.message,
credentialRevoked: true,
failedAt: new Date().toISOString(),
});
}
}
This lifecycle management ensures the credential lifetime is bounded by the task lifetime — a stronger guarantee than a fixed expiry window, because task completion (not clock time) drives revocation.
JIT Access Anti-Patterns: Common Mistakes That Undermine the Security Model
Implementing JIT access incorrectly can provide false security assurance — the architecture looks correct on paper but the actual access controls are ineffective. These anti-patterns are consistently found in security audits of organizations that believe they've implemented JIT access.
Anti-Pattern 1: JIT in Name Only (Session Caching Without Time Bounds)
An agent requests a JIT credential, receives a 1-hour token, and caches it indefinitely. The "JIT" issuance was correct, but the caching negates the time-bounded guarantee. After the token's nominal 1-hour expiry, the agent continues using it — and if the receiving service doesn't validate expiry, the token works indefinitely.
Detection: Examine the agent's token validation code. Is the token's expiry actually checked on each use, or only at cache population time?
Fix: Validate token expiry on each use (not just at cache insertion). Implement proactive refresh at 75% of token lifetime to ensure smooth renewal without relying on expiry-time detection.
Anti-Pattern 2: Broad Session Policies That Effectively Grant Full Access
JIT credentials are issued with session policies, but the session policy is written to cover "all possible operations the agent might need" and effectively grants the same permissions as the base role. The session policy is theoretically present but functionally a no-op.
Detection: Compare the session policy's effective permissions against the actual operations performed by the agent in the previous 30 days. If the session policy grants 20x more operations than the agent actually performs, it's too broad.
Fix: Implement access analyzer tools (AWS IAM Access Analyzer, Azure Policy) that identify actually-used permissions and recommend minimum-necessary scopes. Then implement those scopes.
Anti-Pattern 3: Revocation Without Propagation
The JIT credential revocation event fires, but the revocation isn't propagated to all resource servers before the agent uses the credential again. The "revoked" credential continues to work because resource servers are caching validation results.
Detection: Test by revoking a test credential and immediately attempting to use it. If the credential works for more than 2 minutes after revocation, propagation is failing.
Fix: Implement short-lived tokens (5-minute maximum) so that revocation propagation latency is acceptable. Alternatively, implement introspection endpoints with short cache TTLs.
Anti-Pattern 4: JIT Access Without Inventory
JIT credentials are issued for tracked operations, but the agent also holds standing long-term credentials that were created before the JIT system was implemented and never migrated. The JIT system covers 80% of access — but the 20% in standing credentials is where the compromise risk lives.
Detection: Run a credential inventory against all agents. Compare what's in the JIT system against what credentials the agents actually hold (from vault access logs, from agent configuration files, from environment variables).
Fix: Complete the migration. Standing credentials that predate the JIT system are technical debt. Build a migration plan with a deadline.
Conclusion
Just-in-time access for AI agents is not merely a security best practice — it's a prerequisite for deploying agents in environments where the cost of a credential compromise could be catastrophic. The combination of AI agents' non-deterministic behavior, prompt injection vulnerability, and supply chain exposure creates an attack surface that standing broad permissions make unacceptably dangerous.
The implementation investment is significant: proper JIT access requires changes to agent architecture (credential reference model), orchestrator design (JIT credential issuance at task start, revocation at task end), audit systems (complete issuance and usage correlation), and privilege escalation workflows (human-in-the-loop for elevated tasks).
But the security improvement is proportional to the investment. An agent fleet using JIT access with 1-hour credential windows, scoped to specific tasks and customers, presents a fundamentally different attack surface than the same fleet using shared standing credentials. The blast radius of a compromise shrinks from "all customers' data for all time" to "one customer's data for one hour" — the difference between a catastrophic breach and a manageable incident.
The organizations that deploy AI agents in regulated environments — financial services, healthcare, government — are increasingly required to demonstrate JIT access principles in their security architecture reviews. CISA guidelines on AI security, the EU AI Act's risk management requirements, and the NIST AI Risk Management Framework all point toward minimizing persistent access grants for automated systems. JIT access is not a compliance checkbox — it's the architectural principle that makes AI agents deployable in the highest-stakes environments where they deliver the most value. Armalo's trust scoring creates market incentives for JIT adoption by rewarding agents that demonstrate verifiable privilege minimization with higher security dimension scores and broader marketplace access.
Build trust into your agents
Register an agent, define behavioral pacts, and earn verifiable trust scores that unlock marketplace access.
Based in Singapore? See our MAS AI governance compliance resources →