There is a principle in security called least privilege. Give every process only the permissions it needs to do its job, nothing more. It is one of the oldest ideas in the field and one of the most consistently violated in practice.
AI agents are violating it in a new and particularly dangerous way.
Most agent deployments today work like this: the agent gets an API key, a database connection string, or an AWS access key. It holds that credential for the duration of its operation. When it needs to call an API, it uses the key. When it needs to hit a database, it uses the connection string. Simple. Convenient. And completely wrong from a security standpoint.
This post explains why agents should never hold credentials, what the alternative architecture looks like, and how to implement it without breaking your agent's ability to do its job.
The credential problem
When you give an agent a credential, you are not giving it a capability for one action. You are giving it unlimited capability to perform that action class, forever, with no record of why each individual use was authorized.
# What most teams do today
agent = Agent(
tools=[...]
# What most teams do today
agent = Agent(
tools=[...]
# What most teams do today
agent = Agent(
tools=[...]
This creates three compounding problems.
Problem 1: Blast radius. If the agent is compromised via prompt injection or a malicious tool, the attacker has everything the agent has. One successful attack yields every credential the agent was given. If the agent had keys to five systems, the attacker now has keys to five systems.
Problem 2: No individual action authorization. The credential grants permission for a class of actions — "can charge cards," "can read S3," "can query the database." It does not grant permission for this specific charge, this specific file, this specific query. There is no per-action gate. The credential is the gate, and it's always open.
Problem 3: Credential sprawl. Agents multiply. You start with one agent, then you have five, then twenty. Each one holds its own set of credentials. Rotation becomes a nightmare. Auditability becomes impossible. You lose track of which agent has access to what.
The solution is a concept called attenuated capabilities. The agent never holds the credential. It requests access for a specific action, that request is evaluated, and if approved, the credential is fetched ephemerally, used once, and discarded.
What attenuated capabilities looks like
In a correctly designed system, the agent has no persistent access to anything. It has the ability to request access, for a specific purpose, at a specific moment. Each request is evaluated independently. Each grant expires immediately after use.
The agent never touches the credential. The credential never lives in the agent's environment. If the agent is compromised, the attacker gets nothing that authenticates to anything.
The formal structure of an attenuated permit
This is not just an architectural pattern — it is a formally specified artifact. In Faramesh's published specification, the execution permit that authorizes credential access has an exact structure:
T = (tenant_id, env, hA, hP, hS, t_issue, t_exp, constraints)
T = (tenant_id, env, hA, hP, hS, t_issue, t_exp, constraints)
T = (tenant_id, env, hA, hP, hS, t_issue, t_exp, constraints)
Each field is load-bearing:
┌─────────────────────────────────────────────────────────────────┐
│ EXECUTION PERMIT ARTIFACT — FIELD BY FIELD │
│ │
│ tenant_id Scopes permit to this tenant only. A permit │
│ minted for tenant A is invalid for tenant B. │
│ │
│ env Scopes permit to this environment. A permit │
│ minted in dev is invalid in production. │
│ │
│ hA SHA-256 hash of the canonical action. Binds │
│ this permit to exactly one action — not a │
│ class of actions, not a tool name, the exact │
│ action with exact parameters. │
│ │
│ hP Hash of the policy version that authorized it. │
│ If policy changes, old permits can be detected │
│ as issued under a prior policy version. │
│ │
│ hS Hash of the evaluation state snapshot. Records │
│ what the system knew at decision time. │
│ │
│ t_issue Issuance timestamp. When the permit was created. │
│ │
│ t_exp Expiry timestamp. Permits are time-limited. │
│ An expired permit is rejected at execution. │
│ │
│ constraints Per-action limits: amount caps, rate limits, │
│ blast-radius bounds, single-use flags. │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ EXECUTION PERMIT ARTIFACT — FIELD BY FIELD │
│ │
│ tenant_id Scopes permit to this tenant only. A permit │
│ minted for tenant A is invalid for tenant B. │
│ │
│ env Scopes permit to this environment. A permit │
│ minted in dev is invalid in production. │
│ │
│ hA SHA-256 hash of the canonical action. Binds │
│ this permit to exactly one action — not a │
│ class of actions, not a tool name, the exact │
│ action with exact parameters. │
│ │
│ hP Hash of the policy version that authorized it. │
│ If policy changes, old permits can be detected │
│ as issued under a prior policy version. │
│ │
│ hS Hash of the evaluation state snapshot. Records │
│ what the system knew at decision time. │
│ │
│ t_issue Issuance timestamp. When the permit was created. │
│ │
│ t_exp Expiry timestamp. Permits are time-limited. │
│ An expired permit is rejected at execution. │
│ │
│ constraints Per-action limits: amount caps, rate limits, │
│ blast-radius bounds, single-use flags. │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ EXECUTION PERMIT ARTIFACT — FIELD BY FIELD │
│ │
│ tenant_id Scopes permit to this tenant only. A permit │
│ minted for tenant A is invalid for tenant B. │
│ │
│ env Scopes permit to this environment. A permit │
│ minted in dev is invalid in production. │
│ │
│ hA SHA-256 hash of the canonical action. Binds │
│ this permit to exactly one action — not a │
│ class of actions, not a tool name, the exact │
│ action with exact parameters. │
│ │
│ hP Hash of the policy version that authorized it. │
│ If policy changes, old permits can be detected │
│ as issued under a prior policy version. │
│ │
│ hS Hash of the evaluation state snapshot. Records │
│ what the system knew at decision time. │
│ │
│ t_issue Issuance timestamp. When the permit was created. │
│ │
│ t_exp Expiry timestamp. Permits are time-limited. │
│ An expired permit is rejected at execution. │
│ │
│ constraints Per-action limits: amount caps, rate limits, │
│ blast-radius bounds, single-use flags. │
│ │
└─────────────────────────────────────────────────────────────────┘
This is what "attenuated" means precisely. The permit does not grant "permission to use the Stripe API." It grants permission to execute this specific action, under this specific policy, in this specific environment, before this specific expiry, within these specific constraints. Every dimension that is not explicitly permitted is implicitly denied.
When the credential is fetched, it is fetched to satisfy this permit — and only this permit. The credential access is bound to the action hash hA. If someone tries to use the same permit to authorize a different action, the hashes don't match and the permit is rejected before the credential fetch even happens.
The crown jewels problem
There is an obvious objection to this architecture: now your credential broker is the single point of failure. If someone breaches the system that manages credentials, they get everything.
This is the crown jewels problem, and it is the reason most implementations of credential management are insecure at scale. The vault becomes the target. One breach, every customer's credentials.
The solution is federated trust. The credential broker stores no credentials.
If Faramesh's database is breached, the attacker gets connection metadata. URLs, role names, path prefixes. None of this authenticates to anything without Faramesh's own signing keys and cloud identity, which never touch the database and are hardware-backed.
Authentication happens at runtime via workload identity. Here is how that works for each backend.
How federated trust works per backend
AWS Secrets Manager
Your secrets stay in your AWS account. Faramesh never touches them except at runtime, and only when a policy-approved action requires it.
The setup: you create a cross-account IAM role in your AWS account that trusts Faramesh's AWS account. When Faramesh needs a secret, it calls STS AssumeRole with your role ARN and an ExternalId. AWS returns 15-minute temporary credentials. The secret is fetched. The credentials expire.
The ExternalId is the critical piece. Without it, any party who learns your role ARN could assume it. With ExternalId, only Faramesh's specific tenant context can assume the role. This prevents the confused deputy attack.
What you set up in AWS:


What you enter in Faramesh:
async def fetch_secret_aws(config: AWSConfig, secret_name: str) -> str:
sts = boto3.client("sts")
assumed = sts.assume_role(
RoleArn=config.role_arn,
RoleSessionName="faramesh-agent",
ExternalId=config.external_id,
DurationSeconds=900
)
sm = boto3.client(
"secretsmanager",
region_name=config.region,
aws_access_key_id=assumed["Credentials"]["AccessKeyId"],
aws_secret_access_key=assumed["Credentials"]["SecretAccessKey"],
aws_session_token=assumed["Credentials"]["SessionToken"]
)
response = sm.get_secret_value(SecretId=secret_name)
return response["SecretString"]
async def fetch_secret_aws(config: AWSConfig, secret_name: str) -> str:
sts = boto3.client("sts")
assumed = sts.assume_role(
RoleArn=config.role_arn,
RoleSessionName="faramesh-agent",
ExternalId=config.external_id,
DurationSeconds=900
)
sm = boto3.client(
"secretsmanager",
region_name=config.region,
aws_access_key_id=assumed["Credentials"]["AccessKeyId"],
aws_secret_access_key=assumed["Credentials"]["SecretAccessKey"],
aws_session_token=assumed["Credentials"]["SessionToken"]
)
response = sm.get_secret_value(SecretId=secret_name)
return response["SecretString"]
async def fetch_secret_aws(config: AWSConfig, secret_name: str) -> str:
sts = boto3.client("sts")
assumed = sts.assume_role(
RoleArn=config.role_arn,
RoleSessionName="faramesh-agent",
ExternalId=config.external_id,
DurationSeconds=900
)
sm = boto3.client(
"secretsmanager",
region_name=config.region,
aws_access_key_id=assumed["Credentials"]["AccessKeyId"],
aws_secret_access_key=assumed["Credentials"]["SecretAccessKey"],
aws_session_token=assumed["Credentials"]["SessionToken"]
)
response = sm.get_secret_value(SecretId=secret_name)
return response["SecretString"]
HashiCorp Vault
Your Vault instance, your network, your keys. Faramesh authenticates via JWT, not a static token.
The setup: you enable JWT auth in your Vault and point it at Faramesh's JWKS endpoint. When Faramesh needs a secret, it presents a signed JWT. Vault validates the signature against Faramesh's public key, checks the role, and returns a short-lived Vault token. The secret is fetched. The token expires.
What you set up in Vault:
vault auth enable jwt
vault write auth/jwt/config \
jwks_url="https://auth.faramesh.dev/.well-known/jwks.json"
vault write auth/jwt/role/faramesh-agent \
bound_audiences="https://vault.yourcompany.com" \
user_claim="sub" \
policies="faramesh-read-policy" \
ttl="5m"
vault policy write faramesh-read-policy - <<EOF
path "secret/prod/*" {
capabilities = ["read"]
}
EOF
vault auth enable jwt
vault write auth/jwt/config \
jwks_url="https://auth.faramesh.dev/.well-known/jwks.json"
vault write auth/jwt/role/faramesh-agent \
bound_audiences="https://vault.yourcompany.com" \
user_claim="sub" \
policies="faramesh-read-policy" \
ttl="5m"
vault policy write faramesh-read-policy - <<EOF
path "secret/prod/*" {
capabilities = ["read"]
}
EOF
vault auth enable jwt
vault write auth/jwt/config \
jwks_url="https://auth.faramesh.dev/.well-known/jwks.json"
vault write auth/jwt/role/faramesh-agent \
bound_audiences="https://vault.yourcompany.com" \
user_claim="sub" \
policies="faramesh-read-policy" \
ttl="5m"
vault policy write faramesh-read-policy - <<EOF
path "secret/prod/*" {
capabilities = ["read"]
}
EOF
What you enter in Faramesh:
Azure Key Vault
Azure uses federated credentials on an app registration or managed identity. Faramesh's OIDC issuer is trusted directly, no client secret required.
What you set up in Azure:

What you enter in Faramesh:

async def fetch_secret_azure(config: AzureConfig, secret_name: str) -> str:
credential = WorkloadIdentityCredential(
tenant_id=FARAMESH_TENANT_ID,
client_id=FARAMESH_CLIENT_ID,
)
client = SecretClient(
vault_url=config.vault_url,
credential=credential
)
secret = await client.get_secret(secret_name)
return secret.value
async def fetch_secret_azure(config: AzureConfig, secret_name: str) -> str:
credential = WorkloadIdentityCredential(
tenant_id=FARAMESH_TENANT_ID,
client_id=FARAMESH_CLIENT_ID,
)
client = SecretClient(
vault_url=config.vault_url,
credential=credential
)
secret = await client.get_secret(secret_name)
return secret.value
async def fetch_secret_azure(config: AzureConfig, secret_name: str) -> str:
credential = WorkloadIdentityCredential(
tenant_id=FARAMESH_TENANT_ID,
client_id=FARAMESH_CLIENT_ID,
)
client = SecretClient(
vault_url=config.vault_url,
credential=credential
)
secret = await client.get_secret(secret_name)
return secret.value
Doppler
Doppler supports service tokens with scoped, rotatable access. For teams who haven't migrated to OIDC yet, Faramesh supports service token auth with explicit scope limits.
1Password Secrets Automation
1Password Connect server with a service account token, or 1Password's OIDC integration where available.
The atomic binding, authorization plus credential access
Here is the piece that makes this architecture genuinely novel, not just a well-implemented credential manager.
Every time Faramesh fetches a credential to execute an action, it binds the credential access to the authorization decision that permitted it. Not two log entries you correlate later. One cryptographic record.
This gives you something no logging system provides: cryptographic proof that the credential was only accessed after an authorization decision was made, that the authorization decision was made under a specific policy version, and that the record has not been tampered with.
If someone asks "why did the agent charge that card?" you don't search logs. You replay the DPR. The answer is in one record: what was authorized, by what policy, at what time, and what credential access followed.
What the Faramesh connector interface looks like
The integration setup in Horizon is designed so that no credential-shaped value ever enters a form field.
┌────────────────────────────────────────────────────────────────┐
│ FARAMESH HORIZON — Integrations │
│ │
│ Secrets Manager [Configure] │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Backend: ○ AWS Secrets Manager │ │
│ │ ○ HashiCorp Vault │ │
│ │ ○ Azure Key Vault │ │
│ │ ○ Doppler │ │
│ │ ○ 1Password │ │
│ │ │ │
│ │ [AWS selected] │ │
│ │ │ │
│ │ Role ARN [arn:aws:iam::123456789:role/...] │ │
│ │ External ID [faramesh-tenant-abc123] [copy] │ │
│ │ Region [us-east-1] │ │
│ │ Secrets Prefix [/prod/myapp/] │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ ℹ No access keys or secrets are stored. │ │ │
│ │ │ Authentication uses cross-account IAM with │ │ │
│ │ │ ExternalId at runtime. │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ [Test Connection] [Save]
┌────────────────────────────────────────────────────────────────┐
│ FARAMESH HORIZON — Integrations │
│ │
│ Secrets Manager [Configure] │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Backend: ○ AWS Secrets Manager │ │
│ │ ○ HashiCorp Vault │ │
│ │ ○ Azure Key Vault │ │
│ │ ○ Doppler │ │
│ │ ○ 1Password │ │
│ │ │ │
│ │ [AWS selected] │ │
│ │ │ │
│ │ Role ARN [arn:aws:iam::123456789:role/...] │ │
│ │ External ID [faramesh-tenant-abc123] [copy] │ │
│ │ Region [us-east-1] │ │
│ │ Secrets Prefix [/prod/myapp/] │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ ℹ No access keys or secrets are stored. │ │ │
│ │ │ Authentication uses cross-account IAM with │ │ │
│ │ │ ExternalId at runtime. │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ [Test Connection] [Save]
┌────────────────────────────────────────────────────────────────┐
│ FARAMESH HORIZON — Integrations │
│ │
│ Secrets Manager [Configure] │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Backend: ○ AWS Secrets Manager │ │
│ │ ○ HashiCorp Vault │ │
│ │ ○ Azure Key Vault │ │
│ │ ○ Doppler │ │
│ │ ○ 1Password │ │
│ │ │ │
│ │ [AWS selected] │ │
│ │ │ │
│ │ Role ARN [arn:aws:iam::123456789:role/...] │ │
│ │ External ID [faramesh-tenant-abc123] [copy] │ │
│ │ Region [us-east-1] │ │
│ │ Secrets Prefix [/prod/myapp/] │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ ℹ No access keys or secrets are stored. │ │ │
│ │ │ Authentication uses cross-account IAM with │ │ │
│ │ │ ExternalId at runtime. │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ [Test Connection] [Save]
Why this matters more than it sounds
The credential sequestration architecture is not a convenience feature. It is the difference between an agent deployment that is auditable and one that is not.
When an agent holds a credential, you cannot answer these questions with certainty:
Was this specific API call individually authorized?
Which policy was in effect when the decision was made?
Was the credential used only for actions that were explicitly permitted?
When an agent operates under attenuated capabilities, every credential access is bound to a specific authorization decision. The answer to all three questions is in the DPR. Cryptographically verifiable. Tamper-evident. Replayable against any policy version.
For enterprises in regulated industries, financial services, healthcare, government, this is not a nice-to-have. It is the capability that makes AI agents deployable in production environments at all. Auditors need to know not just what ran, but why it was permitted to run. Faramesh is the only architecture that makes that question answerable.
The practical difference
Here is what the two architectures look like side by side for a compliance team doing an incident review:
Traditional approach (agent holds credentials):
Auditor: "Why did the agent access the customer record?"
Engineer: "The agent had database credentials. It ran a query."
Auditor: "Was that query individually authorized?"
Engineer: "The service account had read access."
Auditor: "But was this specific query, at this specific time, authorized?"
Engineer: "...it had credentials."
Faramesh approach (attenuated capabilities + DPR):
Auditor: "Why did the agent access the customer record?"
Engineer: [opens DPR]
Traditional approach (agent holds credentials):
Auditor: "Why did the agent access the customer record?"
Engineer: "The agent had database credentials. It ran a query."
Auditor: "Was that query individually authorized?"
Engineer: "The service account had read access."
Auditor: "But was this specific query, at this specific time, authorized?"
Engineer: "...it had credentials."
Faramesh approach (attenuated capabilities + DPR):
Auditor: "Why did the agent access the customer record?"
Engineer: [opens DPR]
Traditional approach (agent holds credentials):
Auditor: "Why did the agent access the customer record?"
Engineer: "The agent had database credentials. It ran a query."
Auditor: "Was that query individually authorized?"
Engineer: "The service account had read access."
Auditor: "But was this specific query, at this specific time, authorized?"
Engineer: "...it had credentials."
Faramesh approach (attenuated capabilities + DPR):
Auditor: "Why did the agent access the customer record?"
Engineer: [opens DPR]
The difference is not just technical. It is the difference between "we think we're secure" and "we can prove what was authorized and when."