Credential mediation — short-lived scoped tokens
The strongest way to shrink an agent's blast radius is to stop handing it
ambient, long-lived credentials at all. When a policy decision is
scoped_token, Unveilr's broker mints a short-lived credential scoped to
the exact action and resource, bound to the decision that authorized it — so
the agent proceeds with the least authority the task needs, for as short a time
as possible.
This is the runtime companion to Govern: same one decision brain, a new outcome that reduces authority instead of only allowing or denying it.
Added value
| Without credential mediation | With scoped_token |
|---|---|
Agent runs with a broad, long-lived role (iam:*, hours/days) | Broker mints a credential scoped to one action on one resource |
| A prompt-injected or drifted agent inherits full standing authority | Authority is the intersection of the role and a per-request session policy |
| "Who used this key?" is a CloudTrail archaeology project | Every credential is bound to a sealed decision + request hash |
| Revocation means rotating a shared key | Revoke one issued credential; the event is sealed |
How it works
agent runtime ──POST /v1/govern/check──▶ PDP (one decision brain)
│ decision = scoped_token
▼
scoped-token broker
│ STS AssumeRole
│ + inline session policy
│ { Allow: <action> on <resource> }
│ DurationSeconds ≤ BROKER_MAX_TTL
▼
short-lived credential ◀──────── returned to the caller (secret here only)
│
▼
credential.issued.v1 sealed into the
hash-chained evidence ledger (no secret)
The effective permission is the intersection of the broker role's identity policy and the per-request session policy, so an issued credential can never exceed the decision that minted it. The credential is returned to the calling agent once; the secret material is never persisted and never sealed — only the non-secret access-key id is retained, so CloudTrail activity correlates back to the exact decision.
A policy that mints scoped tokens
name: prod IAM via scoped token
effect: scoped_token
priority: 100
match:
toolNameGlobs: ["aws.iam.*"]
A call the policy matches returns scoped_token; the agent receives a credential
bound to the action (iam:UpdateRole) and the resource ARN it passed:
{
"decision": "scoped_token",
"credential": {
"credentialId": "cred_…",
"accessKeyId": "ASIA…",
"secretAccessKey": "…", // in this response only — never sealed
"sessionToken": "…",
"expiration": "2026-07-27T16:15:00Z",
"action": "iam:UpdateRole",
"resource": "arn:aws:iam::123456789012:role/ProdDeploy",
"scopePolicyHash": "…",
"evidenceId": "evt_…"
}
}
Enforcement points
| Surface | Behavior on scoped_token |
|---|---|
POST /v1/govern/check (SDK / CLI wrapper) | Mints the scoped credential and returns it, bound to the sealed decision |
| MCP gateway (proxy) | Fails closed — the proxy cannot mint AWS credentials, so it blocks and points the caller at govern/check. It never forwards with the agent's ambient credential |
Fail-safe by construction: a scoped_token decision is treated as not
forwardable as-is everywhere — the action only proceeds once a scoped credential
is issued.
Proof & containment
credential.issued.v1andcredential.revoked.v1are sealed into the same per-tenant, hash-chained ledger as every other decision — the chain still verifies after issuance (see Prove).- List issued credentials for audit / CloudTrail correlation:
GET /v1/broker/credentials.
Proving bounded authority with CloudTrail
Issuing a scoped credential is a claim; CloudTrail is the proof. Because the broker records each credential's non-secret access-key id, Unveilr can correlate the credential's real AWS activity back to the decision that minted it:
POST /v1/broker/credentials/{id}/correlate
It pulls the credential's CloudTrail events (by access-key id, over the
credential's lifetime) and classifies each one against the grant, returning a
verdict sealed as credential.activity.v1:
| Verdict | Meaning |
|---|---|
bounded_authority_proven | The granted action ran; any out-of-scope action was denied by AWS — the inline session policy held |
scope_violation | An out-of-scope action succeeded — the credential exceeded its grant (alarm; the boundary failed) |
no_activity | The credential was issued but never used |
An out-of-scope AccessDenied in CloudTrail is not a failure — it is the
evidence that the boundary worked. This is REQ-GRD-004's acceptance criterion:
CloudTrail and token metadata prove bounded authority. (Read-only; needs
cloudtrail:LookupEvents, enabled with CLOUDTRAIL_ENABLED.)
- Revoke (containment):
POST /v1/broker/credentials/{id}/revokemarks the credential revoked and seals the event. (STS session tokens cannot be individually invalidated mid-flight; the durable compensating control is the target role'sAWSRevokeOlderSessionspolicy — recorded on the timeline.)
Configuration
| Setting | Meaning |
|---|---|
BROKER_ENABLED | Off by default; credential mediation only runs when enabled |
BROKER_ROLE_ARN | The role the broker assumes to mint scoped credentials |
BROKER_MAX_TTL_SECONDS | Hard cap on credential lifetime (default 900s) |
CLOUDTRAIL_ENABLED | Enables CloudTrail correlation (read-only cloudtrail:LookupEvents) |
With the broker disabled, a scoped_token decision still stands, but no
credential is issued and the call cannot proceed — fail-safe, never fail-open.