Per-Org Detection Posture
Concept page. This explains how one deployment can enforce different detection actions for different teams. For the detectors themselves, see PII Detection; for how each decision is recorded, see Audit Logging.
Available on platform v8.7.0+. Since v11 an organization's override is the only thing that replaces a stored detection policy action; no environment variable or profile sets one.
A single AxonFlow deployment often serves more than one team, and those teams do not always want the same enforcement behavior. A customer-facing chat assistant usually wants PII redacted so the conversation keeps flowing; a nightly batch export usually wants the same PII to block the job outright. Before v8.7.0 the detection action was a single deployment-wide environment setting, resolved once at boot - so those two teams could not coexist on one platform. v11 removed those environment settings entirely.
Detection posture makes the enforcement action per-organization and per-category. Each org can override the action stored on its policies for a specific detection category; anything it does not override keeps the action stored on the matched policy. An override re-actions the platform's policies in its category, on the planes that apply organization overrides (the Decision API, Gateway Mode's pre-check, MCP, the OpenAI-compatible route, Proxy Mode's request, the orchestrator's response filter and the policy test surface). It does not reach the 22 organization-editable policies, among them the dangerous-command, DROP TABLE and TRUNCATE prevention and two SQL-injection blocks: your organization changes those in its own typed policy document.
For the detection categories, the action stored on the matched policy decides the runtime outcome. An organization's recorded override is the only thing that replaces it, and it replaces it for every platform policy in the category. A pii-* policy row that stores block denies until the organization records a weaker pii override; under pii=redact it resolves to redact. The per-plane matrix, the resolution order, and the planes that do not apply the override are in System Policies: what the Action column means at runtime.
The model
The effective action for a given (org, category) is resolved as:
effective action = per-org override ELSE the action stored on the matched policy
- The stored policy action is the action on the matched policy row: the seeded action of a shipped system policy (see System Policies), or the action your organization gave that one policy, which in v11 every edition can change. It is the baseline every org starts from. Since v11 no environment variable or profile changes it; the removed variables are listed in Environment Variables Reference.
- A per-org override replaces the stored action for every platform policy in one category, for one organization. Set none and the stored policy actions decide.
Overrides are stored per organization with row-level security, so one org can never read or set another org's posture.
Categories you can set
| Category | What it governs |
|---|---|
pii | The 18 platform pii-* policies |
sqli | The 38 platform security-sqli policies, including the destructive-statement rows (DROP TABLE, TRUNCATE, DELETE without WHERE, ALTER, GRANT, REVOKE, CREATE USER) |
dangerous_query | Maps onto no policy category, so recording it changes no policy outcome. Destructive SQL statements are security-sqli rows and follow sqli. |
dangerous_command | The four platform policies in security-dangerous: the indirect prompt-injection guards. The dangerous shell and command blocks share the category name but are organization-editable policies, which this override does not reach. |
obligation_fallback | Not a detector. What to do when a policy decided a request body must be redacted but the calling enforcement point structurally cannot mask it (e.g. an Envoy ext_authz leg, which is headers-only). Accepts block or log only - default log. Requires platform ≥ 9.11.0. See Decision Mode → Seam capabilities. |
sensitive-data, the compliance categories (compliance-*, fincrime) and admin-access have no override category: their stored action always decides. To change them, change the policy's action.
Actions you can set
| Action | Behavior |
|---|---|
block | Hard-deny the request (or response) when the category matches. |
redact | Mask the matched value and continue. |
warn | Detect and record, but do not modify content. |
log | Detect and record only (lowest-friction). |
Not every action is meaningful for every category. obligation_fallback accepts only block and log: redact is precisely what the seam in question cannot do, and warn has no enforcement distinct from log on that axis. The API rejects the other two for that category and names the ones it accepts.
There is no "governance off" action - a category is always at least detected and recorded. To run a category permissively, set it to warn or log; you cannot disable detection through posture.
Where posture is enforced
A team's posture has to hold on every path a request can take, not just the one someone happened to test. v8.7.0 wires the per-org override into all three enforcement planes:
| Plane | What it covers |
|---|---|
| Agent request/response | The MCP check-input / check-output paths, the Gateway pre-check, POST /api/v1/decide, the proxy, the policy-test surface, and the OpenAI-compatible endpoint. |
| Orchestrator response redaction | The proxy / gateway / MAP LLM-response redaction plane, which runs in a separate binary from the agent. |
| Customer-portal write path | The authenticated surface platform teams use to set posture (below). |
In v11 the override reaches the policy decision engine on every plane it decides. The two v10.x exceptions, the proxy's POST /api/v1/request tier engine (which read the row's base action) and Cowork / Claude Code OTEL ingest (which force-redacted at the collector), end when those planes move to the engine.
Because the agent and the orchestrator are separate processes, an override is applied independently on each. On the hot path neither process makes a per-request database call - each holds a short-TTL per-org cache. If the organization's overrides cannot be read, the decision fails closed: the request is refused (503 with detection_overrides_unreadable on POST /api/v1/decide) rather than decided without the posture the organization recorded.
Seeing when posture weakened a stored action
From platform v9.19.0 to v10.x, an override that resolved a matched policy's stored action downward put an advisory reason on the decision API's allow verdict, naming the policy, both actions and the override category. In v11 that reason is no longer emitted: the decision names the policy that decided, and a matched policy whose obligation the wire does not carry is named in the allow verdict's reasons. axonflow_agent_policy_stored_action_displaced_total{category,stored,resolved} still counts downward displacements on the planes that resolve an action through the shared conversion, so an alert on an override that is quietly weakening blocking policies keeps working.
Tightening (an override taking a warn policy to block) is the override doing its job and is not counted.
Setting posture from the portal
Enterprise. The customer portal exposes a Detection Posture page under Settings → Governance (/settings/detection-posture). For each category you can pick an action or leave it on Default (policy action), which records no override so the stored policy actions decide.
The write path is authenticated and gated by the sso:configure permission - the same gate as SCIM token management - so only a user who can already configure identity for the org can change its enforcement posture. The organization is taken from the authenticated session, never from a request header or body, so a spoofed X-Tenant-ID can't steer the write to another org.
Posture changes propagate to the enforcement planes within the cache TTL (a short, bounded staleness window - seconds, not minutes). The TTL is deployment-tunable via AXONFLOW_DETECTION_OVERRIDE_TTL_SECONDS (default 60s, clamped to 5-600s).
The API surface
| Method | Endpoint | Purpose |
|---|---|---|
GET | /api/v1/detection-posture | List the org's current per-category overrides. |
PUT | /api/v1/detection-posture/{category} | Set the action for one category (body {"action":"block"}). |
DELETE | /api/v1/detection-posture/{category} | Clear the override (the category returns to the stored policy actions). |
All three require an authenticated session with sso:configure. A request without the permission returns 403; an unauthenticated request returns 401.
The API accepts only the supported categories and actions documented above. Invalid categories return 400 with INVALID_CATEGORY; invalid actions return 400 with INVALID_ACTION. There is no off action. Deleting an override is the way to return a category to the stored policy actions.
Each set or delete attempt is recorded as an admin audit event in admin_audit_log, with updated_by:
DETECTION_POSTURE_SETDETECTION_POSTURE_DELETE
Audit details include the category and, for set operations, the new action. The audit path records both success and failure outcomes and does not include secrets.
A concrete example
A deployment runs on the shipped PII policy actions, which warn or log on most PII requests so interactive products stay usable. One org, nightly-exports, runs a batch pipeline where any PII in the output is a compliance incident, not something to mask:
- A platform owner for
nightly-exportsopens Settings → Governance → Detection Posture. - They set PII to Block.
- The override is written for
(nightly-exports, pii). - Within the cache TTL, every plane that applies the override - agent request/response, orchestrator LLM-response redaction - enforces block for that org's PII, while every other org continues on the stored policy actions.
No deployment restart, no global config change, no impact on any other tenant.
Community vs Enterprise
| Capability | Community | Enterprise |
|---|---|---|
| Stored policy actions decide when no override is recorded | ✅ | ✅ |
| Disable, re-enable or re-action one shipped policy for your organization | ✅ | ✅ |
| Author policies of your own as a typed policy document | ✅ | ✅ |
| Per-org / per-category posture enforcement (agent + orchestrator planes) | ✅ | ✅ |
Portal write path to set posture (/settings/detection-posture, sso:configure) | ✅ |
The enforcement planes are part of the platform binaries, so per-org posture is honored in both editions. The authenticated portal surface for setting posture is part of the Enterprise customer portal; a Community deployment can seed overrides directly.
The deployment-wide action variables (PII_ACTION, SQLI_ACTION, DANGEROUS_COMMAND_ACTION, SENSITIVE_DATA_ACTION, their MCP_* / GATEWAY_* variants, AXONFLOW_PROFILE, AXONFLOW_ENFORCE and the rest) no longer set an action. A deployment that still sets one keeps running, logs one boot WARN per variable, and increments axonflow_ignored_posture_env_total{name="<NAME>"}. The full list is in Environment Variables Reference.
See also
- PII Detection - what each detector matches, and the context-gating that prevents false positives
- Audit Logging - how an enforcement action is recorded
- SCIM 2.0 Provisioning - the
sso:configurepermission and group-to-role mapping - Decision Mode architecture - the PDP/PEP request and response planes posture applies to
Rollout Checklist
Use this page as one layer of the broader governance rollout:
- decide where the rule belongs with Policy Hierarchy
- test the request path with Runtime Request Paths
- connect review workflows to HITL Approval Gates when a block should become a human decision
- compare Community vs Evaluation vs Enterprise when simulation, evidence export, SSO, SCIM, or portal operations become requirements
