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. Der Blob ist opaque für jeden Debugger. Tree-Shaking funktioniert nicht. Du brauchst await init() bevor du einen einzigen Request evaluieren kannst. Und wenn du auf Cloudflare Workers, Deno Deploy oder Vercel Edge deployst, hast du WASM Size Limits oder fehlende APIs, bevor du irgendetwas shippst.
cedar-ts wirft das alles weg und reimplementiert Cedar in purem TypeScript. 15KB gzipped. Synchron. Debuggable. Läuft überall.
Was drin steckt
Recursive Descent Parser. Hand-geschriebener Tokenizer und Parser für die komplette Cedar Syntax: permit, forbid, when, unless, Annotations, alle Scope Constraint Formen. Kein Parser Generator, weil die Grammatik klein genug ist, dass ein Generator keinen Mehrwert bringt, aber die Fehlermeldungen verschlechtert.
Evaluator. Implementiert die Cedar Semantik: Default Deny, Forbid überschreibt Permit, Short-Circuit Evaluation, Error Handling nach Spec. Jede Authorization Response sagt dir, welche Policies matched haben, welche denied wurden, und die exakten Evaluation Errors.
Entity Store. MemoryEntityStore mit transitiver Ancestor-Auflösung via DFS, Cycle Protection und Per-Entity Caching. Du modellierst User in Team in Org und bekommst korrekte in-Checks ohne Graph Traversal Code zu schreiben.
Schema Validator. Statische Validierung von Policies gegen Entity- und Action-Schemas. Fängt Tippfehler in Entity Types, invalide Attribute Accesses und Type Mismatches, bevor deine Policies in Production landen. Errors mit Source Locations.
import { Authorizer, MemoryEntityStore } from "cedar-ts";
const entities = new MemoryEntityStore([
{
uid: { type: "User", id: "alice" },
attrs: { role: "admin" },
parents: [{ type: "Team", id: "engineering" }],
},
{
uid: { type: "Team", id: "engineering" },
attrs: {},
parents: [],
},
]);
const authorizer = Authorizer.fromText(`
permit(
principal in Team::"engineering",
action == Action::"view",
resource in Folder::"docs"
) when { principal.role == "admin" };
`, entities);
const response = authorizer.isAuthorized({
principal: { type: "User", id: "alice" },
action: { type: "Action", id: "view" },
resource: { type: "Document", id: "roadmap" },
context: {},
});
// response.decision === "allow"
// response.diagnostics.reasons === ["policy0"]
Synchroner Call. Kein await, kein Lifecycle, kein WASM Setup.
Warum nicht einfach das WASM SDK
Die Zahlen sprechen für sich:
| 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 |
Für einen Authorization Check auf dem Edge willst du keinen 2MB Blob laden. Du willst eine Funktion aufrufen und eine Antwort bekommen.
Design Decisions
Fünf ADRs dokumentieren die Kernentscheidungen: warum pure TypeScript statt WASM Bindings, warum ein Hand-geschriebener Parser, wie der Entity Store Ancestry auflöst, wie die Evaluation Semantik Cedar’s Spec folgt, und warum Schema Validation ein eigener Pass ist statt inline im Evaluator.
Jede Entscheidung hat Trade-offs. Der Hand-geschriebene Parser ist mehr Code als ein generierter, aber die Fehlermeldungen sind besser und du kannst ihn debuggen. Pure TypeScript ist langsamer als Rust/WASM für große Policy Sets, aber für die typischen 10-50 Policies einer Anwendung ist der Unterschied nicht messbar.
Was ich gelernt hab
Eine Sprache zu reimplementieren zwingt dich, jede Ecke der Spec zu verstehen. Cedar hat Feinheiten, die du beim Benutzen nie triffst: wie Error Propagation in verschachtelten when/unless-Clauses funktioniert, was passiert wenn ein in-Check auf eine Entity trifft, die nicht im Store existiert, wie Forbid-Policies mit Conditions interagieren.
Die größte Überraschung: der Parser war der einfache Teil. Die korrekte Evaluation Semantik, besonders das Error Handling, hat dreimal so lang gedauert.