Skip to content
Open
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
### Added

- **Out-of-process host for existing Python CPEX plugins.** A new `cpex-hosts-python` crate registers `kind: isolated_venv`, running an unmodified Python CPEX plugin in its own cached virtualenv as a subprocess instead of in-process through the PyO3 bindings. Each plugin gets a venv keyed by a SHA-256 fingerprint of its requirements + manifest (rebuilt when either changes, `rmtree`d rather than upgraded in place so a removed dependency actually disappears), and the host drives the Python framework's `worker.py` over a newline-delimited JSON stdio protocol. Hook payloads, `context`, and the capability-filtered `Extensions` view cross as JSON; returns come back as a serialized `PluginResult`, with `modified_extensions` merged through the executor's existing copy-on-write tier validation — the host implements no tier logic of its own. Failure modes the executor cannot otherwise distinguish (venv build failure, worker death mid-flight, a task over `max_content_size`, per-invocation timeout) map to distinct `PluginError`s carrying a stable `code` and structured `details`, so the executor's configured `on_error` policy applies unchanged. Pure Rust plus a subprocess — no libpython link, so the crate is in `default-members` and a plain `cargo build` does not require a Python dev install. The wire contract is pinned in `docs/specs/extensions-wire-contract.md`; CMF §3 remains normative for the extension slots themselves. (#149)
- **First-class decision and effect auditing.** CPEX can now audit its own enforcement — every allow, deny, and modify — instead of only the allowed post-hook traffic an observation plugin happened to see. A new `AuditHook` family, auto-attached by the `PluginManager`, fires at the executor's verdict return points (not a pipeline phase), so a blocked call, a scope narrowing, and a clean allow all produce a record. Each carries a `DecisionLog` — executor-owned and handed only to audit sinks, never placed on `PluginContext` — with the ordered plugin steps, the terminal verdict, the invocation's W3C trace span (`trace_id` / `span_id` / `parent_span_id`, child-span model: a fresh span whose parent is the request's span, for causal-DAG reconstruction), the taint labels the request arrived with, and, opt-in, a content hash of the payload at entry. Irreversible external effects (a token mint, an approval grant) are audited as their own events through a capability-gated, write-ahead protocol: a plugin holding `emit_effect` calls `ext.begin_effect` to durably record intent *before* the act (fail-closed — no durable record, no act) and `ext.complete_effect` to record the outcome (`confirmed` / `rejected` / `unknown`). A durable `FileEffectLog` write-ahead log (append + `fsync`, serialized against concurrent writers, self-compacting at a configurable threshold) makes the intent crash-safe; startup recovery (`PluginManager::recover_effects`) compacts completed effects and reconciles crash-orphaned ones against the issuing participant through an `EffectReconciler` seam (the default logs and leaves them `unknown`). `Extensions::perform_effect` brackets the two-phase protocol so a caller cannot skip, reorder, or forget it. Opt-in throughout: no effect WAL and no content hashing unless `plugin_settings.effect_log_path` / `plugin_settings.capture_content_provenance` are set. (#XXX)
- **The OAuth delegator emits write-ahead audit for the tokens it mints.** `cpex-plugin-delegator-oauth` now brackets both mint legs — the workload `client_assertion` base-token mint and the RFC 8693 exchange — with `begin_effect` / `complete_effect`, mapping a successful exchange to `confirmed`, a definitive IdP rejection to `rejected`, and a timeout or unreachable IdP to `unknown` (reconciled later, never assumed minted). Effects are emitted only when the operator grants the plugin `emit_effect` and configures an effect WAL; otherwise the mint path is unchanged. There is deliberately no OAuth-specific reconciler — an IdP exposes no lookup by mint key, so the core default (log and leave `unknown`) is the honest behavior. (#XXX)
- **The reference `audit-logger` renders the new provenance.** Decision records now include the invocation `span`, a `taint` object (the labels the request arrived with vs. the labels after the pipeline — their difference is the taint this node added), and, when content provenance is enabled, a `content` object with the input and output payload hashes (`sha256:…`, digests only, never the content itself). (#XXX)

### Fixed

Expand Down
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 4 additions & 10 deletions builtins/plugins/audit-logger/src/factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,10 @@ impl PluginFactory for AuditLoggerFactory {
fn create(&self, config: &PluginConfig) -> Result<PluginInstance, Box<PluginError>> {
let logger = Arc::new(AuditLogger::new(config.clone())?);

if config.hooks.is_empty() {
return Err(Box::new(PluginError::Config {
message: format!(
"plugin '{}' (cpex-plugin-audit-logger): `hooks:` must list at \
least one CMF hook to audit (e.g. cmf.tool_pre_invoke)",
config.name
),
}));
}

// With no `hooks:` listed the logger runs in audit-only mode — it
// registers no CMF post-hook handlers and instead auto-attaches as a
// decision-audit sink (see `Plugin::as_audit_handler`). Listing hooks
// keeps the legacy per-hook observation behavior.
let handlers: Vec<_> = config
.hooks
.iter()
Expand Down
Loading
Loading