Skip to main content

ACI Security Hardening Specification

Cryptographic and Runtime Security Controls for AI Agents Version: 1.0.0 Status: Draft Last Updated: January 24, 2026


Abstract

This specification defines mandatory and recommended security controls for ACI-compliant systems. It addresses cryptographic token security, hardware attestation, privacy-preserving identity, and revocation enforcement. These controls harden the ACI architecture against token theft, impersonation, correlation attacks, and revocation bypass.


1. Introduction

1.1 Motivation

The base ACI specification establishes identity and capability encoding for AI agents. However, deploying AI agents in production environments requires additional security controls:

  1. Token Theft: Stolen tokens can be replayed by attackers
  2. Code Integrity: Certified agents may be swapped with malicious code
  3. Correlation Attacks: Reused identifiers enable cross-service tracking
  4. Revocation Latency: Compromised agents execute transactions before revocation propagates

1.2 Scope

This specification covers:

  • Sender-constrained tokens (DPoP)
  • Hardware-bound keys (TEE attestation)
  • Pairwise identity requirements
  • Revocation enforcement SLAs
  • Delegation chain security

1.3 Conformance Levels

LevelNameRequirements
SH-1BasicDPoP, short-lived tokens
SH-2StandardSH-1 + pairwise DIDs, recursive revocation
SH-3HardenedSH-2 + TEE binding, sync revocation checks

Trust tier mapping:

Trust TierMinimum Conformance
T0-T1None (not recommended)
T2SH-1
T3SH-2
T4-T5SH-3

2. Sender-Constrained Tokens (DPoP)

2.1 Requirement

All ACI Access Tokens MUST be bound to a proof-of-possession key using DPoP (RFC 9449).

2.2 Rationale

Traditional bearer tokens can be stolen and replayed. For AI agents that may have their storage compromised (via side-channel attacks, memory dumps, or host compromise), sender-constrained tokens ensure stolen tokens are unusable without the private key.

2.3 Implementation

2.3.1 Token Request

POST /token HTTP/1.1
Host: auth.agentanchor.io
Content-Type: application/x-www-form-urlencoded
DPoP: <dpop_proof>

grant_type=authorization_code&
code=<auth_code>&
client_id=<agent_client_id>&
redirect_uri=<redirect_uri>

2.3.2 DPoP Proof Structure

{
"typ": "dpop+jwt",
"alg": "ES256",
"jwk": {
"kty": "EC",
"crv": "P-256",
"x": "...",
"y": "..."
}
}
.
{
"jti": "unique-token-id",
"htm": "POST",
"htu": "https://auth.agentanchor.io/token",
"iat": 1706097600,
"ath": "sha256-hash-of-access-token"
}

2.3.3 Token Response

The access token includes a confirmation claim binding it to the DPoP key:

{
"access_token": "eyJ...",
"token_type": "DPoP",
"expires_in": 300,
"cnf": {
"jkt": "sha256-thumbprint-of-dpop-key"
}
}

2.3.4 Resource Access

GET /api/resource HTTP/1.1
Host: api.example.com
Authorization: DPoP <access_token>
DPoP: <fresh_dpop_proof>

2.4 Key Requirements

RequirementSpecification
AlgorithmES256 (P-256) REQUIRED; ES384, ES512 RECOMMENDED
Key StorageHardware-backed RECOMMENDED (TPM, Secure Enclave)
Proof LifetimeMaximum 60 seconds
JTI UniquenessMUST be unique; servers MUST reject replays
Clock SkewMaximum 5 seconds tolerance

2.5 Replay Prevention

Authorization servers and resource servers MUST maintain a JTI cache:

interface JTICache {
// Store JTI with expiration
store(jti: string, expiresAt: Date): Promise<void>;

// Check if JTI has been seen
exists(jti: string): Promise<boolean>;
}

// Reject if JTI exists
if (await jtiCache.exists(proof.jti)) {
throw new Error('DPoP proof replay detected');
}

3. Trusted Execution Environment (TEE) Binding

3.1 Requirement

For Trust Tier T4 and above, agent DID keys MUST be bound to a Trusted Execution Environment with remote attestation.

3.2 Rationale

Verifiable Credentials attest to what was certified at issuance time. They cannot guarantee that the certified code is actually running. An attacker could obtain valid credentials for "Safe-Agent-v1" but execute "Malicious-Agent-v9".

TEE binding solves this by:

  1. Generating keys inside the secure enclave
  2. Binding the DID to the enclave's measurement
  3. Proving at runtime that the expected code is executing

3.3 Supported Platforms

PlatformAttestation TypeUse Case
Intel SGXDCAP/EPIDCloud workloads
AWS Nitro EnclavesNitro AttestationAWS deployments
AMD SEV-SNPSNP AttestationAMD cloud instances
ARM TrustZonePlatform-specificEdge/mobile agents
Apple Secure EnclaveDeviceCheckApple platform agents

3.4 Attestation Flow

+---------------+     +---------------+     +---------------+
| Agent | | Attestation | | Relying |
| (in TEE) | | Service | | Party |
+-------+-------+ +-------+-------+ +-------+-------+
| | |
| 1. Generate key | |
| inside enclave | |
| | |
| 2. Request | |
| attestation ---->| |
| | |
| 3. Attestation | |
| report <---------| |
| | |
| 4. Present DID + attestation ------------>|
| | |
| | 5. Verify |
| | attestation <----|
| | |
| | 6. Confirm -------->|
| | |

3.5 DID Document with TEE Binding

{
"@context": [
"https://www.w3.org/ns/did/v1",
"https://aci.agentanchor.io/ns/aci/v1",
"https://aci.agentanchor.io/ns/tee/v1"
],
"id": "did:aci:a3i:vorion:banquet-advisor",

"verificationMethod": [
{
"id": "did:aci:a3i:vorion:banquet-advisor#tee-key-1",
"type": "JsonWebKey2020",
"controller": "did:aci:a3i:vorion:banquet-advisor",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-256",
"x": "...",
"y": "..."
}
}
],

"aciTeeBinding": {
"platform": "nitro",
"enclaveId": "enclave-abc123",
"pcrs": {
"PCR0": "sha384:...",
"PCR1": "sha384:...",
"PCR2": "sha384:..."
},
"attestationEndpoint": "https://attestation.agentanchor.io/nitro",
"keyBinding": {
"verificationMethodId": "#tee-key-1",
"bindingProof": "base64-attestation-document"
},
"validUntil": "2026-02-24T00:00:00Z"
}
}

3.6 Runtime Attestation Verification

Relying parties MUST verify TEE attestation for T4+ agents:

async function verifyTeeBinding(
agent: AgentIdentity,
attestationService: AttestationService
): Promise<TeeVerificationResult> {
const binding = agent.didDocument.aciTeeBinding;

// 1. Verify attestation document signature
const attestationValid = await attestationService.verify(
binding.platform,
binding.keyBinding.bindingProof
);

if (!attestationValid) {
return { valid: false, reason: 'Invalid attestation signature' };
}

// 2. Verify PCR measurements match expected values
const expectedPcrs = await getExpectedPcrs(agent.aci);
for (const [pcr, value] of Object.entries(binding.pcrs)) {
if (expectedPcrs[pcr] !== value) {
return { valid: false, reason: `PCR mismatch: ${pcr}` };
}
}

// 3. Verify key is bound to enclave
const keyBound = await verifyKeyBinding(
binding.keyBinding,
agent.didDocument.verificationMethod
);

if (!keyBound) {
return { valid: false, reason: 'Key not bound to enclave' };
}

// 4. Check attestation freshness
if (new Date(binding.validUntil) < new Date()) {
return { valid: false, reason: 'Attestation expired' };
}

return { valid: true };
}

4. Pairwise Identity Requirements

4.1 Requirement

Agents interacting with services that handle non-public data MUST use pairwise (relationship-specific) DIDs.

4.2 Rationale

Reusing the same DID across services creates a "correlation super-cookie." If Agent A uses the same DID with an airline and a hospital, those services can collude to build a comprehensive profile.

4.3 Data Classification

Data TypePairwise RequiredExample
PublicNoWeather queries, public APIs
BusinessRecommendedB2B transactions
PersonalYesUser preferences, history
SensitiveYesFinancial, health, location
RegulatedYes + AuditPII under GDPR, PHI under HIPAA

4.4 Pairwise DID Derivation

interface PairwiseDerivation {
masterDid: string; // Agent's root DID
relierPartyDid: string; // Service's DID
contextSalt: string; // Random salt per relationship
derivedDid: string; // Unique DID for this relationship
}

function derivePairwiseDid(
masterDid: string,
relierPartyDid: string,
salt: string
): string {
// HKDF derivation
const ikm = Buffer.from(`${masterDid}:${relierPartyDid}`);
const info = Buffer.from('aci-pairwise-did-v1');
const derivedKey = hkdf('sha256', ikm, salt, info, 32);

// Generate DID from derived key
const keyPair = generateKeyPairFromSeed(derivedKey);
return `did:key:${multibaseEncode(keyPair.publicKey)}`;
}

4.5 Pairwise DID Registry

Agents MUST maintain a secure mapping of pairwise DIDs:

interface PairwiseRegistry {
// Store pairwise relationship
store(
masterDid: string,
relierPartyDid: string,
pairwiseDid: string,
salt: string
): Promise<void>;

// Retrieve pairwise DID for relationship
get(
masterDid: string,
relierPartyDid: string
): Promise<PairwiseDerivation | null>;

// List all relationships (for audit)
list(masterDid: string): Promise<PairwiseDerivation[]>;

// Revoke specific relationship
revoke(pairwiseDid: string): Promise<void>;
}

4.6 Negotiation Protocol

During the ACI handshake, agents negotiate pairwise DID usage:

// Agent -> Service (initial contact)
{
"type": "aci-handshake-request",
"from": "did:aci:a3i:vorion:agent-master",
"to": "did:web:api.example.com",
"pairwiseSupport": true,
"dataClassification": "sensitive"
}

// Service -> Agent (response)
{
"type": "aci-handshake-response",
"requirePairwise": true,
"serviceDid": "did:web:api.example.com",
"nonce": "random-nonce-for-derivation"
}

// Agent -> Service (pairwise DID)
{
"type": "aci-handshake-complete",
"pairwiseDid": "did:key:z6Mk...",
"proof": {
"type": "Ed25519Signature2020",
"verificationMethod": "did:aci:a3i:vorion:agent-master#key-1",
"proofValue": "..."
}
}

5. Revocation Enforcement

5.1 Requirement

ACI systems MUST enforce revocation within defined SLA windows based on trust tier and operation criticality.

5.2 Revocation SLAs

Trust TierMax PropagationSync Check Required
T0-T160 secondsNo
T230 secondsFor L3+ operations
T310 secondsFor L3+ operations
T4-T51 secondAlways

5.3 Operation Criticality

Regardless of trust tier, certain operations require synchronous revocation checks:

Operation TypeSync Check Required
Financial transactionAlways
PII accessAlways
External API callAlways
Data exportAlways
Privilege escalationAlways
Delegation creationAlways

5.4 Revocation Check Implementation

async function checkRevocation(
agent: AgentIdentity,
operation: Operation
): Promise<RevocationStatus> {
const requiresSync =
agent.trustTier >= TrustTier.T4 ||
isCriticalOperation(operation);

if (requiresSync) {
// Synchronous check - call registry
return await registry.checkRevocationSync(agent.did);
} else {
// Cached check with SLA-based TTL
const ttl = getRevocationCacheTTL(agent.trustTier);
return await cache.getOrFetch(
`revocation:${agent.did}`,
() => registry.checkRevocationAsync(agent.did),
ttl
);
}
}

function getRevocationCacheTTL(tier: TrustTier): number {
switch (tier) {
case TrustTier.T0:
case TrustTier.T1: return 60000; // 60 seconds
case TrustTier.T2: return 30000; // 30 seconds
case TrustTier.T3: return 10000; // 10 seconds
default: return 0; // No caching for T4+
}
}

5.5 Recursive Revocation

When an agent in a delegation chain is revoked, all downstream capabilities MUST be invalidated:

async function revokeRecursive(
agentDid: string,
reason: string
): Promise<RecursiveRevocationResult> {
// 1. Revoke the agent
await registry.revoke(agentDid, reason);

// 2. Find all delegations from this agent
const delegations = await registry.getDelegationsFrom(agentDid);

// 3. Recursively revoke each delegate
const revokedDescendants: string[] = [];
for (const delegation of delegations) {
const result = await revokeRecursive(
delegation.delegateDid,
`Parent revoked: ${agentDid}`
);
revokedDescendants.push(delegation.delegateDid);
revokedDescendants.push(...result.revokedDescendants);
}

// 4. Invalidate all active tokens
const tokensInvalidated = await tokenService.invalidateForAgent(agentDid);

// 5. Notify webhooks
await webhooks.notify('revocation', {
agentDid,
reason,
revokedDescendants,
tokensInvalidated
});

return { agentDid, revokedDescendants, tokensInvalidated };
}

6. Delegation Chain Security

6.1 Chain Validation

Relying parties MUST validate the entire delegation chain:

async function validateDelegationChain(
token: ACIToken
): Promise<ChainValidationResult> {
const chain = token.claims.delegation_chain;

for (let i = 0; i < chain.length; i++) {
const link = chain[i];

// 1. Verify issuer signature
const signatureValid = await verifySignature(link);
if (!signatureValid) {
return { valid: false, failedAt: i, reason: 'Invalid signature' };
}

// 2. Check issuer not revoked
const revoked = await checkRevocation(link.issuer);
if (revoked) {
return { valid: false, failedAt: i, reason: 'Issuer revoked' };
}

// 3. Verify scope reduction (monotonic)
if (i > 0) {
const parentScope = chain[i - 1].scope;
if (!isScopeSubset(link.scope, parentScope)) {
return { valid: false, failedAt: i, reason: 'Scope escalation' };
}
}

// 4. Check chain depth limit
if (i >= MAX_CHAIN_DEPTH) {
return { valid: false, failedAt: i, reason: 'Chain too deep' };
}
}

return { valid: true };
}

6.2 Chain Depth Limits

Trust TierMax Chain Depth
T0-T11 (no delegation)
T22
T33
T4-T55

6.3 Scope Reduction Enforcement

Each delegation MUST reduce or maintain (never expand) capabilities:

function isScopeSubset(child: Scope, parent: Scope): boolean {
// Domain check: child domains must be subset of parent
const childDomains = new Set(child.domains);
const parentDomains = new Set(parent.domains);
for (const domain of childDomains) {
if (!parentDomains.has(domain)) return false;
}

// Level check: child level must be <= parent
if (child.level > parent.level) return false;

// Trust check: child trust must be <= parent
if (child.trustTier > parent.trustTier) return false;

return true;
}

7. Security Levels Summary

SH-1: Basic Security

  • DPoP for all tokens
  • Access token lifetime <= 5 minutes
  • JTI replay prevention
  • ES256 minimum algorithm

SH-2: Standard Security

  • All SH-1 requirements
  • Pairwise DIDs for sensitive data
  • Recursive revocation support
  • Delegation chain validation
  • Chain depth limits

SH-3: Hardened Security

  • All SH-2 requirements
  • TEE-bound keys
  • Remote attestation verification
  • Synchronous revocation checks
  • Hardware-backed key storage

8. References


Specification authored by AgentAnchor (A3I) License: Apache 2.0