Dev.to Security πŸ” Cybersecurity πŸ‘ 1 πŸ“– 3 min read

Logs are claims. Receipts are proofs.

Your AI agent just refunded a customer, deleted a database table, or emailed a spreadsheet to the wrong person. Three weeks later, someone asks what happened. What do you have? A log line. Who wrote it? Your own code

Your AI agent just refunded a customer, deleted a database table, or emailed a spreadsheet to the wrong person. Three weeks later, someone asks what happened.

What do you have?

A log line.

Who wrote it?

Your own code.

Could it have been edited?

Absolutely.

That is the state of most AI audit trails today, and they're only as trustworthy as whoever controls the logs.

Payment systems solved this decades ago. You don't get an assertion that money movedβ€”you get a receipt that anyone can verify.

This post shows how to give every LLM response a cryptographically verifiable receipt, what that receipt actually proves, and just as importantly, what it doesn't.

Everything below ships in an MIT-licensed gateway you can run with a single command. You don't even need an API key:

npx @axiorank/gateway demo

What an AI response receipt needs to contain

A useful receipt should commit to at least four things:

  1. The exact request bytes received by the gateway (stored as a SHA-256 hash)
  2. The exact response bytes returned (also stored as a SHA-256 hash)
  3. Everything the gateway did in between:
    • routing decisions
    • selected provider
    • guardrail verdicts
    • redactions applied
    • token usage
  4. Its position in history so previous records cannot be silently modified.

A real receipt (trimmed) looks like this:

{
  "kind": "axiorank-gateway-receipt-v1",
  "request": {
    "modelRequested": "gpt-4o-mini",
    "bodySha256": "..."
  },
  "route": {
    "alias": "axio/auto",
    "strategy": "cost",
    "served": {
      "upstream": "groq",
      "model": "llama-3.3-70b-versatile"
    }
  },
  "guardrails": {
    "prompt": {
      "decision": "allow",
      "redactions": 0
    },
    "completion": {
      "decision": "allow",
      "redactions": 1
    }
  },
  "response": {
    "status": 200,
    "bodySha256": "...",
    "inputTokens": 45,
    "outputTokens": 118
  },
  "chain": {
    "seq": 42,
    "prevReceiptHash": "..."
  },
  "keyId": "a1b2c3...",
  "algorithm": "EdDSA",
  "signature": "..."
}

Why canonicalization matters

Signing JSON is harder than it sounds.

JSON doesn't have a single canonical byte representation. Different serializers can reorder keys, change whitespace, or format numbers differently. Even though the data is identical, the bytes change, which invalidates signatures.

The solution is RFC 8785 (JSON Canonicalization Scheme), which defines one deterministic serialization that every verifier can reproduce.

The receipt payload is:

  1. Canonicalized using RFC 8785
  2. Signed using Ed25519
  3. Stored with a detached signature

Verification only requires standard cryptography librariesβ€”you never have to trust the gateway's own implementation.

Turning receipts into an audit trail

A signed receipt proves one response.

An audit trail has to prove something more important:

  • nothing was deleted
  • nothing was reordered
  • nothing was modified

Every receipt includes:

  • a sequence number
  • the hash of the previous receipt

Together they form a hash chain stored as JSONL.

If any historical byte changes, every receipt after it fails verification.

npx @axiorank/gateway verify ~/.axiorank/gateway/receipts.jsonl

# receipt chain valid (128 receipts, key a1b2c3d4)

The signing key is generated locally on first run and published through a local JWKS endpoint.

No account.

No cloud service.

No central authority.

What receipts do not prove

It's important to be precise.

A receipt does not prove anything about what happened inside the model itself.

It only proves what crossed the gateway boundary.

It also doesn't prove the gateway operator is honest.

If the operator is your adversary, you need an external transparency log or witness service (that's what the hosted version provides).

The local receipt chain provides tamper evidence.

If someone edits history, the chain simply stops verifying.

Blocked requests also generate receipts, turning "our filters caught it" into something that can actually be verified later.

Guardrails belong at the gateway

Since every model request already passes through the gateway, it's the natural place for baseline security.

The gateway can:

  • block prompt injection
  • block destructive commands
  • redact API keys
  • redact PII
  • score requests and responses
  • include every decision in the signed receipt

Everything runs in-process.

No second network request.

Enabled by default.

The guardrails are heuristic-based with scored confidence bandsβ€”not a silver bulletβ€”and benchmark results against open-source alternatives at a fixed false-positive rate are published.

Try it

# OpenAI-compatible gateway on :8787
npx @axiorank/gateway

# Full offline demo (~15 seconds)
npx @axiorank/gateway demo

The project is MIT licensed with zero runtime dependencies and includes:

  • 34 provider presets
  • LangChain examples
  • LiteLLM examples
  • Vercel AI SDK examples
  • Ollama examples
  • CrewAI examples
  • CI integration examples

GitHub:

https://github.com/AxioRank/gateway

πŸ“° Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.