DEV Community

Cover image for How to secure AI agents with post-quantum signatures
German
German

Posted on • Edited on

How to secure AI agents with post-quantum signatures

Everyone is building AI agents. Most are not thinking about how to secure them.

An agent that sends emails, executes trades, controls infrastructure, or manages files is not just a chatbot. It's a system that takes real actions with real consequences. And when something goes wrong — a compromised agent, a replayed action, a forged instruction — the question isn't just "what happened?" It's "can you prove it, and can you stop it?"

This post covers three practical problems in agent security and how to solve them with ML-DSA-65 (NIST FIPS 204) signatures.


The problem with agent actions

When an agent executes an action, you need to answer three questions:

  1. Was this action actually authorized? Not just "did the agent do it" but "was the agent authorized to do it at this specific moment?"
  2. Can you invalidate it? If the agent is compromised mid-session, can you stop future actions without waiting for a token to expire?
  3. How do you verify identity at scale? When you have dozens of agents communicating with each other, how does Agent B know it's actually talking to Agent A and not an impersonator?

JWT with RS256 answers none of these well. It has no revocation. Its signatures are vulnerable to Shor's algorithm. And there's no concept of agent identity — a token proves a session, not an entity.


Part 1 — Sign every action

The simplest and most useful pattern: every action an agent takes gets a signed token before execution.

import { PQAuth } from 'fipsign-sdk'

const pq = new PQAuth(process.env.FIPSIGN_API_KEY)

// Agent is about to send an email
const { token } = await pq.sign({
  sub:              'agent_email_sender',
  action:           'send_email',
  to:               'user@example.com',
  subject:          'Your weekly report',
  authorizedBy:     'workflow_xyz',
  expiresInSeconds: 300,  // this action is valid for 5 minutes
})

// Execute the action — pass the token along
await emailService.send({ token, ...emailData })

// On the receiving side — verify before executing
const { valid, payload } = await pq.verify(token)
if (!valid) throw new Error('Unauthorized agent action')

console.log(payload.sub)        // 'agent_email_sender'
console.log(payload.action)     // 'send_email'
console.log(payload.authorizedBy) // 'workflow_xyz'
Enter fullscreen mode Exit fullscreen mode

Same pattern in Python:

from fipsign import PQAuth

pq = PQAuth(os.environ["FIPSIGN_API_KEY"])

result = pq.sign(
    "agent_email_sender",
    action="send_email",
    to="user@example.com",
    authorized_by="workflow_xyz",
    expires_in_seconds=300,
)
token = result.token

# Verify before executing
result = pq.verify(token)
if not result.valid:
    raise PermissionError("Unauthorized agent action")
Enter fullscreen mode Exit fullscreen mode

What you gain: every action has a cryptographic proof of authorization. The signature is ML-DSA-65 — no known quantum attack. The payload is tamper-proof. If someone intercepts and modifies action: "delete_files" from action: "send_email", verification fails.


Part 2 — Revoke immediately when an agent is compromised

This is where JWT breaks down completely.

With JWT, if an agent is compromised, you can't invalidate its tokens. You wait for expiry. For a token with a 1-hour TTL, that's up to 60 minutes of a compromised agent still passing authentication.

With FIPSign, revocation is immediate and permanent:

// Agent_X is compromised at 14:32
// Revoke its active token immediately
await pq.revoke(agentToken, 'agent compromised — security incident')

// Any subsequent verify() call returns valid: false
const { valid, error } = await pq.verify(agentToken)
console.log(valid)  // false
console.log(error)  // 'Token has been revoked'
Enter fullscreen mode Exit fullscreen mode

The revocation is backed by a blacklist in Cloudflare D1. Every remote verify() call checks it before returning a result. There's no TTL to wait out.

The failure mode @valentin_monteiro mentioned — agents failing in production — almost always involves a window of time where a compromised agent keeps operating because nothing invalidated its credentials. Revocation closes that window to zero.


Part 2.5 — Closing the detection gap

Revocation is immediate — but only from the moment you call pq.revoke(). The real problem is the window between when an agent is compromised and when someone detects it and triggers the revoke. That gap can be minutes, hours, or days depending on your setup.

Three mechanisms to close it:

1. Short TTL as a damage ceiling

TTL isn't a substitute for revocation — it's a limit on how much damage can happen if detection is slow. A compromised agent signing actions with a 5-minute TTL has at most 5 minutes of undetected exposure per token. Keep high-risk actions under 5 minutes. Keep low-risk actions under 60 minutes. Never use TTLs over a few hours for agent tokens.

This is already in the checklist above — but the reasoning matters: short TTL is your last line of defense when everything else fails.

2. Wire token.rejected to automatic revocation

FIPSign emits a token.rejected webhook every time a verification fails. A compromised agent that starts replaying tokens, sending malformed payloads, or operating outside its expected scope will generate rejections. You can wire that signal directly to an auto-revoke:

// Your webhook handler
app.post('/fipsign-webhook', verifyWebhookSignature, async (req) => {
  const { event, data } = req.body

  if (event === 'token.rejected') {
    const { reason, projectId } = data

    // A project generating repeated rejections is a signal worth acting on
    if (activeTokensByProject[projectId]) {
      await pq.revoke(activeTokensByProject[projectId], 'auto-revoked: anomaly detected')
      await alertSecurityTeam({ projectId, reason })
    }
  }
})
Enter fullscreen mode Exit fullscreen mode

This closes the detection gap to seconds — no human in the loop required. FIPSign provides the signal. Your system decides what to do with it.

3. Use limit.warning as an indirect signal

A compromised agent that starts spamming actions will consume tokens faster than expected. FIPSign emits a limit.warning webhook when usage reaches 80% of your monthly limit. It's not a security signal by design — but if you're nowhere near your normal consumption and limit.warning fires unexpectedly, something is wrong.

if (event === 'limit.warning') {
  // Unexpected spike — investigate
  await alertSecurityTeam({
    message: 'Unusual token consumption spike',
    freeRemaining: data.freeRemaining,
    month: data.month,
  })
}
Enter fullscreen mode Exit fullscreen mode

Not a substitute for proper anomaly detection — but it's a signal you already have for free.


The model: FIPSign provides the cryptographic primitives and the signals. Your system provides the context and the response logic. Neither layer can do the other's job — and they shouldn't try.


Part 3 — Agent identity with Private CA

For simple agents, signed action tokens are enough. But when you have persistent agents — a fleet of IoT devices, a set of microservices, a group of agents that communicate with each other — you need something more durable than a session token.

That's where a Private CA comes in.

The model: each agent gets an ML-DSA-65 certificate issued by your project's CA. The certificate contains the agent's public key, its identity, and its expiry. It's signed by the CA's private key — which never leaves the server.

import { PQAuth, generateKeyPair } from 'fipsign-sdk'

const pq = new PQAuth(process.env.FIPSIGN_API_KEY)

// At agent provisioning time: generate a key pair for the agent
const { publicKey, secretKey } = await generateKeyPair()
// secretKey stays on the agent — never transmitted

// Issue a certificate for this agent
const { certificate } = await pq.ca.issue({
  subject:          'agent-data-processor-001',
  publicKey,
  expiresInSeconds: 30 * 24 * 60 * 60,  // 30 days
  meta: {
    role:    'data_processor',
    team:    'analytics',
    version: '2.1.0',
  },
})

// Store certificate on the agent
// certificate.id, certificate.publicKey, certificate.signature, certificate.expiresAt
Enter fullscreen mode Exit fullscreen mode

Now when Agent B receives a message from Agent A, it can verify Agent A's identity offline:

import rootCert from './root-cert.json' assert { type: 'json' }

// No API call — purely local ML-DSA-65 verification
const result = pq.ca.verifyCert(agentACertificate, rootCert)

if (!result.valid) {
  console.error(result.error)  // 'Invalid certificate signature', 'CERT_EXPIRED', etc.
  return reject('Agent not authorized')
}

console.log(result.cert.subject)   // 'agent-data-processor-001'
console.log(result.cert.expiresAt) // Unix timestamp
Enter fullscreen mode Exit fullscreen mode

And if Agent A is decommissioned or compromised:

// Revoke the certificate immediately
await pq.ca.revokeCert(certificate.id, 'agent decommissioned')

// Check revocation before trusting
const { crl } = await pq.ca.getCrl()
if (pq.ca.isCertRevoked(agentACertificate, crl)) {
  return reject('Agent certificate revoked')
}
Enter fullscreen mode Exit fullscreen mode

The full security model for agents

Putting it together:

Concern Solution
Was this action authorized? Sign every action with /sign
Can I stop a compromised agent? Revoke with /revoke — immediate, permanent
How do agents identify themselves? Private CA — one certificate per agent
How do agents verify each other? verifyCert() offline — no API call needed
Is the agent's identity still valid? Check CRL with getCrl() + isCertRevoked()
Are signatures quantum-resistant? ML-DSA-65 — NIST FIPS 204

Security checklist for agents

Before deploying an agent that takes real actions:

  • [ ] Every action produces a signed token before execution
  • [ ] Token expiry is short — 5 minutes maximum for high-risk actions
  • [ ] Revocation is wired in — if the agent is compromised, you can stop it in seconds
  • [ ] Persistent agents have certificates, not just session tokens
  • [ ] Agents verify each other's certificates before trusting inter-agent messages
  • [ ] CRL is checked periodically — don't rely on certificate expiry alone
  • [ ] Signatures use ML-DSA-65 — not RS256, not ES256

Why post-quantum matters for agents specifically

Most of the "harvest now, decrypt later" discussion focuses on data encryption. But signatures are equally at risk.

An attacker recording your agents' signed actions today can — once quantum computers are available — forge signatures for actions that never happened. That means fabricated audit trails, forged authorizations, and retroactive tampering with your agent's history.

ML-DSA-65 has no known quantum attack. Migrating now, while your agent system is being built, costs one install command. Migrating later, after you've deployed thousands of agents with RS256-signed credentials, costs months.


Get started

npm install fipsign-sdk
# or
pip install fipsign-sdk
Enter fullscreen mode Exit fullscreen mode

Free tier: 10,000 tokens/month, no credit card. Private CA included — create one from the dashboard in seconds.

fipsign.dev · Developer guide


If you're building agents and hitting security questions I didn't cover — drop them in the comments. Happy to go deeper on specific failure modes.

Top comments (0)