[TECHNICAL DEEP-DIVES]

[

2/5/26

]

Attenuated Capabilities for AI Agents: Why Your Agent Should Never Hold a Credential

[Author]:

Amjad Fatmi

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:


# What Faramesh does at runtime — you never write this code
async def fetch_secret_aws(config: AWSConfig, secret_name: str) -> str:
    sts = boto3.client("sts")

    # assume the customer's role with ExternalId
    assumed = sts.assume_role(
        RoleArn=config.role_arn,
        RoleSessionName="faramesh-agent",
        ExternalId=config.external_id,   # prevents confused deputy
        DurationSeconds=900              # 15 minutes, minimum
    )

    # use temporary credentials to fetch secret
    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"]
    # credentials are garbage collected when this scope ends
    # they expire in 15 minutes regardless
# What Faramesh does at runtime — you never write this code
async def fetch_secret_aws(config: AWSConfig, secret_name: str) -> str:
    sts = boto3.client("sts")

    # assume the customer's role with ExternalId
    assumed = sts.assume_role(
        RoleArn=config.role_arn,
        RoleSessionName="faramesh-agent",
        ExternalId=config.external_id,   # prevents confused deputy
        DurationSeconds=900              # 15 minutes, minimum
    )

    # use temporary credentials to fetch secret
    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"]
    # credentials are garbage collected when this scope ends
    # they expire in 15 minutes regardless
# What Faramesh does at runtime — you never write this code
async def fetch_secret_aws(config: AWSConfig, secret_name: str) -> str:
    sts = boto3.client("sts")

    # assume the customer's role with ExternalId
    assumed = sts.assume_role(
        RoleArn=config.role_arn,
        RoleSessionName="faramesh-agent",
        ExternalId=config.external_id,   # prevents confused deputy
        DurationSeconds=900              # 15 minutes, minimum
    )

    # use temporary credentials to fetch secret
    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"]
    # credentials are garbage collected when this scope ends
    # they expire in 15 minutes regardless


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:

# Enable JWT auth
vault auth enable jwt

# Configure JWKS endpoint
vault write auth/jwt/config \
    jwks_url="https://auth.faramesh.dev/.well-known/jwks.json"

# Create a role for Faramesh
vault write auth/jwt/role/faramesh-agent \
    bound_audiences="https://vault.yourcompany.com" \
    user_claim="sub" \
    policies="faramesh-read-policy" \
    ttl="5m"

# Create a policy — read only, scoped to your prefix
vault policy write faramesh-read-policy - <<EOF
path "secret/prod/*" {
  capabilities = ["read"]
}
EOF
# Enable JWT auth
vault auth enable jwt

# Configure JWKS endpoint
vault write auth/jwt/config \
    jwks_url="https://auth.faramesh.dev/.well-known/jwks.json"

# Create a role for Faramesh
vault write auth/jwt/role/faramesh-agent \
    bound_audiences="https://vault.yourcompany.com" \
    user_claim="sub" \
    policies="faramesh-read-policy" \
    ttl="5m"

# Create a policy — read only, scoped to your prefix
vault policy write faramesh-read-policy - <<EOF
path "secret/prod/*" {
  capabilities = ["read"]
}
EOF
# Enable JWT auth
vault auth enable jwt

# Configure JWKS endpoint
vault write auth/jwt/config \
    jwks_url="https://auth.faramesh.dev/.well-known/jwks.json"

# Create a role for Faramesh
vault write auth/jwt/role/faramesh-agent \
    bound_audiences="https://vault.yourcompany.com" \
    user_claim="sub" \
    policies="faramesh-read-policy" \
    ttl="5m"

# Create a policy — read only, scoped to your prefix
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:


# Runtime fetch — no client secret anywhere
async def fetch_secret_azure(config: AzureConfig, secret_name: str) -> str:
    credential = WorkloadIdentityCredential(
        tenant_id=FARAMESH_TENANT_ID,
        client_id=FARAMESH_CLIENT_ID,
        # token_file_path comes from projected service account volume
        # Faramesh's own identity, never the customer's credentials
    )

    client = SecretClient(
        vault_url=config.vault_url,
        credential=credential
    )

    secret = await client.get_secret(secret_name)
    return secret.value
    # credential object does not persist beyond this call
# Runtime fetch — no client secret anywhere
async def fetch_secret_azure(config: AzureConfig, secret_name: str) -> str:
    credential = WorkloadIdentityCredential(
        tenant_id=FARAMESH_TENANT_ID,
        client_id=FARAMESH_CLIENT_ID,
        # token_file_path comes from projected service account volume
        # Faramesh's own identity, never the customer's credentials
    )

    client = SecretClient(
        vault_url=config.vault_url,
        credential=credential
    )

    secret = await client.get_secret(secret_name)
    return secret.value
    # credential object does not persist beyond this call
# Runtime fetch — no client secret anywhere
async def fetch_secret_azure(config: AzureConfig, secret_name: str) -> str:
    credential = WorkloadIdentityCredential(
        tenant_id=FARAMESH_TENANT_ID,
        client_id=FARAMESH_CLIENT_ID,
        # token_file_path comes from projected service account volume
        # Faramesh's own identity, never the customer's credentials
    )

    client = SecretClient(
        vault_url=config.vault_url,
        credential=credential
    )

    secret = await client.get_secret(secret_name)
    return secret.value
    # credential object does not persist beyond this call


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."


Previous

More

Previous

More

Next

More

Next

More

[GET STARTED IN MINUTES]

Ready to give Faramesh a try?

The execution boundary your agents are missing.
Start free. No credit card required.

[GET STARTED IN MINUTES]

Ready to give Faramesh a try?

The execution boundary your agents are missing.
Start free. No credit card required.