Benchmarks
Numbers you can rerun
The TypeScript policy engine does 2.45M warm-cache evaluations per second on one core, with a p99 of 625 ns. Everything below was measured on the machine, versions and commits listed, on 2026-10-07.
TypeScript
Policy engine
One user, one agent and five permissions in an in-memory SQLite database, audit logging off. Warm means a pre-warmed cache hit. Cold means the cache is invalidated before every call, so each evaluation pays one database round trip. The range column is the lowest and highest run, so you can see how noisy the machine was.
| Scenario | Evaluations per second | Range across runs | p99 latency | p99 range |
|---|---|---|---|---|
| warm cache hit | 2.45M | 2.41M to 2.51M | 625 ns | 583 ns to 625 ns |
| cold path - direct permission | 10.2k | 10.1k to 10.9k | 189 µs | 187 µs to 206 µs |
| cold path - rbac role expansion | 22.0k | 20.7k to 22.4k | 73.0 µs | 67.8 µs to 77.4 µs |
| cold path - rebac graph lookup | 6.4k | 6.1k to 6.7k | 356 µs | 297 µs to 375 µs |
Go
Hot paths in theauth-go
The twelve benchmarks that gate every release against regressions, in memory storage. Argon2 runs at the production work factor, so those two rows are slow on purpose. Times are per operation.
| Benchmark | What it measures | Time per op | Range across runs | Memory per op | Allocations per op |
|---|---|---|---|---|---|
| BenchmarkArgon2Hash | Argon2id hash at the production work factor. | 26.4 ms | 25.9 ms to 27.1 ms | 64.0 MiB | 86 |
| BenchmarkArgon2Verify | Argon2id verify at the production work factor. | 26.3 ms | 26.2 ms to 26.8 ms | 64.0 MiB | 88 |
| BenchmarkAuditRedactor | Audit redactor key matching. | 689 ns | 669 ns to 714 ns | 0.7 KiB | 4 |
| BenchmarkOAuthCallback | Social-provider callback: AES-GCM encrypt and in-memory upsert. | 813 ns | 788 ns to 843 ns | 1.3 KiB | 5 |
| BenchmarkOAuthCodeFlow | Authorization code grant: PKCE S256 check, code consume, JWT mint, refresh insert. | 24.1 µs | 23.8 µs to 24.4 µs | 16.5 KiB | 106 |
| BenchmarkOAuthTokenEndpointRefreshHit | Refresh-token grant on /oauth/token with the client-secret Argon2 cache warm. | 24.1 µs | 23.7 µs to 24.5 µs | 16.2 KiB | 106 |
| BenchmarkJWKSEndpoint | JWKS endpoint: signing-key snapshot read and JSON marshal. | 3.49 µs | 3.46 µs to 3.58 µs | 3.5 KiB | 42 |
| BenchmarkSCIMTokenAuth | SCIM bearer authentication: SHA-256 hash plus one storage lookup. | 7.25 µs | 7.16 µs to 7.55 µs | 10.4 KiB | 60 |
| BenchmarkSessionLookup | Cookie parse, token hash and in-memory lookup: the floor cost of an authenticated request. | 4.34 µs | 4.16 µs to 8.87 µs | 9.1 KiB | 46 |
| BenchmarkJWTSign | Ed25519 JWT sign. | 15.7 µs | 15.5 µs to 19.1 µs | 2.8 KiB | 15 |
| BenchmarkJWTVerify | Ed25519 JWT verify. | 36.1 µs | 34.9 µs to 37.8 µs | 3.4 KiB | 61 |
| BenchmarkRateLimitReadHeavy | Rate limiter under parallel, read-heavy load. | 129 ns | 116 ns to 131 ns | 0.0 KiB | 0 |
What these numbers do not cover
- Network and TLS. Every benchmark runs in process.
- Real databases. Storage is in memory (SQLite in memory for TypeScript). Postgres and MySQL add round trips these results do not include.
- Scaling across cores. The TypeScript engine is single-threaded here. Go has one parallel case, the rate limiter.
- Large policy sets. The TypeScript run uses five permissions. Cold-path cost grows with the policy graph.
- Audit logging, memory use and cold start. Audit is off in the TypeScript run, and neither run measures process memory or startup time.
- Other libraries. We publish our own numbers only. A fair comparison needs the same harness and hardware for every library, and we have not built that.
Results move with the machine. A laptop running other work gives noisier and slower numbers, which is why the range columns exist. Treat the figures as a baseline for your own hardware, not a promise.
Reproduce
Two commands
Both repositories are public. Clone, install, run. The Go script reads its benchmark list from benchgate/curated.txt and prints output that benchstat accepts.
# TypeScript policy engine
git clone https://github.com/glincker/theauth.git && cd theauth
pnpm install && pnpm bench
# Go hot paths (BENCH_TIME and BENCH_COUNT default to 2s and 10)
git clone https://github.com/glincker/theauth-go.git && cd theauth-go
./scripts/bench-gate.sh
Get started
Give your first agent an identity.
Install the package, create an agent with scoped permissions, and read its first audit record. The core runs on Postgres, SQLite, MySQL or D1, and the Go module needs a single go get.
npm install @glinr/theauthgo get github.com/glincker/theauth-go