Technical Architecture
Zero-trust authorization, engineered for machine-speed decisions.
Sub-millisecond authorization with cryptographic proof, designed from first principles for the AI-agent era. Every decision is a signed artifact — not a logged event, not a token claim, but a cryptographically verifiable permit.
Authorization Pipeline
Every decision traverses a verifiable pipeline.
The hot path is designed for latency, not flexibility. Policy loading, key rotation, and tenant configuration happen on the control plane; the data plane executes a fixed pipeline with no round-trips to external systems.
Constructs permit payload, signs with tenant key, attaches nonce + TTL.
Verifies Ed25519 signature, checks nonce replay cache, validates TTL window.
Evaluates compiled decision tree against permit fields, rate limits, and conditions.
Appends decision leaf to append-only Merkle tree. Async — does not block response.
Returns permit + gateway signature. Any party can verify offline using public key.
Constructs permit payload, signs with tenant key, attaches nonce + TTL.
Verifies Ed25519 signature, checks nonce replay cache, validates TTL window.
Evaluates compiled decision tree against permit fields, rate limits, and conditions.
Appends decision leaf to append-only Merkle tree. Async — does not block response.
Returns permit + gateway signature. Any party can verify offline using public key.
Cumulative latency budget
Cryptographic Signing
Ed25519 signing flow.
Permits are cryptographically bound to the issuing agent identity. A permit without a valid signature is rejected before touching the policy engine — no database lookups, no network calls.
Key Hierarchy
Root Key (HSM)
└─ Tenant Key
└─ Agent Signing Key (Ed25519, rotated daily)
└─ Permit SignatureRoot key lives exclusively in an HSM (AWS CloudHSM or on-prem PKCS#11). Never exported, never touches application memory.
Tenant keys are derived per-tenant via HKDF-SHA256 and sealed in the HSM.
Agent signing keys are Ed25519 ephemeral keys rotated on a 24h schedule. Rotation is zero-downtime — the public key is published to the gateway before the old key expires.
Sign + Verify Flow
// Agent side — permit construction
const permit = {
agent: "billing-ai",
action: "payment.create",
resource: "stripe:customer_xyz",
amount: 2450_00, // cents
nonce: randomBytes(16), // prevents replay
issued_at: Date.now(),
expires_at: Date.now() + 30_000 // 30-second TTL
};
// Canonical JSON ensures byte-identical serialization
const signature = ed25519.sign(
canonicalize(permit), // RFC 8785 JCS
tenantSigningKey // 32-byte private key
);
// ─── Gateway side ───────────────────────────────────
const valid = ed25519.verify(
signature,
canonicalize(permit),
tenantPublicKey // distributed via control plane
);
if (!valid) reject(401, "invalid_signature");
if (Date.now() > permit.expires_at) reject(401, "permit_expired");
if (nonceSeen(permit.nonce)) reject(401, "replay_detected");Why Ed25519
Deterministic signatures
Same input always produces the same signature — simplifies testing and auditability. No k-nonce, no PRNG dependency.
~100µs sign time
On commodity x86-64 hardware (single core). 10× faster than ECDSA P-256, 100× faster than RSA-2048 at equivalent security.
64-byte signatures
Versus 256+ bytes for RSA-2048. Matters at wire level: 20K req/s × 200 bytes saved = ~4 MB/s less bandwidth per gateway.
No entropy at sign time
Eliminates a class of entropy exhaustion bugs common in containerized runtimes with /dev/urandom pressure.
Post-quantum roadmap
Migration path to SLH-DSA (FIPS 205, stateless hash-based) planned. Signature format includes algorithm field for negotiated upgrade.
Audit Log
Merkle-tree audit log — tamper-evident by construction.
Every authorization decision appends a leaf to an append-only Merkle tree. The tree root is published hourly as a signed checkpoint. Enterprise customers can anchor roots to Bitcoin or Ethereum for third-party verifiability.
8-leaf tree · proof path for L5★ highlighted in orange
Proof size
O(log n)
3 hashes for 8 leaves; 20 for 1M decisions
Verification
O(log n)
Hash operations only, no DB query
Anchoring interval
1 hour
Configurable; enterprise can go 1-min
On-chain anchoring
Optional
Bitcoin OP_RETURN or Ethereum calldata
Inclusion proof verification
function verifyInclusion(
leaf: Hash,
proof: Hash[],
root: Hash,
index: number
): boolean {
let current = leaf;
for (const sibling of proof) {
current = (index & 1)
? sha256(concat(sibling, current)) // sibling is left child
: sha256(concat(current, sibling)); // sibling is right child
index >>= 1;
}
return current === root;
}
// Example: prove leaf[5] exists in a tree of 8 leaves
// proof = [leaf[4], H(leaf[6]||leaf[7]), H(H01 || H23)]
// 3 hashes suffice for O(log 8) proofTamper detection
Modifying any leaf changes its hash. That changes its parent hash, which changes the grandparent, all the way to the root. Since roots are published and externally anchored, retroactive tampering is detectable even without access to the original leaf data.
Leaf schema
{
decision_id: "01HXXXXX...", // ULID
permit_hash: "sha256(...)",
outcome: "allow" | "deny" | "review",
policy_id: "billing-spend-limit",
timestamp: 1713600000000,
agent: "billing-ai",
gateway_id: "gw-us-east-1a"
}Policy Engine
Policies compile to a bytecode VM — not interpreted at runtime.
Treating policy evaluation as an interpreter problem is why most authz systems top out at ~5ms. We compile policy JSON to a flat instruction set at deploy time. The hot path executes a bytecode VM — no JSON parsing, no tree traversal.
Policy document (JSON)
{
"id": "billing-agent-spending-limit",
"version": 3,
"match": {
"agent.role": "billing",
"action": "payment.create"
},
"rules": [
{
"condition": "amount <= 5000_00",
"effect": "allow"
},
{
"condition": "amount <= 50000_00 AND business_hours",
"effect": "review"
},
{
"condition": "default",
"effect": "deny"
}
],
"rate_limits": {
"per_minute": 10,
"per_hour": 100,
"burst": 15
}
}Condition types
Amount-based
amount <= NTime-based
business_hours, cron()Rate-based
per_minute, per_hourRelational
agent.org == resource.orgComposite
AND, OR, NOTCustom fn
call(fn_id, ...args)Execution pipeline
- 01
Compile on deploy
Policy JSON → AST → flat bytecode at policy publish time. Compilation errors surface before any request is served.
- 02
Bundle to gateway
Compiled bytecode is signed (Ed25519), versioned, and pushed to all edge gateways. Gateways validate the bundle signature before loading.
- 03
VM execution (~50µs)
Written in Rust, exposed via FFI. The VM is a simple stack machine: LOAD, CMP, AND, OR, JMP, EFFECT. No heap allocation in the hot path.
- 04
Conflict resolution
When multiple policies match, the most specific match wins (longest-prefix on match fields). Ties resolved by policy priority field.
Performance target
Policy evaluation budget: 40µs target, 60µs current (beta). Bottleneck is the FFI boundary between Rust VM and Node.js worker — replacing with Wasm module is on the roadmap.
Deployment Architecture
Data plane lives in your VPC. Control plane lives in ours.
The data plane executes authorization decisions. It is stateless, horizontally scalable, and has zero runtime dependencies on the control plane. If PermitNetworks goes down, your decisions keep working.
Data Plane — your infrastructure
Edge Gateway
Docker container, ~80MB image. Runs in your VPC, k8s, or VM. mTLS listener on :8443.
Policy cache
In-process LRU cache (512MB default) holding compiled bytecode bundles. Refreshed on push from control plane.
Nonce store
Local Redis (or Redis Cluster) for replay prevention. TTL matches permit max-TTL (default 60s).
Public key ring
Rotated Ed25519 public keys, delivered via signed JWT from control plane. No private key material leaves control plane.
Availability guarantee
Last-cached policy bundle survives indefinitely. Gateway continues making decisions for the subset of agents whose public keys and policies are cached. Graceful degradation: unknown agents are denied by default.
Control Plane — PermitNetworks managed
Policy store
Postgres with row-level tenant isolation. Policies versioned with immutable history. Signed at publish time.
Audit log
Append-only, partitioned by tenant and date. Merkle tree computed continuously. No deletes, no updates.
Key management
HSM-backed (AWS CloudHSM or customer-provided PKCS#11). Agent key rotation is automated, zero-downtime.
Admin API + UI
Policy CRUD, agent registration, audit log queries, metrics. SOC 2 Type II in progress.
Isolation model
Control plane is multi-tenant but all data is logically isolated at the Postgres row level with tenant_id enforced at the RLS layer. Cross-tenant queries are structurally impossible in the data model.
Communication pattern
Decision traffic never leaves your VPC. The control plane only pushes policy bundles and public keys; it never receives permit payloads.
Threat Model
Security properties with cryptographic backing.
Each security property is grounded in a cryptographic primitive or protocol — not a policy claim, not a runtime check that can be bypassed.
| Threat | Mitigation | Cryptographic Guarantee |
|---|---|---|
| Replay attacks | Nonce + expiry window (default 30s TTL). Nonces stored in local Redis, checked O(1). | TTL-bounded signatures — replayed permit rejected after expiry or nonce reuse |
| Forged permits | Ed25519 signature verification on every request. No signature → immediate reject, no policy eval. | EUF-CMA unforgeability — computationally infeasible to forge without private key |
| Policy tampering | Policy bundles are Ed25519-signed at publish time. Gateway rejects bundles with invalid or missing signature. | Integrity guarantee — tampered bundle detected before loading |
| Log tampering | Append-only Merkle tree. Tree root published hourly and optionally anchored on-chain. | Tamper-evidence — retroactive modification changes root hash, detectable by any observer |
| Insider threat | Key hierarchy ensures no single engineer has access to tenant signing keys. HSM operations are logged. | Accountability — every key operation is attributable to a principal |
| Network MITM | mTLS required for agent → gateway and gateway → control plane. Certificate pinning on SDK. | Confidentiality + authenticity — eavesdropping yields ciphertext; active injection is detected |
| Timing attacks | Ed25519 verify uses constant-time comparison (Rust dalek crate, formally verified). | Side-channel resistance — execution time does not leak key material |
Performance
Honest numbers — target vs. current beta.
We publish both target and current metrics because that gap tells you more about our engineering priorities than either number alone. Bottlenecks are known and tracked.
| Metric | Target | Current (Beta) | Status |
|---|---|---|---|
| p50 decision latency | 0.3ms | 0.5ms | In progress |
| p99 decision latency | 1.0ms | 1.4ms | In progress |
| p999 decision latency | 3.0ms | 4.2ms | In progress |
| Throughput per gateway | 50K req/s | 20K req/s | In progress |
| Signature verification | 80µs | 80µs | At target |
| Policy evaluation | 40µs | 60µs | In progress |
| Nonce lookup (Redis) | 50µs | 120µs | In progress |
| Audit log append | async | async | At target |
Nonce lookup bottleneck
Current 120µs is dominated by a single-region Redis setup. Moving to a local in-process nonce cache (Bloom filter for recent nonces, Redis for overflow) targets 20µs.
Throughput ceiling
20K req/s limit is single-gateway. Horizontal scaling is linear — tested to 200K req/s across 10 gateways. Client-side sharding by agent prefix.
Policy eval gap
60µs → 40µs target: replacing the Rust FFI boundary with a Wasm module eliminates marshaling overhead. ETA: Q3 2025.
Open Questions
Problems we haven't solved yet.
Technical buyers should know what's unresolved. These are the hardest open problems in our design. If you have opinions, we want to hear them.
Post-quantum signature migration
SLH-DSA (FIPS 205, stateless hash-based) vs. ML-DSA (FIPS 204, lattice-based). SLH-DSA has larger signatures (~8KB) but stronger security assumptions. ML-DSA signs faster but relies on module-lattice hardness. We haven't decided. We're watching NIST's guidance on hybrid schemes.
Cross-region audit log consistency
Multi-region Merkle trees require a consensus protocol for the tree root. We currently replicate audit events async (eventual consistency, <200ms lag). Strong consistency across regions requires 1 RTT of coordination — that conflicts with our latency budget. Active area of research.
Policy conflict resolution
When two policies match the same request with different effects, our current rule is 'most specific match wins'. Edge cases exist: what if two policies have equal specificity but different effects? Deny-overrides is safe but creates policy authoring friction. Allow-overrides is flexible but risky. We haven't published a formal semantics document yet.
Retry storms without replay vulnerability
A client that retries a failed request must use the same permit (same nonce, same signature) or generate a new one. Same permit: valid if within TTL, but nonce is spent on first gateway to process it. New permit: requires another signing round-trip. We're designing a 'permit bundle' that pre-mints N permits for use in retry scenarios — but bundle revocation is an open problem.
Get Started
Questions about the architecture?
Our engineering team is available for technical deep-dives. We're happy to walk through the implementation, discuss tradeoffs, or review your agent architecture.