v9.1.0 — Audit coverage made permanent + the sensitive-data lever now enforces
[9.1.0] - 2026-06-12 — Audit coverage made permanent, policy-inventory truth, a portal policy-display fix, and the sensitive-data lever now enforced
Community. v9.0.0 converged every enforcement plane onto one canonical audit vocabulary and closed the then-known early-return-deny holes by hand. v9.1.0 makes that coverage durable instead of a recurring pre-release sweep: a deterministic CI gate now fails the build whenever a Policy Enforcement Point's deny path doesn't write a canonical audit_logs row, the last remaining agent-plane gaps are closed (so the gate reports zero deferred exceptions), the built-in policy inventory is pinned to a single source of truth, a customer-portal bug that silently hid most seeded system policies is fixed, and the sensitive-data governance lever — long documented but inert — now actually enforces. One behavior change to note: the strict and compliance profiles now BLOCK credential/secret detections that previously only logged (see Fixed); set SENSITIVE_DATA_ACTION=log to retain the prior behavior. The audit and inventory changes are otherwise additive; a single data migration realigns a legacy sensitive-pattern phase column to the active action with no behavior change.
Added
- A deterministic audit-coverage CI gate that ends the recurring "this deny path forgot to audit" surprises. A new test walks every non-test
.gofile underplatform/andee/platform/, resolves from the parsed AST every function that calls the policy decision engine (EvaluateRequest/EvaluateResponse/EvaluateDynamicPolicies/EvaluatePolicy/EvaluateStepGate/EvaluateMCPPermission/EvaluateWithGracefulDegradation, plus a one-hop set of in-tree policy-delegating helpers), and fails the build if any such enforcement point's deny path doesn't write a canonical row through a blessed audit writer. Every conscious exception lives in a checked-in, reviewed allowlist with a one-line reason —BY-DESIGN(engine-internal / adapter / dry-run whose audit is the caller's responsibility, with the auditing caller named) or a tracked deferred gap. This replaces the hand-run coverage sweeps that historically found a fresh batch of holes only after they shipped. The gate's known structural limits — single-hop helper resolution and single-deny-per-function detection — are documented.
Fixed
- Every Policy Enforcement Point now audits — the gate reports zero deferred exceptions. The last agent-plane gaps the new gate flagged are closed so a denied request can no longer return before recording its decision:
- Agent
/api/requestdenials write a canonicalplane=agentrow. The agent proxy's terminal denials (circuit breaker, static / tenant policy, HITL gate, budget) previously landed only in the reader-less legacyagent_audit_logstable, so an agent-side block was invisible in the portal/decisionsfeed and the compliance exporters. Each deny now also writes a canonicalaudit_logsrow via the established decision writer, keyed on the authenticated user / tenant. - The MCP service-license permission gate audits its
403. An authorization failure now writes a canonicalplane=mcpblockedrow, keyed on the authenticated request identity (never the service-license deployment id, which is the licensee — not a customer tenant); the license key is never passed to the writer. - The legacy in-memory HITL engine audits a fail-open policy-check error. When the optional in-memory HITL gate's policy check itself errors, the engine still fails open for availability, but the errored governance verdict is no longer lost — it writes a canonical
errorstep_gaterow instead of being silently treated as an allow.
- Agent
- The
sensitive-datagovernance lever now actually enforces — on both the request and response planes. (Community) The built-in sensitive-data system policies (credentials, tokens, secrets, connection strings) resolved to a hardcodedlogregardless ofAXONFLOW_PROFILEorSENSITIVE_DATA_ACTION— the documenteddefault=warn/strict=blockposture was a no-op. Sensitive-data is now wired into the action-override map (mirroring SQL-injection and PII) and evaluated on every request plane and the response plane, so the profile and env lever drive it.⚠️ Behavior change: thestrictandcomplianceprofiles now BLOCK sensitive-data detections that previously onlylogged, and thedefaultprofile now WARNs (waslog) — on both the request and response planes (a credential-shaped LLM response is withheld under strict/compliance). This matches the long-documented Governance Profiles matrix and the System Policies reference. A deployment runningAXONFLOW_PROFILE=strict/compliancewill start blocking content carrying credentials/secrets. SetSENSITIVE_DATA_ACTION=log(or=warn) to retain the prior pass-through behavior.
- Three privileged mutations that were unaudited now write a persistent, secret-free audit row. Reusing the established writers (no new table):
- SCIM bearer-token mint / revoke. (Enterprise) Token create / revoke now emit an
admin_audit_logevent on every outcome, keyed on the session tenant; the row carries only the token id / display prefix / name through an allowlist — the minted plaintext value and its hash structurally cannot be logged. - Deployment upgrade trigger. (Enterprise) The portal's trigger-upgrade endpoint now audits every post-auth outcome (validation / lookup / cloud-API failures and the terminal success / partial / all-failed result) with the session org, initiator, and targeted service; a decode failure is recorded with a fixed label, never the request body.
- Legacy in-memory HITL gate verdicts. A
block/require_approvalverdict from the optional in-memory HITL engine now emits a canonicalstep_gateaudit_logsrow.
- SCIM bearer-token mint / revoke. (Enterprise) Token create / revoke now emit an
- The customer-portal Policies page now shows all seeded system policies instead of silently dropping them. (Enterprise / Community) The unified-policies view fetches the seeded system (static) policies from the agent over an internal-service-authenticated call. Both
docker-compose.ymlanddocker-compose.enterprise.ymldefaulted the shared internal-service secret to empty, so on a local / evaluation enterprise stack the agent rejected the unauthenticated call with a401, and the portal handler silently dropped every static policy — leaving a short list (≈17) that looked complete. The compose files now default that secret to a shared local-dev value across the agent, orchestrator, and portal (real deployments still inject their own secret), and the list / summary / effective unified-policies endpoints now return an additivepartialflag withsource_errorsso a degraded policy view is never presented as complete.
Changed
- The built-in policy inventory has a single source of truth: the migrations. (Community) A dead, never-consumed in-code seed file (
platform/agent/system_policies_seed.go) that advertised a phantom 106-policy count — and caused confusion about the real number — is deleted, along with the four test files that solely exercised it. The authoritative inventory is now pinned by a red-on-revert real-Postgres test against a fully-migrated database: 79 immutable system policies (69 static + 10 dynamic), 103 enabled out of the box (any tier), 112 seeded rows total. Stale count comments and references to the deleted file were corrected. No runtime behavior change — the deleted Go definitions never reached the database or the engine. - A legacy sensitive-pattern phase column realigned to the active action. (Community) A data migration realigns the per-phase action column on the built-in SQL-injection system policies to match their already-relaxed base action, so the stored value no longer contradicts the runtime posture (which is profile-driven). No behavior change — the runtime already used the profile/override action; this removes a stale column that misrepresented the default posture.
CI / Internal
- The audit-coverage gate runs as a dedicated, DB-free CI job wired into the test summary, so every change that adds or moves an enforcement point is checked automatically.
Full release notes: https://docs.getaxonflow.com/docs/releases/v9-1-0
Source-available under BSL 1.1. Upgrade: docker compose pull && docker compose up -d.