First-class decision & effect auditing (the audit seam) - #166
Draft
terylt wants to merge 8 commits into
Draft
Conversation
…iting of denies. Signed-off-by: Teryl Taylor <terylt@ibm.com>
Signed-off-by: Teryl Taylor <terylt@ibm.com>
Brings in the identity work (5 commits) before building effect auditing, which touches delegation/identity. Only executor.rs conflicted: dev's `payload_modified` flag and the audit seam's `decisions` log each appended a trailing parameter to the phase functions — kept both, ordered decisions then payload_modified. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t.begin_effect, fail-closed durability. Signed-off-by: Teryl Taylor <terylt@ibm.com>
… recovery + reconciliation seam. Signed-off-by: Teryl Taylor <terylt@ibm.com>
Signed-off-by: Teryl Taylor <terylt@ibm.com>
…ction. Signed-off-by: Teryl Taylor <terylt@ibm.com>
…mint effect-audit. Signed-off-by: Teryl Taylor <terylt@ibm.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
CPEX could not audit its own enforcement. An observation-only plugin — the
reference
audit-logger, or an OCSF emitter — only ever sees allowedpost-hook traffic; a blocked call, an approval rejection, a delegation failure,
or an injection-stop produced no audit record at all. And irreversible
external actions a plugin causes (a token mint, an approval grant) were not
recorded crash-safely — a process that died between "about to mint" and "minted"
left no trace.
This PR makes auditing first-class in the executor. The core owns a decision
record and emits it at every verdict; any audit sink consumes it. Irreversible
effects are recorded write-ahead, crash-safe, and reconcilable. Each decision
carries the provenance needed to reconstruct a causal graph (span + taint +
content hash). The OAuth delegator is wired as the first real consumer.
Everything is opt-in — no behavior changes unless the operator configures a
sink, an effect WAL, or content provenance.
What's included
Decision auditing.
AuditHookfamily, auto-attached by thePluginManager, fired at theexecutor's verdict return points (not a pipeline phase) — so allow, deny,
and modify all produce a record. "Which phase, before or after which deny"
stops being a question.
DecisionLog— executor-owned, handed only to audit sinks, never placedon
PluginContext(the thing that records must not be able to change what itrecords). Carries the ordered plugin steps and the terminal verdict.
Effect auditing (irreversible external actions).
emit_effectcallsext.begin_effectto durably record intent before theact (fail-closed — no durable record, no act) and
ext.complete_effectto record the outcome (
confirmed/rejected/unknown).FileEffectLog— a durable WAL (append +fsync, serialized againstconcurrent writers, self-compacting past a configurable threshold).
PluginManager::recover_effectscompacts completed effectsand reconciles crash-orphaned ones against the issuing participant via an
EffectReconcilerseam. The default (LogUnknownsReconciler) logs and leavesthem
unknown— correct for any participant with no lookup-by-key.Extensions::perform_effectbrackets the whole protocol so a caller cannotskip, reorder, or forget it.
Provenance on the decision node (for downstream causal-graph reconstruction).
DecisionLog::span()carries a W3Ctrace_id/span_id/parent_span_id(child-span model: a fresh span whoseparent is the request's span), set by the executor at pipeline entry.
diffs them against the final labels to show the taint this node added.
PluginPayload::audit_bytes()(per-type opt-invia
impl_plugin_payload!(_, audit_serialize)) feeds asha256:content ref.The executor hashes the payload at entry behind
capture_content_provenance;the sink hashes the output lazily. Only digests are kept, never content —
provenance without re-spilling the data a PII scanner exists to redact.
First real consumer.
cpex-plugin-delegator-oauthbrackets both mint legs — the workloadclient_assertionbase-token mint and the RFC 8693 exchange — withbegin_effect/complete_effect, mapping a successful exchange toconfirmed,a definitive IdP rejection to
rejected, and a timeout/unreachable IdP tounknown(reconciled later, never assumed minted).Reference sink.
audit-loggernow renders the verdict, ordered steps,span,taint, and(when enabled)
contenthashes.Notable design decisions
phase; emitting at the executor's return points is complete by construction.
DecisionLogreaching an audit handlerbut never
PluginContextis a real security property; anAuditHookfamily(not a manager special-case) makes "sees verdicts, cannot influence them"
type-level.
Err → unknown, notrejected. A failed mint may still have landed at theparticipant, so recovery reconciles it rather than assuming it didn't happen.
The delegator, which knows a clean 4xx from a timeout, maps precisely.
self-describing
EffectRecordand does a keyed ledger lookup — participant-specific at most, and today no participant offers one, so the default suffices.
Opt-in / compatibility
PluginPayload::audit_bytes()has adefault (
None);AuditHook/effect emitter/WAL/hashing all engage only whenconfigured (
effect_log_path,capture_content_provenance,emit_effect).plugin_settings:effect_log_path,effect_log_compaction_threshold,capture_content_provenance— all defaultto off.
Testing
cpex-core,audit-logger, anddelegator-oauth:verdict-point emit, capability gating, WAL durability / concurrent-append
integrity / recovery+compaction / reconciliation,
perform_effectstatemapping, span child-model, taint delta, content-hash gating, and the OAuth
delegator emitting
prepared → confirmed/rejectedagainst a mock IdP.cargo fmtclean,cargo clippy --workspace --all-targetsclean, fullworkspace test green.
Out of scope (follow-ups)
ocsf-auditmappingspan()/on_effectinto OCSFtrace/spanand theAuthentication class.
docs/effect-ledger-integration-note.md; would makeunknowntruly resolvableand, if it sits in the mint path, deliver v2 structural enforcement too.