Skip to content

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.