Skip to content

Releases: praxis-proxy/policy

v0.5.0

v0.5.0 Pre-release
Pre-release

Choose a tag to compare

@araujof araujof released this 13 Oct 19:28
d29a6a7

Added

  • Added the opt-in delegator/vault builtin 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 readOnlyHint and destructiveHint. Workload attestation timestamps are available as RFC3339 strings and Unix seconds; APL can compare attested_at_epoch against a fixed threshold. (#175)
  • Added secret-provider health details through secrets_health and atomic value/generation reads through SecretRef::snapshot. (#191)

Changed

  • Breaking: Secret configuration reloads that change secrets: now fail; restart to apply those changes. The env provider rejects unused settings. secrets_last_success retains its last successful timestamp after a failure, so use secrets_health to 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::resolve and assertions::render now take a secret store, AssertionsConfig::validate takes secret configuration, SourcePath::capability returns Option<Capability>, and SourcePath gains a Secret(String) variant and is non-exhaustive. (#174)
  • Breaking for custom CEL functions: CelResolver::with_functions and its registration callback now return Result. Conflicts with standard-library overloads fail construction; later registration failures deny even with on_error: allow. Hosts that need such overloads must use a CEL Env overload. (#185)
  • CEL 0.15 counts Unicode code points instead of UTF-8 bytes in string size(). exists() returns true if 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's float_roundtrip feature. 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 seven BAG_*_PREFIX constants. 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_scopes permits 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_teams mapping, 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

Contributors

Full Changelog: v0.4.1...v0.5.0

v0.4.1

v0.4.1 Pre-release
Pre-release

Choose a tag to compare

@araujof araujof released this 06 Oct 02:49
a178f14

Added

  • Optional bounded PDP decision cache. A cache: block on a global.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.md states 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: route args; RoutePayload struct literals require structured.
    See migration guidance. (#144)
  • Breaking: cloned extensions drop HTTP transport, and effects are limited to sequential and transform modes. (#84)
  • Missing required LLM requests and overly deep structured input now deny. (#144)
  • PDP-generated errors hide input values; Cedar schemas require structured_context: true for 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

Contributors

Full Changelog: v0.4.0...v0.4.1

v0.4.0

v0.4.0 Pre-release
Pre-release

Choose a tag to compare

@araujof araujof released this 29 Sep 20:42
fb94b2b

Added

  • Added named secrets: providers and values, startup resolution, and host-driven refresh. Built-in file and env providers 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 quota plugin backed by Limitador for per-principal LLM token budgets. (#116)
  • Added the ibmverify JWT claim mapper preset for scalar or array collection claims. (#134)
  • Added an identity/api-key resolver with file and HTTP directories, shared identity mapping, and bounded caching. Configure the backend with provider:; the facade exposes the resolver through the api-key feature. (#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 policy kind strings remain the same. Direct users of a retired extension crate must switch to praxis-policy-builtins with 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::new now takes a MappingProfile. The operator's claim_map: configuration is unchanged. (#119)
  • Exposed execute_with_retry for secret providers using the host transport. (#103)
  • Replaced serde_yaml with yaml_serde and updated regorus to 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

Contributors

Full Changelog: v0.3.1...v0.4.0

v0.3.1

v0.3.1 Pre-release
Pre-release

Choose a tag to compare

@shaneutt shaneutt released this 22 Sep 23:45
90e7f23

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

v0.3.0 Pre-release
Pre-release

Choose a tag to compare

@araujof araujof released this 18 Sep 18:20
8b28974

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_PREFIX constant. (#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-core self-test dependency path-only so release packaging can resolve it. (#71)
  • Corrected read_labels and read_workload capability 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 rustls to 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

Contributors

Full Changelog: v0.2.0...v0.3.0

v0.2.0

v0.2.0 Pre-release
Pre-release

Choose a tag to compare

@araujof araujof released this 03 Sep 21:03
9ee972f

Upgrading from 0.1.0? Configurations require changes: see docs/upgrade-apl.md.

docs/content/apl/apl-grammar.md documents 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, with http.response and http.status supporting response policy. (#42)
  • Hook declarations now include their routing metadata. (#38)
  • Hosts provide the transport used by PPE plugins, gated by the perform_http capability. (#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 only run(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, and identity-jwt refreshes 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_config path, 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_router were removed. (#38, #44)

Fixed

  • Praxis integration fixes cover false narrowed-plugin alarms, explicit ring selection, 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 MessageView uses for phase detection. (#42)
  • Subject claims preserve JSON shape, scalar arrays become StringSet values, 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

Contributors

Full Changelog: v0.1.0...v0.2.0

v0.1.0

v0.1.0 Pre-release
Pre-release

Choose a tag to compare

@araujof araujof released this 14 Aug 20:18
2f6baa5

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/cpex with history intact. Extracted with git-filter-repo at source commit aed0f15, 192 files across 37 filtered commits, so git log and git blame reach back before this repository existed. The Rego decision point came in a second pass from fa222c4. docs/port-provenance.md records 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. default is empty, so the bare dependency is the engine alone with nothing extra compiled in; builtins turns 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, Cookie and X-API-Key are 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 PluginFactory against praxis_policy_core::prelude and register it with PolicyEngine::register_factory under the kind: your policy names. An unrecognised kind fails 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, not ignored, 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, and crates/ppe-core/tests/wire_compatibility.rs pins 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-sdk became praxis_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-builtins folded 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 had features = ["pii"] or ["audit"], or who named PiiScannerFactory / 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

  • nbf is now enforced on inbound JWTs. validate_nbf is off by default in jsonwebtoken, unlike validate_exp, and nothing turned it on. The module documented auth.token_not_yet_valid as a stable code and mapped ImmatureSignature to 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 covers exp, so ordinary clock skew is still tolerated. A deployment whose IdP deliberately mints a future nbf will 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.