Facio's Per-Tool Credentials: How AI Agents Authenticate Without Becoming a Credential Vault
An AI agent that calls a hundred APIs needs credentials for each one. A Postgres credential for queries. A Slack token for messaging. A GitHub PAT for repo access. A Stripe API key for payments. A Google service account for Drive. The credentials accumulate. The agent becomes a credential vault.
The naive approach is to give the agent one master credential with broad access. The agent logs in as a powerful user, has access to everything, and uses whatever credential it needs. This is a security catastrophe: if the agent is compromised, the attacker gets everything. If the credential leaks (and credentials in prompts leak), the attacker gets everything. The blast radius is enormous.
The slightly less naive approach is to let the agent store credentials in environment variables or config files. The agent reads the credentials when it needs them. This is also bad: the credentials are accessible to anyone who can read the agent's environment. The blast radius is bounded by who can read the environment, but that's still a lot of people.
The discipline Facio applies is per-tool credentials. Each tool has its own narrowly-scoped credential. The credential is never seen by the model. The credential is replaced on rotation. The credential use is audited per call. The agent gets access; the security team gets controls.
Here's how the discipline works, what it enforces, and why per-tool credentials are what makes AI agents deployable in security-conscious environments.
The AI Credential Problem
AI agents create a new kind of credential problem:
Problem 1: The model sees the prompt. If the credential is in the prompt (or appears in tool inputs), the model can be tricked into revealing it. Prompt injection attacks can extract credentials.
Problem 2: The agent runs continuously. Traditional credentials are short-lived (a human logs in, does work, logs out). Agent credentials run for hours or days. The longer a credential is in use, the higher the exposure.
Problem 3: The agent touches many systems. A single agent might call 10-50 APIs. Each API needs its own credential. The credential count is high.
Problem 4: The credential is hard to scope. Many APIs offer only coarse-grained permissions. A Slack token might have access to all channels, not just specific ones. A GitHub PAT might have access to all repos, not just specific ones.
Problem 5: The credential lifetime is long. Service account credentials are often valid for months or years. If compromised, the attacker has long-term access.
Problem 6: The credential audit is weak. Traditional credential systems audit "user X logged in at time Y." They don't audit "user X's agent called API Z with payload P." The agent's actions are hard to attribute.
The combination is structurally worse than human credentials. Humans notice unusual activity. Agents don't. Humans can be trained on security. Agents follow their instructions. Humans log out. Agents run forever.
The discipline is to address each problem with a specific control.
The Per-Tool Credentials Discipline
Facio's per-tool credentials discipline has five pillars. Each addresses a specific aspect of the credential problem.
Pillar 1: Credential Isolation (Per Tool, Per Scope)
Each tool has its own credential. The credential is narrowly scoped to the tool's needs:
# Per-tool credentials
tool_credentials = {
"postgres.execute_query": {
"credential_id": "cred-pg-readonly-001",
"type": "postgres",
"username": "agent_readonly",
"permissions": ["SELECT"], # Read-only
"databases": ["customers", "orders"], # Limited to specific DBs
"tables": ["public.orders", "public.customers"], # Limited to specific tables
"max_connections": 5,
},
"slack.send_message": {
"credential_id": "cred-slack-bot-001",
"type": "slack_bot",
"scopes": ["chat:write"], # Can only send, not read
"channels": ["#customer-support", "#alerts"], # Limited to specific channels
"workspace": "acme-corp",
},
"github.create_issue": {
"credential_id": "cred-github-bot-001",
"type": "github_pat",
"scopes": ["repo:write"], # Specific scope
"repos": ["acme-corp/support-tracker"], # Single repo
"permissions": ["create_issue", "add_comment"], # Specific permissions
},
"stripe.create_refund": {
"credential_id": "cred-stripe-refund-001",
"type": "stripe_restricted",
"permissions": ["refunds:write"],
"max_refund_amount_usd": 500, # Hard cap
"max_refunds_per_hour": 20,
},
}
Each credential is narrowly scoped. The Postgres credential can't write. The Slack credential can't read. The GitHub credential is limited to one repo. The Stripe credential has a dollar cap.
The narrow scoping limits blast radius. If one credential is compromised, the damage is bounded.
Pillar 2: Credential Injection (Never in Prompts)
The credential is injected by the runtime, never written into prompts or tool inputs visible to the model:
# Naive (BAD): credential in tool input
{
"tool": "postgres.execute_query",
"input": {
"connection_string": "postgresql://agent_readonly:secret_password@host/db"
}
}
# Problem: "secret_password" is visible to the model
# Disciplined (GOOD): credential referenced by ID
{
"tool": "postgres.execute_query",
"input": {
"credential_ref": "cred-pg-readonly-001", # Runtime resolves this
"query": "SELECT * FROM orders WHERE customer_id = $1",
"parameters": ["cust-123"]
}
}
# Runtime resolves credential_ref → actual credentials
# Runtime injects credentials into the connection
# Model never sees the actual password
The credential reference pattern keeps credentials out of the model's context. Even prompt injection can't extract credentials that aren't there.
Pillar 3: Credential Rotation (Automatic, Frequent)
Credentials rotate automatically. The agent doesn't need to know; the runtime handles it:
# Credential rotation policy
rotation_policy = {
"cred-pg-readonly-001": {
"rotation_interval_days": 7,
"rotation_strategy": "create_new_then_revoke_old", # Graceful rotation
"grace_period_hours": 24, # Old credential works for 24h
},
"cred-slack-bot-001": {
"rotation_interval_days": 30,
"rotation_strategy": "instant_revoke", # Immediate cutover
},
"cred-github-bot-001": {
"rotation_interval_days": 1, # Daily for high-value
"rotation_strategy": "create_new_then_revoke_old",
},
}
# Rotation happens automatically by the runtime
# The agent gets the new credential without code change
# Old credentials stop working after grace period
The frequent rotation limits the value of stolen credentials. A credential stolen today is invalid tomorrow.
Pillar 4: Credential Auditing (Per Call)
Every credential use is audited:
# Per-call audit
audit_entry = {
"timestamp": "2026-07-16T10:23:45Z",
"session_id": "agent-2026-07-16-101000",
"tool": "postgres.execute_query",
"credential_ref": "cred-pg-readonly-001",
"credential_user": "agent_readonly",
"query_preview": "SELECT * FROM orders WHERE customer_id = $1",
"result_status": "success",
"rows_returned": 12,
"duration_ms": 47,
}
The audit trail captures every credential use. The team can investigate suspicious activity, attribute actions to specific sessions, and demonstrate compliance.
Pillar 5: Credential Broker (Centralized Management)
A centralized credential broker manages all credentials:
# Credential broker responsibilities
broker_responsibilities = {
"storage": "Encrypted at rest, decrypted only at use time",
"access_control": "Only the runtime can request credentials, never the model",
"rotation": "Automatic per policy, with notification to relevant teams",
"audit": "Every access logged to immutable audit trail",
"revocation": "Immediate on compromise detection or policy violation",
"scope_enforcement": "Credentials are returned only for authorized tools/users/workspaces",
}
The broker is the single source of truth for credentials. No agent, no model, no prompt ever has direct access to raw credentials.
The Per-Tool Credentials Patterns
Several patterns emerge from disciplined credential management.
Pattern 1: Just-In-Time Credentials
Some credentials are issued just-in-time for specific operations:
# Just-in-time credential issuance
def issue_credential(session_id, tool, purpose, ttl_seconds=300):
"""
Issue a credential that's valid only for a specific session,
tool, and purpose, with a short TTL.
"""
credential = {
"credential_id": f"jit-{uuid.uuid4()}",
"session_id": session_id,
"tool": tool,
"purpose": purpose,
"expires_at": now() + ttl_seconds,
"uses_remaining": 1, # Single use
}
record_issuance(credential)
return credential
# Example: agent needs to make a refund
credential = issue_credential(
session_id="agent-2026-07-16-101000",
tool="stripe.create_refund",
purpose="refund_for_order_12345",
ttl_seconds=300 # 5 minutes
)
# Use credential once, it expires
The just-in-time pattern minimizes credential lifetime. A credential exists only when needed and expires after use.
Pattern 2: Credential Chaining for Multi-Step Workflows
For workflows that need multiple credentials, the credentials are linked:
# Credential chain for order fulfillment workflow
workflow_chain = {
"workflow_id": "wf-order-fulfillment-12345",
"credentials": [
{"tool": "postgres.read_order", "credential_ref": "cred-pg-readonly-001"},
{"tool": "stripe.create_refund", "credential_ref": "cred-stripe-refund-001"},
{"tool": "slack.notify_customer", "credential_ref": "cred-slack-bot-001"},
{"tool": "postgres.update_order_status", "credential_ref": "cred-pg-write-002"},
],
"binding": {
# All credentials in this chain must be used by the same session
"session_id": "agent-2026-07-16-101000",
"max_duration_minutes": 15,
}
}
# The runtime ensures all credentials in the chain are used by the same session
# If any credential use fails, the chain is invalidated
# Audit trail links all credential uses to the same workflow
The chaining pattern ensures credentials are used coherently. A workflow's credentials are linked, and the linkage is auditable.
Pattern 3: Temporary Elevation
For operations requiring elevated permissions, temporary elevation is used:
# Temporary elevation for high-stakes operation
elevation_request = {
"operation": "database_admin",
"justification": "Production incident requires index rebuild",
"elevated_credential": "cred-pg-admin-elevated",
"duration_minutes": 30,
"approver_required": True,
"approver": "database_team_lead",
}
# Approval flow
if ask_approval(elevation_request, timeout_minutes=5):
elevated_cred = issue_elevated_credential(
"cred-pg-admin-elevated",
ttl_seconds=1800, # 30 minutes
session_id=current_session
)
use_credential(elevated_cred, "postgres.admin_command", ...)
# Credential auto-revokes after 30 minutes
else:
deny_elevation()
The temporary elevation pattern grants elevated permissions only when justified and approved. The elevation is time-bounded.
Pattern 4: Credential Substitution
When a credential fails, the runtime substitutes an alternative:
# Credential substitution
primary_credential = "cred-pg-readonly-001"
primary_failed = True
if primary_failed:
# Try fallback
fallback_credential = "cred-pg-readonly-002" # Backup credential, read-only to different DB
if verify_credential(fallback_credential):
use_credential(fallback_credential, ...)
else:
# Fail gracefully, log the failure
fail("primary_and_fallback_credentials_unavailable")
The substitution pattern handles credential failures without exposing credentials.
Pattern 5: Customer-Managed Credentials
For sensitive workloads, customers bring their own credentials:
# Customer-managed credentials (BYOC: Bring Your Own Credential)
customer_credentials = {
"customer_id": "cust-acme-corp",
"credentials": {
"stripe": {
"type": "customer_managed",
"credential_ref": "cust-acme-stripe-prod",
"managed_by": "customer", # Customer rotates, customer owns
"facio_access": "metadata_only", # Facio sees metadata, not the secret
},
"database": {
"type": "customer_managed",
"credential_ref": "cust-acme-db-prod",
"managed_by": "customer",
"facio_access": "metadata_only",
},
}
}
The customer-managed pattern gives customers control over their most sensitive credentials. Facio uses them without ever seeing the secret.
The Per-Tool Credentials Doesn't Do
Honest limitations:
- It doesn't eliminate credential risk. Credentials still exist. They're just better managed. Risk is reduced, not eliminated.
- It can be complex. Multiple credentials, multiple rotation policies, multiple scopes. The complexity is the price of security.
- It requires API support. Some APIs only offer coarse-grained credentials. The discipline works best with APIs that support fine-grained scopes.
- It doesn't prevent all misuse. A scoped credential can still be misused within its scope. A read-only credential can read too much. The discipline reduces blast radius, not eliminates it.
- It adds latency. Credential resolution takes time. Per-call resolution adds latency. The discipline trades latency for security.
The Credential Discipline as Operational Practice
The per-tool credential discipline is operational practice:
Least privilege. The team starts with the minimum permissions and expands only when justified. Permissions accumulate slowly.
Regular audits. The team reviews credential scopes. They revoke unused credentials. They tighten overly-broad credentials.
Rotation monitoring. The team monitors rotation success. They alert on rotation failures. They verify old credentials stop working.
Incident response. The team has runbooks for credential compromise. They can rotate immediately, revoke broadly, and investigate.
The practice is what makes the discipline sustainable. Without it, credentials accumulate and permissions drift. With it, credentials stay minimal and current.
The Compound Effect of Per-Tool Credentials
Per-tool credentials compound:
- Bounded blast radius. A compromised credential damages only one tool, one scope, one resource. The system survives.
- Easier compliance. Auditors see fine-grained access control. The audit is convincing.
- Customer trust. Customers trust systems with scoped credentials. They trust systems without them less.
- Faster incident response. When something is wrong, the team knows exactly which credential to revoke. The response is fast.
- Better engineering hygiene. The discipline forces clear thinking about who needs what. The engineering is cleaner.
The undisciplined approach has the opposite trajectory. Broad credentials, large blast radius, slow incident response, customer distrust, compliance gaps.
Bottom Line
AI agents need credentials. Without discipline, credentials become liabilities. With per-tool credentials, they become manageable security boundaries.
Facio's per-tool credentials discipline provides credential isolation per tool/scope, credential injection via references never visible to the model, automatic rotation with policies, per-call auditing, and a centralized credential broker. The discipline makes AI agents deployable in security-conscious environments.
The agent without credential discipline is a credential vault waiting to be breached. The agent with it is a partner with managed access. The security team prefers the managed access.
Because AI agents in production touch many systems. The question is whether the access is controlled or exposed. The per-tool credentials discipline is what makes the access controlled.
See the credential management documentation for broker configuration, rotation policies, and audit trail queries.