Skip to main content

Enforcement Behavior

Supported modes: Hosted Hybrid Local Available in: Free Solo Teams

Every Control Zero surface (SDKs, Gateway, coding-agent hooks, browser extension) evaluates your policies the same way, using the same three knobs and the same machine-readable decision codes. This page documents the contract so a policy you author once behaves identically everywhere.

The three knobs

A policy bundle carries three top-level settings that govern what happens outside the happy path of a rule match. All three ship inside the signed, encrypted bundle, so a tampered file cannot flip an organization from deny to allow.

KnobValuesFires whenDefault
default_actiondeny | allow | warnBundle loaded, zero rules matched the call.deny
default_on_missingdeny | allowClient is enrolled but no bundle can be loaded (never synced, network error, bad key).deny
default_on_tamperwarn | deny | deny-all | quarantineBundle verification fails, or machine is in local quarantine.warn

When to reach for each

default_action is the knob that answers the question, "I attached a policy that blocks deletes. What happens to writes?"

  • Keep deny (the default) when you want the policy you authored to be the complete list of allowed operations -- a true allow-list.
  • Set allow for audit-only rollouts or for policies that only block a specific operation class (e.g. "block deletes; everything else is fine"). This is the fix for the classic "I attached db-read-only, why is my AI blocked from everything?" scenario.
  • Set warn during a soft rollout to see what the blocks would be before flipping to deny.

default_on_missing exists separately from default_action because "I have no rule that matches this call" is a different question from "I have no bundle at all."

  • Keep deny to fail closed if a machine cannot pull its bundle.
  • Set allow to avoid bricking the agent during an outage. Coding-agent hooks default to this today.

default_on_tamper picks the severity of the response when the bundle signature does not verify or a local state file was altered.

  • warn just logs.
  • deny denies the one call that triggered the check.
  • deny-all and quarantine are aliases: the machine enters a quarantine state and denies every tool call until recovery (re-enrollment or a fresh policy pull).

Override precedence

The three knobs can be set at three levels. Lower levels override higher levels:

  1. Organization -- the default for every project in the org, set from the dashboard.
  2. Project -- overrides the org default for a specific project.
  3. User YAML -- in local/unenrolled mode only, a controlzero.yaml with a settings: block overrides the project default.

In hosted mode the server resolves org -> project -> canonical default at bundle build time, so the SDK only sees the single resolved triple. In local mode the SDK reads settings: from the YAML directly.

Example user YAML:

version: '1'
settings:
default_action: allow
default_on_missing: deny
default_on_tamper: quarantine
rules:
- deny: 'delete_*'
reason: 'No deletes in staging.'

The eight decision codes

Every decision carries a reason_code alongside the free-text reason. Automation should branch on reason_code. The free-text reason can be re-worded or translated without breaking downstream consumers.

reason_codeMeaningTypical decision
RULE_MATCHA user-authored rule fired and its effect is the decision.Whatever the rule's effect is.
NO_RULE_MATCHBundle loaded cleanly, no rule matched. Decision follows default_action.Follows default_action.
NO_ACTIVE_POLICIESBundle is structurally empty (zero attached policies). Synthetic rule fires.Follows default_action.
BUNDLE_MISSINGClient is enrolled but the bundle cannot be loaded. Decision follows default_on_missing.Follows default_on_missing.
BUNDLE_TAMPEREDBundle verification failed. Decision follows default_on_tamper.deny under deny / deny-all.
MACHINE_QUARANTINEDMachine is in the local quarantine state. Every tool call is denied until recovery.Always deny.
NETWORK_ERRORBackend is unreachable and no cached bundle is available. Follows default_on_missing.Follows default_on_missing.
DLP_BLOCKEDA DLP rule with action: block matched the tool arguments and overrode a would-be allow.Always deny.

Which surface emits which

Not every surface can emit every code. The table below lists which codes a given surface is expected to produce.

SurfaceRULE_MATCHNO_RULE_MATCHNO_ACTIVE_POLICIESBUNDLE_MISSINGBUNDLE_TAMPEREDMACHINE_QUARANTINEDNETWORK_ERRORDLP_BLOCKED
Python SDK guard()YYYYYYYY
Node SDK guard()YYYYYYYY
Go SDK Guard()YYYYYYYY
Compiled policy engineYYY-----
Gateway request guardYYYYY-Y-
Coding-agent hook-checkYYYY-Y--
Browser extensionYY-Y---Y
  • Server-side surfaces (Gateway, compiled engine) have no per-machine state, so they do not emit MACHINE_QUARANTINED.
  • The compiled engine receives only rules + settings; it has no bundle-loading concept, so BUNDLE_MISSING / BUNDLE_TAMPERED / NETWORK_ERROR are not reachable there.
  • Browser extension DLP is a separate decision surface (regex against text content), so its matches emit DLP_BLOCKED but not the other DLP-adjacent codes.

Checking the effective values

In hosted mode you see the resolved knobs on the dashboard's Project Settings page.

In local mode, or to double-check what a machine is actually enforcing, SDK callers can introspect the settings at runtime.

Python:

import controlzero

cz = controlzero.Client()
settings = cz.policy_settings
print(settings.default_action, settings.default_on_missing, settings.default_on_tamper)

Node:

import { Client } from '@controlzero/sdk';

const cz = new Client();
const s = cz.policySettings;
console.log(s.default_action, s.default_on_missing, s.default_on_tamper);

Go:

c, _ := controlzero.New()
s := c.PolicySettings()
fmt.Println(s.DefaultAction, s.DefaultOnMissing, s.DefaultOnTamper)

The controlzero status CLI (shipped with the Python SDK) also prints the effective values under the "Enforcement defaults" section. Run it from any project directory to see which triple your SDK will enforce on the next guard() call.

$ controlzero status
...
Enforcement defaults
default_action: allow (from project)
default_on_missing: deny (from org)
default_on_tamper: quarantine (from user YAML)

Compatibility

Older SDK versions that do not understand the three knobs fall back to deny / deny / warn, which matches the hard-coded pre-Phase-2 behaviour. Upgrading is non-breaking in both directions: a new bundle is safe for old SDKs, and a new SDK is safe against an old bundle.