Repository navigation
Releases: praxis-proxy/policy
Release list
v0.5.0
Added
- Added the opt-in
delegator/vaultbuiltin to read downstream credentials from Vault KV v2 for a user, client, or workload. Routes using it must authorize before delegation. (#168) - Request header assertions can use a declared
secret.<name>value for static upstream credentials. Values are resolved at startup and read from the secret store at the point of use, so rotation takes effect on the next request after refresh without fetching on the request path. Unknown names and response assertions using secrets are rejected. (#174) - Added an OCSF audit sink as an unpublished reference plugin. It emits decision records with optional chained attestations and DSSE signatures for offline verification. (#171)
- Exposed MCP tool, resource, and prompt annotations to policy, including
readOnlyHintanddestructiveHint. Workload attestation timestamps are available as RFC3339 strings and Unix seconds; APL can compareattested_at_epochagainst a fixed threshold. (#175) - Added secret-provider health details through
secrets_healthand atomic value/generation reads throughSecretRef::snapshot. (#191)
Changed
- Breaking: Secret configuration reloads that change
secrets:now fail; restart to apply those changes. Theenvprovider rejects unused settings.secrets_last_successretains its last successful timestamp after a failure, so usesecrets_healthto detect outages. (#191) - Breaking for Rust callers: Secret assertions add
HttpExtension::secret_headers; exhaustive struct literals must initialize it, and hosts must preserve it between hooks.SourcePath::resolveandassertions::rendernow take a secret store,AssertionsConfig::validatetakes secret configuration,SourcePath::capabilityreturnsOption<Capability>, andSourcePathgains aSecret(String)variant and is non-exhaustive. (#174) - Breaking for custom CEL functions:
CelResolver::with_functionsand its registration callback now returnResult. Conflicts with standard-library overloads fail construction; later registration failures deny even withon_error: allow. Hosts that need such overloads must use a CELEnvoverload. (#185) - CEL 0.15 counts Unicode code points instead of UTF-8 bytes in string
size().exists()returnstrueif an element matches even when another element errors. Review policies that depend on byte limits or list-predicate errors, since previously denied requests may now allow. (#185) - JSON floats now parse with
serde_json'sfloat_roundtripfeature. Exact numeric boundaries and re-serialized audit records can change. (#171)
Removed
- Breaking for Rust callers: Removed eight
CAP_READ_*names for unrestricted CMF slots and sevenBAG_*_PREFIXconstants. The policy attributes remain available. (#175)
Fixed
- Rejected JWTs with unsupported critical headers. OAuth token exchange now validates issued token type, granted scopes, and JWT audience and subject by default. Empty scope requests accept IdP defaults;
allowed_extra_scopespermits named defaults alongside requested scopes. (#188) - Bound CIBA approvals to the selected route, tool, and authenticated requester, with atomic single-use redemption within a plugin instance. Backchannel and poll failures no longer expose OP response bodies that may contain credentials. (#188)
- Made non-scalar numeric policy comparisons fail closed. Structured JSON redaction now matches keys without case sensitivity, supports recursive paths, preserves keys containing dots, and bounds recursive traversal. (#188)
- Kept secret assertion headers redacted in plugin and audit views across subsequent hooks, config reloads, and header case normalization. Plugin header writes preserve the original credential while allowing unrelated changes. (#174)
- Confined file-provider reads to
base_dir, rejecting symlinks that escape it while supporting links within it, including Kubernetes projected secrets. (#191) - Corrected CMF capability namespace descriptions, including the
read_teamsmapping, and added checks against the keys the bridge actually emits. (#175)
Documentation and tests
- Added integration, resilience, and security suites through the reference host, covering Cedar, CEL, OPA, dependency failures, concurrency, and credential leaks, with optional live Keycloak, Valkey, and Vault scenarios. (#180)
- Expanded authorization coverage for glob and wildcard entity routes, including route specificity. (#122)
- Defined requirements for audit serialization, delivery, and offline verification. (#173)
- Updated project governance, contribution, security, maintainer, and code ownership guidance. (d2bf93c, 940c5c5, 93580f2)
New Contributors
- @Levaj2000 made their first contribution in #173
Contributors
Full Changelog: v0.4.1...v0.5.0
v0.4.1
Added
- Optional bounded PDP decision cache. A
cache:block on aglobal.pdp[]entry stores Allow and Deny for a positive TTL and a positive entry cap. Omission leaves evaluation unchanged. Keys are a digest of dialect, call arguments, the full attribute bag, and structured request input, so request data is not retained. Dispatch errors are never stored, expired entries are never returned, FIFO eviction stays inside the cap, and a PPE configuration generation change drops the map. External PDP policy can still go stale until the TTL expires;docs/pdp-decision-cache.mdstates that. (#67) - PDPs can evaluate host-supplied LLM request JSON and native tool arguments. (#144)
- Added verdict audit sinks, durable effect logs, and keyed content provenance. (#84)
Changed
- Breaking: OPA and CEL now use native JSON for
tool:routeargs;RoutePayloadstruct literals requirestructured.
See migration guidance. (#144) - Breaking: cloned extensions drop HTTP transport, and effects are limited to
sequentialandtransformmodes. (#84) - Missing required LLM requests and overly deep structured input now deny. (#144)
- PDP-generated errors hide input values; Cedar schemas require
structured_context: truefor structured input. (#144) - The reference audit logger uses sink mode when
hooks:is absent. (#84) - Serial and transform plugin panics are contained and report
plugin_panic. (#84)
Documentation
- Documented Kuadrant/PPE attribute mapping and policy parity. (#131)
New Contributors
- @maleck13 made their first contribution in #131
- @rkaplan-hub made their first contribution in #101
Contributors
Full Changelog: v0.4.0...v0.4.1
v0.4.0
Added
- Added named
secrets:providers and values, startup resolution, and host-driven refresh. Built-infileandenvproviders support local and Kubernetes-mounted credentials. (#97) - Added a Vault KV v2 secret provider behind
secrets-vault, with Kubernetes and AppRole authentication through the host HTTP transport. (#103) - Added an opt-in
quotaplugin backed by Limitador for per-principal LLM token budgets. (#116) - Added the
ibmverifyJWT claim mapper preset for scalar or array collection claims. (#134) - Added an
identity/api-keyresolver with file and HTTP directories, shared identity mapping, and bounded caching. Configure the backend withprovider:; the facade exposes the resolver through theapi-keyfeature. (#125, #135, #136) - Documented how to add and test provider-specific JWT claim mapper presets. (#128)
Changed
- Consolidated the nine bundled extensions into
praxis-policy-builtins, with one feature per extension. The facade's features, re-exports, and policykindstrings remain the same. Direct users of a retired extension crate must switch topraxis-policy-builtinswith the matching feature to avoid duplicate registration. See the crate mapping. (#140) - JWT, OAuth, and CIBA configuration errors now name the plugin kind instead of the former crate name; update log or alert patterns that match this text. (#140)
- Moved the configurable identity claim mapper to
praxis_policy_core::identity::mapping. Rust callers using the JWT plugin's module paths must update their imports;ConfiguredClaimMap::newnow takes aMappingProfile. The operator'sclaim_map:configuration is unchanged. (#119) - Exposed
execute_with_retryfor secret providers using the host transport. (#103) - Replaced
serde_yamlwithyaml_serdeand updatedregorusto 0.12.0. (#121, #114)
Fixed
- Kept dotted subject and client membership names from creating ambiguous flattened policy aliases. (#126)
- Sized Vault login JSON buffers before serialization to avoid leaving credentials in a reallocated buffer; added an optional live Vault KV v2 test. (#141)
Internal
- Updated Criterion from 0.7.0 to 0.8.2. (#113)
New Contributors
- @hexfusion made their first contribution in #135
- @tedhabeck made their first contribution in #134
Contributors
Full Changelog: v0.3.1...v0.4.0
v0.3.1
What's Changed
- test(cel): pin stdlib-vs-custom and namespace-vs-scalar collision pre… by @aviavissar in #83
Full Changelog: v0.3.0...v0.3.1
v0.3.0
Added
- Added comprehensive PPE documentation and tested examples. (#82)
- Documented the CMF extension bag contract and expanded cross-PDP conformance tests. (#59)
- Added a catalog of fail-closed safety invariants with fault-injection tests. (#71)
- Added Criterion benchmarks for hook dispatch, full decisions, throughput, PDP evaluation, and session memory. (#35)
Changed
- Updated
base64, CEL, Redis, deadpool-redis, UUID, and related dependencies.(#111)
Removed
- Removed the unused public
BAG_WORKLOAD_PREFIXconstant. (#59)
Fixed
- Contained serial, transform, audit, and PDP panics through
on_error; cancelled requests now abort spawned work and preserve prior plugin state. (#71) - Made the
ppe-coreself-test dependency path-only so release packaging can resolve it. (#71) - Corrected
read_labelsandread_workloadcapability namespaces to match the keys emitted by the CMF bridge. (#59)
Security
- Redacted inline PEM keys and HMAC secrets from identity configuration debug output. (#86)
- Updated
rustlsto 0.23.45 for RUSTSEC-2026-0285. (#86)
Internal
- Raised line coverage above 96% and made the LCOV artifact match the gated run. (#86)
- Added multithreaded engine stress tests, a Loom memory-ordering model, and a nightly ThreadSanitizer job. (#60)
New Contributors
- @aviavissar made their first contribution in #86
Contributors
Full Changelog: v0.2.0...v0.3.0
v0.2.0
Upgrading from 0.1.0? Configurations require changes: see
docs/upgrade-apl.md.
docs/content/apl/apl-grammar.mddocuments the APL grammar.
Added
assertions:controls headers at trust boundaries. (#56)- The normative APL grammar and custom
response:denial block are now documented. (#55) - Differential tests cover Cedar, CEL, and OPA. (#43)
- Delegated tokens can be cached until expiry, with warnings for routes that delegate unvalidated credentials. (#36)
http:routes generic HTTP requests, withhttp.responseandhttp.statussupporting response policy. (#42)- Hook declarations now include their routing metadata. (#38)
- Hosts provide the transport used by PPE plugins, gated by the
perform_httpcapability. (#32) - Outbound HTTP gains body limits, HTTP/2, safe retries, explicit egress-denial codes, prohibited-address data, and a deterministic test transport. (#32)
- JWT claim mapping is configurable through presets or inline maps, with claim inclusion and exclusion controls. (#31)
- Roles and permissions are readable as whole sets. (#7)
Changed
- APL now treats
require(P)as!P, permits onlyrun(name)for plugin invocation, and rejects field operations in rule position or empty pipe stages. (#55) - Quoted literals, attribute paths, reserved words, boolean operators, numbers, and lexer positions now follow one strict grammar. (#55)
- Dispatch defaults to
policy; unreachable plugins fail loading, narrowed plugins raise an alarm, and requests without resolvable context are denied. (#55) - The compiled IR, group authorization, plugin reachability, and policy-term compilation now follow the canonical configuration model. (#55)
- Dispatch-specific and unknown keys are rejected at every scope; global-only keys, authentication replacement, and
response:resolution are enforced consistently. (#55) - Generic HTTP has typed request and response hooks; incompatible handlers and message-only field pipelines are rejected. (#42)
- HTTP route resolvers use the matched route, report annotation replacement, expose integration errors, and reject unread route keys. (#42)
- Hook names are validated on every load path, and metadata lookup now returns
Option<HookMetadata>. (#38) - Plugins initialize through
Plugin::initialize_with, andidentity-jwtrefreshes JWKS on demand. (#32) - JWT configuration rejects unknown keys and invalid SPIFFE trust domains; workload trust domains are derived rather than mapped. (#31)
- Unused items now fail the build. (#44)
Security
- A panicking
parallel:branch produces a Deny. (#45) - The bundled transport blocks prohibited destination addresses. (#45)
- Leg-2 token-exchange denials no longer expose IdP response details. (#45)
- JWT issuers must configure at least one audience. (#45)
!=on a missing attribute evaluates to true. (#45)- Non-numeric and non-finite order comparisons fail closed. (#45)
- Unreadable handler results are execution errors, not Allows. (#45)
Removed
- The legacy
compile_configpath, its alternate route shape, and its catch-all are gone. (#55) - Policy-mode activation lists, the HTTP inertness alarm, route
when:, and other parsed-but-inert settings are gone. (#55) - Legacy policy keys, flat phase keys, the
apl:wrapper, global field pipelines, and unknown top-level keys are rejected. (#55) - Unused hook-name modules and
AplRouteHandler::with_pdp_routerwere removed. (#38, #44)
Fixed
- Praxis integration fixes cover false narrowed-plugin alarms, explicit
ringselection, and policy evaluation on glob entity routes. (#72) - Bundles joined through both tags and groups run authentication only once. (#56)
- Policy layers now account for above-route delegation and elicitation, entity-default authentication, and empty authorization phases. (#55)
- HTTP matching now uses the host path exactly and validates duplicate routes, absolute paths, reserved names, and method tokens and precedence. (#42)
- HTTP dispatch preserves response and post-phase handlers while avoiding repeated normalization, path cloning, and authentication scans. (#42)
- Concurrent plugin registration no longer loses plugins. (#41)
- HTTP and elicitation hooks now report complete routing metadata, which
MessageViewuses for phase detection. (#42) - Subject claims preserve JSON shape, scalar arrays become
StringSetvalues, and float claims work with Cedar. (#9) - JWKS rotation no longer depends on a background runtime. (#32)
- Empty sets no longer read as missing attributes. (#7)
New Contributors
- @alexsnaps made their first contribution in #1
- @dhl123 made their first contribution in #7
- @mkoushni made their first contribution in #33
- @abdallahsamabd made their first contribution in #43
Contributors
Full Changelog: v0.1.0...v0.2.0
v0.1.0
First release. The engine was extracted from another project rather than written here, so this entry records what moved, what changed on the way, and what the public surface now is.
Added
-
The policy engine, ported from
contextforge-org/cpexwith history intact. Extracted withgit-filter-repoat source commitaed0f15, 192 files across 37 filtered commits, sogit logandgit blamereach back before this repository existed. The Rego decision point came in a second pass fromfa222c4.docs/port-provenance.mdrecords both anchors, which is what any later comparison between the two trees needs. -
praxis-policy, a host facade. One dependency instead of a dozen. It re-exports the runtime (PolicyEngine,AplOptions,register_apl) and owns registration of the bundled extensions, each behind its own feature.defaultis empty, so the bare dependency is the engine alone with nothing extra compiled in;builtinsturns on the whole set, or name a subset (jwt,oauth,elicitation-ciba,cedar,cel,opa,valkey). -
Three decision points, selectable per route. Cedar policy sets (
cedar:), inline CEL expressions (cel:), and embedded OPA/Rego via regorus (opa:). One binary serves all three; a route picks one with a step. -
Bundled extensions: multi-source JWT identity, RFC 8693 OAuth token delegation, out-of-band human approval over OIDC CIBA, and a Valkey-backed session store for taint that survives a restart.
-
Sensitive headers never reach a decision point.
Authorization,CookieandX-API-Keyare stripped from the projection a PDP receives, matched case-insensitively because headers arrive in whatever case the client sent. For a remote PDP that projection crosses the network, so this is the difference between consulting a policy service and handing it a bearer token. -
A documented path for plugins the engine does not bundle. Implement
PluginFactoryagainstpraxis_policy_core::preludeand register it withPolicyEngine::register_factoryunder thekind:your policy names. An unrecognisedkindfails at load, so a missing registration is a startup error naming the kind rather than a plugin that silently never runs. The prelude's doc example is compiled, notignored, so it cannot drift from what a plugin actually needs.
Changed
-
Renamed to the Praxis Policy Engine throughout, crates, types and docs. Deliberately unchanged: the policy document format, the
kind:strings an operator writes, and the violation codes a client sees. Those are the surface a deployment depends on, andcrates/ppe-core/tests/wire_compatibility.rspins them against a document authored before the rename. -
Edition 2024 and resolver 3, with the MSRV pinned to the same toolchain the formatter and coverage run on, so there is one Rust version to reason about.
-
Six core crates instead of eight.
praxis-policy-sdkbecamepraxis_policy_core::prelude: every name in it was already re-exported from core, so the separate crate offered a curated namespace and no dependency saving, which a module provides without a second crate to version.praxis-policy-builtinsfolded into the facade, because the feature list, the factory re-exports and the registration table all describe one set and can disagree when split across two crates. -
The PII scanner and audit logger are no longer published or bundled. They live under
reference/plugins/as worked examples a host registers itself. The scanner is regex matching with no Luhn check, and the logger writes to stderr; neither is something a policy engine should ship as supported, and both are what a deployment will want to replace. This is breaking for anyone who hadfeatures = ["pii"]or["audit"], or who namedPiiScannerFactory/AuditLoggerFactory: register the factory instead.
Fixed
-
A float in a Cedar attribute source denied every request through that step. Cedar's value model has no floating-point type, so
attributes: { score: 1.5 }failed entity construction with a message that named neither the attribute nor the reason. It now reports which key holds the float and what to supply instead. The same walker covers the operator-authored resource block. -
A quoted argument containing a lone quote aborted the policy parser instead of being read as a literal.
-
Branch outcomes are keyed rather than positional, so a concurrent effect's result can no longer be attributed to the wrong branch.
Security
-
nbfis now enforced on inbound JWTs.validate_nbfis off by default in jsonwebtoken, unlikevalidate_exp, and nothing turned it on. The module documentedauth.token_not_yet_validas a stable code and mappedImmatureSignatureto it, but that error could never be produced, so a token whose own issuer said it was not valid until later was accepted the moment it was minted. Enforced under the same leeway that already coversexp, so ordinary clock skew is still tolerated. A deployment whose IdP deliberately mints a futurenbfwill start seeing that code. -
An issuer accepting no signature algorithms now rejects every token from it, rather than treating the empty list as "any algorithm is acceptable" and handing algorithm choice to whoever minted the token.