Skip to content

Security: ThirdKeyAI/AgentPin

Security

docs/security.md

Security Best Practices

This guide covers the security model, threat mitigations, and operational best practices for AgentPin deployments.


Cryptographic Foundation

AgentPin uses ES256 (ECDSA with P-256) exclusively. All other algorithms are rejected.

PropertyValue
Signing AlgorithmECDSA (Elliptic Curve Digital Signature Algorithm)
CurveP-256 (secp256r1)
HashSHA-256
Key FormatJWK (RFC 7517) for public keys, PEM for private keys
Credential FormatJWT (compact serialization)
Signature EncodingDER-encoded (cross-language compatible)

Why ES256 Only

Single-algorithm enforcement prevents:

  • Algorithm confusion attacks — Attacker substitutes none or HS256 to bypass verification
  • Downgrade attacks — Attacker forces weaker algorithm selection
  • Implementation complexity — Fewer code paths means fewer bugs

All three implementations (Rust, JavaScript, Python) validate alg: "ES256" before any other processing.


Key Management

Private Key Security

  • Never commit private keys to version control
  • Never embed private keys in application code or environment variables in plaintext
  • Store private keys in secure storage: file system with restricted permissions, HSM, or secrets manager
  • Use separate keys for separate environments (dev, staging, production)
# Set restrictive permissions on private key files
chmod 600 ./keys/*.private.pem
# Verify permissions
ls -la ./keys/*.private.pem
# -rw------- 1 agentpin agentpin 227 Feb 15 2026 example-2026-01.private.pem

Key Rotation

Rotate keys regularly. Recommended schedule:

Key TypeRotation Period
Production signing keysEvery 6-12 months
Development/testing keysEvery 3 months
Keys after suspected compromiseImmediately

Rotation procedure:

  1. Generate new key pair
  2. Add new public key to discovery document
  3. Reduce discovery document cache TTL during transition
  4. Begin issuing credentials with new key
  5. After grace period, revoke old key
  6. Remove old key from discovery document

Key ID Naming Convention

Use descriptive, time-stamped key IDs for traceability:

{domain}-{year}-{sequence}
Examples:
example-2026-01 # First key for 2026
example-2026-02 # Second key (after rotation)
staging-2026-01 # Staging environment key

Credential Security

Short-Lived Credentials

Issue credentials with the shortest practical TTL:

Use CaseRecommended TTL
Single API call300 seconds (5 min)
Session3600 seconds (1 hour)
Long-running task14400 seconds (4 hours)
Maximum allowed86400 seconds (24 hours)
# Prefer short TTLscredential=issue_credential(
private_key_pem=key,
kid="example-2026-01",
issuer="example.com",
agent_id="urn:agentpin:example.com:agent",
audience="verifier.com",
capabilities=[Capability.create("read", "data")],
ttl_secs=3600, # 1 hour — not 86400
)

Audience Binding

Always specify an audience (aud claim) to prevent credential replay at unintended verifiers:

constcredential=issueCredential(privateKey,kid,'example.com','urn:agentpin:example.com:agent','verifier.com',// Bind to specific verifiercapabilities,null,null,3600,);

Verifiers should check the audience:

constresult=verifyCredentialOffline(credential,discovery,null,pinStore,'verifier.com',// Reject if aud doesn't match);

Capability Scoping

Follow the principle of least privilege — issue credentials with only the capabilities needed:

# Good: Minimal capabilities for the taskcredential=issue_credential(
...,
capabilities=[Capability.create("read", "reports")],
ttl_secs=300,
)
# Bad: Overly broad capabilitiescredential=issue_credential(
...,
capabilities=[Capability.create("read", "*"), Capability.create("write", "*")],
ttl_secs=86400,
)

TOFU Key Pinning

Trust-On-First-Use pinning protects against key substitution attacks after initial verification.

How It Works

  1. First verification for a domain: public key fingerprint (JWK thumbprint per RFC 7638) is stored
  2. Subsequent verifications: key must match the stored fingerprint
  3. Key change detected: verification fails with key_changed error

Pin Store Persistence

Always persist the pin store between sessions:

importosfromagentpinimportKeyPinStorePIN_FILE="/var/lib/agentpin/pins.json"# Load existing pinsifos.path.exists(PIN_FILE):
pin_store=KeyPinStore.from_json(open(PIN_FILE).read())
else:
pin_store=KeyPinStore()
# Use for verificationresult=verify_credential_offline(jwt, discovery, None, pin_store, audience)
# Save updated pinswithopen(PIN_FILE, "w") asf:
f.write(pin_store.to_json())

Handling Key Changes

A key_changed result requires investigation:

ifresult.key_pinning=="changed":
# This is a security event — do not silently acceptlog_security_event(
event="key_changed",
domain=result.issuer,
action="verification_rejected",
)
# Require manual approval before accepting the new key

Legitimate key changes (rotation) should be communicated out-of-band. Update the pin store explicitly:

constpinStore=KeyPinStore.fromJson(existingPins);// After confirming legitimate rotation:pinStore.addKey(domain,newJwk);

Threat Model

Agent Impersonation

Threat: A malicious agent claims to be a trusted agent.

Mitigation: Cryptographic verification — the agent must present a JWT signed by a key declared in the issuer's discovery document. Without the private key, impersonation is impossible.

Unauthorized Delegation

Threat: An agent claims authorization from an operator who never granted it.

Mitigation: Delegation chains require a maker_attestation — a cryptographic signature from the Maker. The Deployer cannot forge this without the Maker's private key.

Capability Inflation

Threat: An agent claims capabilities beyond what it was authorized.

Mitigation: Capability validation (Step 10) checks that credential capabilities are a subset of capabilities declared in the discovery document. Delegation chains enforce capability narrowing at each level.

Discovery Document Tampering

Threat: An attacker modifies the discovery document in transit or at the server.

Mitigation:

  • HTTPS provides transport-layer integrity
  • TOFU pinning detects key changes after initial verification
  • Redirect rejection prevents redirect-based attacks

Replay Attacks

Threat: An attacker captures and replays a valid credential.

Mitigation:

  • Short-lived credentials minimize the replay window
  • Audience binding prevents replay at unintended verifiers
  • JTI (JWT ID) enables one-time-use verification
  • Nonce-based mutual authentication for real-time verification

Key Compromise

Threat: An attacker obtains a private key.

Mitigation:

  • Revocation at credential, agent, and key levels
  • Short credential TTLs limit exposure window
  • TOFU pinning detects key substitution at verifiers that already pinned the legitimate key

Revocation

Three Levels of Revocation

LevelScopeUse Case
CredentialSingle JWT (by jti)Compromised individual credential
AgentAll credentials for an agentAgent decommissioned or compromised
KeyAll credentials signed by a keyKey compromised

Revocation Document

Publish at /.well-known/agent-identity-revocations.json with a short cache TTL (5 minutes):

{
"agentpin_version": "0.1",
"entity": "example.com",
"revoked_credentials": [
{ "id": "jti-123", "reason": "key_compromise", "revoked_at": "2026-02-15T00:00:00Z" }
],
"revoked_agents": [],
"revoked_keys": [
{ "id": "example-2025-01", "reason": "key_compromise", "revoked_at": "2026-02-15T00:00:00Z" }
],
"updated_at": "2026-02-15T00:00:00Z"
}

Revocation Checking

Verifiers MUST check revocation on every verification, regardless of discovery document cache state.


Network Security

HTTPS Requirements

  • Discovery documents MUST be served over HTTPS
  • TLS certificates MUST be valid (not expired, not self-signed in production)
  • HTTP redirects MUST NOT be followed during discovery document fetching

No-Redirect Policy

AgentPin rejects HTTP redirects to prevent:

  • Redirect to attacker-controlled server serving malicious discovery document
  • Redirect loops causing denial of service
  • Open redirect exploitation
// Correct: reject redirectsconstresponse=awaitfetch(url,{redirect: 'error'});// Incorrect: following redirectsconstresponse=awaitfetch(url,{redirect: 'follow'});// DO NOT DO THIS

Rate Limiting

Protect discovery endpoints from abuse:

# nginx rate limiting for AgentPin endpointslimit_req_zone$binary_remote_addrzone=agentpin:10m rate=10r/s;location /.well-known/agent-identity.json {limit_reqzone=agentpin burst=20 nodelay; # ... other config}

Cross-Language Interoperability

All three AgentPin implementations (Rust, JavaScript, Python) produce interoperable credentials:

  • All use DER-encoded ECDSA signatures
  • All use identical JSON field names
  • A credential issued by one language can be verified by any other

Verify cross-language compatibility in your test suite:

# Issue with Python, verify with JavaScript
python -c "from agentpin import ...; print(issue_credential(...))"| \
node -e "const { verifyCredentialOffline } = require('agentpin'); ..."

There aren't any published security advisories