Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,12 @@
],
"node/no-exports-assign": "error",
"node/no-new-require": "error",
"node/no-path-concat": "error"
"node/no-path-concat": "error",
"no-undef": "error",
"no-dupe-else-if": "error",
"getter-return": "error",
"no-setter-return": "error",
"no-unused-private-class-members": "error",
"no-constant-binary-expression": "error"
}
}
74 changes: 43 additions & 31 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ All notable changes to **`@alexify/kerberos`** are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

Code-review hardening waves (0–4): the Conditions inherited-key fail-open fix,
restored Node-ESM named exports, scope-depth caps, cache-reader
backoff/timeout/degraded-mode options (`cacheRetry`, `cacheKeyPrefix`,
`relationsTimeoutMs`, `maxConcurrency`), per-batch policy-resolution memo and
cross-request instance memo, audit-stream completeness (fail-closed denials,
`principalRoles`, the `audit` option, info-level plan results,
`kerberos.observability.failures`), the synchronous evaluation driver
(~2.5× simple `isAllowed`), reverse-lookup truncation signaling
(`onTruncated`), frozen policy shapes/tokens, and d.ts/export-parity guards.

## [3.1.0] - 2026-07-21

### Added
Expand Down Expand Up @@ -174,6 +186,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- New exported constants: `ALL_ROLES`, `ALL_RESOURCES`, `DEFAULT_VERSION`,
`BASE_SCOPE` (plus `ALL_ACTIONS` and `createCacheReader` are now typed).

- **Native OpenTelemetry support (traces + metrics)** via the new `telemetry`
constructor option, following the same zero-dependency delegation philosophy
as `logger`/`cache`: pass `{ api }` (the `@opentelemetry/api` module — Kerberos
derives its own tracer/meter with the `@alexify/kerberos` instrumentation
scope) or pre-created `{ tracer, meter }` instances. One span per
`isAllowed`/`checkResources` call (started **active**, so auto-instrumented
cache spans nest under it), per-decision `kerberos.decision` events, `ERROR`
span status + exception events on failures, plus two metrics:
`kerberos.decisions` counter and `kerberos.request.duration` histogram.
Identity attributes (`kerberos.principal.id`, `kerberos.resource.id`) are on
by default and can be stripped with `telemetry.includeIdentity: false`.
Telemetry failures never affect authorization results, and the
logger-controlled error contract (fallback vs rethrow) is unchanged. New
structural types (`KerberosTelemetryOptions`, `KerberosTracer`,
`KerberosMeter`, …) are exported from `index.d.ts`.

- **Browser/server entrypoint split** (pino-style). New root `browser.js` entry
plus a package.json `browser` field (object map) and a `browser` condition in
`exports`: browser bundlers (webpack, Vite, esbuild `platform: browser`,
Rollup node-resolve with `browser: true`, Parcel, Bun) now automatically pick
a build with **zero Node.js builtins**.
- New `src/runtime/node.js` / `src/runtime/browser.js` platform modules holding
the only platform-specific code (`generateCallId`, `getNow`). The Node
runtime uses `node:crypto` / `node:perf_hooks` directly; the browser runtime
uses `globalThis.crypto.randomUUID` (with a pseudo-UUID fallback for insecure
contexts) and `globalThis.performance` (falling back to `Date.now`).
- `engines.node >= 18` — documents the already-implicit runtime floor.

### Performance

- **`checkResources` evaluates resources concurrently** via
Expand Down Expand Up @@ -209,37 +249,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
now throw at construction** instead of crashing later during evaluation —
every definition must be either condition-backed or relation-backed.
- `pnpm-lock.yaml` is no longer published in the npm tarball.

- **Native OpenTelemetry support (traces + metrics)** via the new `telemetry`
constructor option, following the same zero-dependency delegation philosophy
as `logger`/`cache`: pass `{ api }` (the `@opentelemetry/api` module — Kerberos
derives its own tracer/meter with the `@alexify/kerberos` instrumentation
scope) or pre-created `{ tracer, meter }` instances. One span per
`isAllowed`/`checkResources` call (started **active**, so auto-instrumented
cache spans nest under it), per-decision `kerberos.decision` events, `ERROR`
span status + exception events on failures, plus two metrics:
`kerberos.decisions` counter and `kerberos.request.duration` histogram.
Identity attributes (`kerberos.principal.id`, `kerberos.resource.id`) are on
by default and can be stripped with `telemetry.includeIdentity: false`.
Telemetry failures never affect authorization results, and the
logger-controlled error contract (fallback vs rethrow) is unchanged. New
structural types (`KerberosTelemetryOptions`, `KerberosTracer`,
`KerberosMeter`, …) are exported from `index.d.ts`.

- **Browser/server entrypoint split** (pino-style). New root `browser.js` entry
plus a package.json `browser` field (object map) and a `browser` condition in
`exports`: browser bundlers (webpack, Vite, esbuild `platform: browser`,
Rollup node-resolve with `browser: true`, Parcel, Bun) now automatically pick
a build with **zero Node.js builtins**.
- New `src/runtime/node.js` / `src/runtime/browser.js` platform modules holding
the only platform-specific code (`generateCallId`, `getNow`). The Node
runtime uses `node:crypto` / `node:perf_hooks` directly; the browser runtime
uses `globalThis.crypto.randomUUID` (with a pseudo-UUID fallback for insecure
contexts) and `globalThis.performance` (falling back to `Date.now`).
- `engines.node >= 18` — documents the already-implicit runtime floor.

### Changed

- Removed the try/catch `require('crypto')` / `require('node:perf_hooks')`
feature detection from `src/Kerberos.js` — each platform entry now targets
its runtime directly. Node behavior is unchanged; browser bundles get
Expand Down Expand Up @@ -387,6 +396,9 @@ Initial release.
- In-browser / serverless authorization.
- Built-in test harness (`Tests`).

[unreleased]: https://github.com/Alexis-Technologies/kerberos/compare/v3.1.0...HEAD
[3.1.0]: https://github.com/Alexis-Technologies/kerberos/releases/tag/v3.1.0
[3.0.0]: https://github.com/Alexis-Technologies/kerberos/releases/tag/v3.0.0
[2.0.1]: https://github.com/Alexis-Technologies/kerberos/releases/tag/v2.0.1
[2.0.0]: https://github.com/Alexis-Technologies/kerberos/releases/tag/v2.0.0
[1.0.0]: https://github.com/Alexis-Technologies/kerberos/releases/tag/v1.0.0
13 changes: 7 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Project

Kerberos.js (`@alexify/kerberos`) is a zero-dependency (~8 KB), in-process authorization engine for JavaScript — a lightweight, embeddable alternative to Cerbos. It evaluates `resourcePolicy` / `principalPolicy` / `rolePolicy` documents against `(principal, resource, action)` requests and returns `EFFECT_ALLOW` / `EFFECT_DENY`, with optional derived roles, conditions, variables, constants, outputs, scopes, schema validation, audit logging, cache-backed dynamic policies, ReBAC (relation-backed derived roles + a built-in SpiceDB-inspired Zanzibar-lite resolver on the `/relations` subpath), and Cerbos-compatible resources query plans (`planResources`). Runs in Node.js and the browser.
Kerberos.js (`@alexify/kerberos`) is a zero-dependency (~29 KB min+gzip), in-process authorization engine for JavaScript — a lightweight, embeddable alternative to Cerbos. It evaluates `resourcePolicy` / `principalPolicy` / `rolePolicy` documents against `(principal, resource, action)` requests and returns `EFFECT_ALLOW` / `EFFECT_DENY`, with optional derived roles, conditions, variables, constants, outputs, scopes, schema validation, audit logging, cache-backed dynamic policies, ReBAC (relation-backed derived roles + a built-in SpiceDB-inspired Zanzibar-lite resolver on the `/relations` subpath), and Cerbos-compatible resources query plans (`planResources`). Runs in Node.js and the browser.

Package manager is **pnpm** (`packageManager: pnpm@11.5.0`). CommonJS throughout (`require`/`module.exports`), no build/transpile step — `src/` ships as-is.
Package manager is **pnpm** (the exact pinned version is the `packageManager` field in `package.json`). CommonJS throughout (`require`/`module.exports`), no build/transpile step — `src/` ships as-is.

## Commands

Expand All @@ -16,9 +16,10 @@ node --test test/Kerberos.test.js # run a single test file
node --test --test-name-pattern="scope" # filter tests by name
pnpm test:types # type-check test/types.test-d.ts against index.d.ts via tsd
pnpm test:coverage # c8 coverage over src/
pnpm lint # oxlint src test
pnpm format # oxfmt src test (format:check for CI)
pnpm lint # oxlint src test scripts bench
pnpm format # oxfmt src test scripts bench (format:check for CI)
pnpm bench # ops/sec benchmark harness (bench/bench.js)
pnpm size # bundle-size report (scripts/size.js; CI-enforced smoke)
pnpm docs:dev # VitePress dev server for docs/ (docs:build / docs:preview too)
```

Expand All @@ -41,7 +42,7 @@ Every DSL concept (`Conditions`, `Constants`, `DerivedRoles`, `Outputs`, `Princi

### Platform runtime split (`src/runtime/`)

`src/runtime/node.js` and `src/runtime/browser.js` are the **only** platform-specific files in the package — both export the identical `{ generateCallId, getNow }` interface. The Node variant uses `node:crypto` / `node:perf_hooks` directly (no try/catch feature detection); the browser variant uses `globalThis.crypto?.randomUUID` (pseudo-UUID fallback for insecure contexts) and `globalThis.performance` (falling back to `Date.now`), reading globals at call time so fallback branches stay testable. `src/Kerberos.js` requires `./runtime/node.js`; browser bundlers swap it via the package.json `browser` field object map (`"./index.js" → "./browser.js"`, `"./src/runtime/node.js" → "./src/runtime/browser.js"`) plus the `browser` condition in `exports`. Invariants: everything else in `src/` must stay platform-neutral (no Node builtins); any new Node builtin usage goes into `src/runtime/node.js` with a matching browser counterpart; renaming runtime files requires updating the `browser` map keys in `package.json` (a mismatch fails loudly at bundle time thanks to the `node:` prefix). Root `browser.js` intentionally mirrors `index.js` — do not deduplicate them.
`src/runtime/node.js` and `src/runtime/browser.js` are the **only** platform-specific files in the package — both export the identical `{ generateCallId, getNow }` interface. The Node variant uses `node:crypto` / `node:perf_hooks` directly (no try/catch feature detection); the browser variant uses `globalThis.crypto?.randomUUID` (pseudo-UUID fallback for insecure contexts) and `globalThis.performance` (falling back to `Date.now`), reading globals at call time so fallback branches stay testable. `src/Kerberos.js` and `src/Relations/RelationResolver.js` require `./runtime/node.js`; browser bundlers swap it via the package.json `browser` field object map (`"./index.js" → "./browser.js"`, `"./src/runtime/node.js" → "./src/runtime/browser.js"`) plus the `browser` condition in `exports`. Invariants: everything else in `src/` must stay platform-neutral (no Node builtins); any new Node builtin usage goes into `src/runtime/node.js` with a matching browser counterpart; renaming runtime files requires updating the `browser` map keys in `package.json` (a mismatch fails loudly at bundle time thanks to the `node:` prefix). Root `browser.js` intentionally mirrors `index.js` — do not deduplicate them.

### Request evaluation flow (`src/Kerberos.js`)

Expand Down Expand Up @@ -74,7 +75,7 @@ Because remote-stored policies must be JSON (no live functions), `codec.js` (`cr

### Logging (`src/logging.js`)

`logger` option accepts `true` (legacy `console.group`/`table`/`debug` output), a custom console-like object, or a structured logger (e.g. Pino, detected via `info`/`debug` methods) which receives one structured audit entry per evaluated action. When logging is enabled, runtime/validation errors are caught and converted to fallback results (`isAllowed` → `false`, `checkResources` → empty results) instead of being thrown; when disabled, errors propagate to the caller.
`logger` option accepts `true` (legacy `console.group`/`table`/`debug` output), a custom console-like object, or a structured logger (e.g. Pino, detected via `info`/`debug` methods) which receives one structured audit entry per evaluated action. Logging is pure observability: every logger call is internally guarded (a throwing logger can never affect decisions), and it never changes error behavior — whether evaluation errors are rethrown or converted into fail-closed results is decided solely by the `onError` option (`'throw'` | `'deny'`), while `KerberosValidationError` (malformed arguments) always propagates regardless.

### OpenTelemetry (`src/telemetry.js`)

Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2024 Alexis Technologies
Copyright (c) 2024-2026 Alexis Technologies

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
Loading
Loading