AWS Cedar ist eine Policy Language für Authorization. Du schreibst Regeln wie “User in Team Engineering darf Dokumente im Folder Docs lesen, wenn er Admin ist”, und Cedar evaluiert das gegen einen Entity Store. Analyzable, auditable, schnell.
Das Problem ist die JavaScript-Seite. Das offizielle SDK (@cedar-policy/cedar-wasm) kompiliert die Rust Engine zu einem 2MB WASM Blob. Opaque für jeden Debugger. Kein Tree-Shaking. Async Initialization. Und auf Edge Runtimes (Cloudflare Workers, Deno Deploy, Vercel Edge) stößt du an WASM Size Limits, bevor du irgendetwas shippst.
cedar-ts reimplementiert Cedar komplett in purem TypeScript. 15KB gzipped. Synchron. Step-through debuggable. Zero Dependencies.

Der Parser
Hand-geschriebener Recursive Descent Parser in zwei Phasen: Tokenizer (Source → Tokens) und Parser (Tokens → AST).
Der Tokenizer trackt Line/Column Positionen für präzise Fehlermeldungen. Verarbeitet String Escapes (\n, \t, \r, \\, \", \0, \*), Keywords via Lookup Table, Line und Block Comments, Two-Character Operators (==, !=, <=, >=, &&, ||, ::).
Der Parser nutzt Precedence Climbing für Expressions: parseOr → parseAnd → parseRelation → parseHasIn → parseAddSub → parseMulDiv → parseUnary → parseAccess → parsePrimary. Zero Backtracking, Single-Pass. Kein Parser Generator, weil die Grammatik klein genug ist, dass ein Generator keinen Mehrwert bringt, aber die Kontrolle über Fehlermeldungen nimmt: “Expected ’}’ but got ‘EOF’ at line 5, column 12” statt generischem “Parse Error”.
Fünf ADRs dokumentieren die Design-Entscheidungen. ADR-002 erklärt, warum Recursive Descent statt PEG oder Combinators: Cedar hat keine ambiguousen Konstrukte, Lookahead reicht, und die Fehlermeldungen müssen exzellent sein.
Der Evaluator
Implementiert die vollständige Cedar Semantik:
Default Deny. Kein Permit? Deny.
Forbid überschreibt Permit. Absolut, keine Ausnahmen.
Independent Evaluation. Policies interagieren nicht miteinander, werden erst bei der Decision kombiniert.
Short-Circuit. && evaluiert die rechte Seite nicht, wenn die linke false ist. || analog.
Error Handling nach Spec. Errors in Conditions werden gecatcht, in Diagnostics aufgezeichnet, und die Policy wird als “not satisfied” behandelt. Kein Crash, kein Silent Fail.
Jede AuthorizationResponse enthält Decision + Diagnostics: welche Policies matched haben (Reasons), welche Evaluation Errors aufgetreten sind. Du weißt genau, warum ein Request denied wurde.
Scope Matching für Principal, Action und Resource unterstützt alle Cedar-Formen: any, == entity, in entity, is Type, is Type in entity, und Action Sets. Entity Hierarchy Traversal via store.getAncestors() mit transitiver Closure.
Entity Store mit Cycle Protection
MemoryEntityStore speichert Entities in einer Map, keyed by Type::"id". Transitive Ancestry via DFS:
private collectAncestors(uid, ancestors, visited) {
const key = entityUIDKey(uid);
if (visited.has(key)) return; // Cycle Detection
visited.add(key);
const entity = this.entities.get(key);
if (!entity) return;
for (const parent of entity.parents) {
ancestors.add(parentKey);
this.collectAncestors(parent, ancestors, visited);
}
}
Ancestor Sets werden pro Entity gecacht und nur bei Mutations invalidiert. Das Interface ist abstrakt: du kannst einen PostgreSQL-backed oder Redis-cached Store implementieren, ohne den Evaluator anzufassen.
Schema Validator
Separater Validation Pass über den AST. Konservative Type Inference (gibt Any zurück, wenn der Typ nicht bestimmbar ist). Prüft:
- Entity Types existieren im Schema
- Actions existieren im Schema
- Attribute Accesses sind valide für den Entity Type
- Operator-Operanden haben kompatible Typen
- Namespace Qualification funktioniert korrekt
Alle Errors werden gesammelt statt Fail-Fast. Jeder Error enthält Message, Policy ID und Source Span. Fängt Tippfehler, bevor sie Production erreichen.
Performance
| Operation | Scale | Zeit |
|---|---|---|
| Parse | 100 Policies | < 5ms |
| Evaluate | 100 Policies × 1 Request | < 1ms |
| Entity Ancestry | 20-Level Hierarchy | < 0.1ms |
| Full Authorization | 100 Entities, komplexe Policies | < 2ms |
Sub-Millisekunde für typische Workloads. Stress Tests verifizieren: 100 Policies pro Request, 100 Entities pro Store, 50+ Level tiefe Expression Nesting. Kein Stack Overflow, lineares Scaling.
Vergleich
| cedar-ts | @cedar-policy/cedar-wasm | |
|---|---|---|
| Bundle Size | 15KB gzipped | ~2MB |
| Initialization | synchron | async (await init()) |
| Debugging | Step-through in DevTools | opaque WASM |
| Edge Runtimes | überall | WASM Limits |
| Dependencies | 0 | WASM Runtime |
| Tree-Shaking | ja (ESM) | nein |
Test Suite
3.890 Zeilen Tests über 6 Dateien. 1.5:1 Test-to-Code Ratio. Parser Tests für jede Syntax-Form und jeden Error Case mit Line/Column Accuracy. Evaluator Tests für Decision Logic, Scope Matching, Entity Hierarchy, Error Handling. Stress Tests für Deep Nesting und große Policy Sets.