Lewati ke konten utama
Halaman ini belum tersedia dalam bahasa Anda, atau terjemahannya sedang menunggu tinjauan teknis. Teks asli dalam bahasa Inggris ditampilkan di bawah. Buka halaman berbahasa Inggris

Debugging your integration

A practical checklist for diagnosing policy decisions, bundle freshness, and enforcement behavior across the Python and Node SDKs. Pair this with Troubleshooting deny-on-every-call for the specific deny-deny scenarios.

Quick triage​

Before reaching for logs or a debugger, run the bundle inspector. The SDK ships with a self-serve command that decrypts the cached policy bundle, prints every rule the SDK would evaluate, and optionally simulates a guard() call against it.

# Print the policies + rules the SDK actually loaded.
controlzero debug bundle

# Dry-run a real call. Args may be a JSON object OR `key=value` pairs
# (values may contain spaces -- everything between key= boundaries
# is treated as the value).
controlzero debug bundle --simulate "database read sql=SELECT id FROM orders"

Sample output:

Control Zero -- bundle on disk

cache prefix : cz_test_loca
bootstrap path : ~/.controlzero/cache/bootstrap-cz_test_loca.json
bundle path : ~/.controlzero/cache/bundle-cz_test_loca.czpolicy
project_id : proj_test_fixture
org_id : org_test_fixture
key_version : 1

schema_version : 1
bundle_id : bundle-fixture-001
created_at : 2024-04-30T16:00:00+00:00
expires_at : 2024-05-01T16:00:00+00:00
default_action : deny
default_on_missing: deny
default_on_tamper : warn

Policies (2)

policy 0
id : pol-db-read
name : Allow database reads
is_enabled: true
priority : 10
rules : 1
rule 0
id : r-db-read-allow
effect : allow
actions : ['database:read']
...

Simulation
tool : database
method : read
args : {'sql': 'SELECT id FROM orders'}

DECISION : ALLOW
reason_code : RULE_MATCH
reason : Reads on the orders table are permitted.
rules eval'd: 1

What this answers in one shot:

  • Is the bundle even on disk? A missing bootstrap or bundle file produces an explicit error pointing at the path the SDK looked for.
  • Did the right policy version make it through? The header shows bundle_id, created_at, and the resolved default_* knobs.
  • Did the rule I expect actually load? The Policies section enumerates each rule with id, effect, actions, resources, and conditions.
  • Why did this specific call deny? --simulate runs the request through the same evaluator the SDK uses and shows the decision, reason code, and matched rule.

The output is intentionally support-thread-safe: the encryption key, signing public key, and API key are never printed.

Enable verbose logging​

Python​

import logging
logging.basicConfig(level=logging.DEBUG)

from controlzero import Client
cz = Client(api_key="cz_live_...")

You can also flip just the controlzero logger via the env var:

export CZ_DEBUG=1
python my_agent.py

CZ_DEBUG=1 (or true/yes/on) flips the controlzero logger to DEBUG at construction. Cheap escape hatch when you cannot edit the code.

Node.js​

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

// SDK logs go to stderr by default. To see them, set the standard
// Node debug-logging env var:
// DEBUG=controlzero:* node my_agent.js
const cz = await Client.create({ apiKey: 'cz_live_...' });

Understand the action evaluated​

Every guard() call evaluates a tool:method action string. For database calls, the SDK also derives a canonical SQL semantic class from the sql argument and evaluates that in parallel. So a single call may match against two action shapes:

CallerPer-call actionCanonical class
cz.guard("database", method="query", ...)database:querydatabase:read
cz.guard("database", method="execute", ...)database:executedatabase:write
cz.guard("database", method="delete", ...)database:deletedatabase:write
cz.guard("database", method="DROP", args={"sql": "DROP TABLE x"})database:DROPdatabase:admin

A rule keyed on either form fires. Legacy names (database:query, database:execute, database:delete) and canonical names (database:read, database:write, database:admin) both work.

Audit log columns​

Every decision is logged. The columns most useful for debugging:

ColumnMeaning
decisionallow, deny, or warn. The final outcome.
toolTool name passed to guard() (e.g., database).
methodMethod name passed to guard() (e.g., query).
policy_idUUID of the rule that matched. Empty for synthetic denies (no rule fired).
policy_matchedHuman-readable name of the matched rule.
reason_codeMachine-readable enum: RULE_MATCH, NO_RULE_MATCH, NO_ACTIVE_POLICIES, BUNDLE_MISSING, BUNDLE_TAMPERED, MACHINE_QUARANTINED, NETWORK_ERROR, DLP_BLOCKED.
reasonFree-text explanation. Safe to display to users.

See Enforcement Behavior for the full reason_code reference and which surface emits which codes.

Bundle on disk​

Hosted-mode SDKs cache the signed policy bundle locally so they keep working offline. The cache layout under ~/.controlzero/cache/:

FileContents
bootstrap-<prefix>.jsonProject ID, org ID, encryption key, signing public key.
bundle-<prefix>.czpolicyRaw signed bundle bytes (.bin is the legacy name).
bundle-<prefix>.metaETag and checksum for conditional fetches.

The <prefix> is the first 12 characters of the API key. The bundle is encrypted at rest and signed; the SDK verifies the signature before every load.

To force a fresh bundle pull from the dashboard:

# Python
cz.refresh()
// Node
await cz.refreshPolicies();

As a last resort, delete the cache and re-bootstrap on the next call:

rm -rf ~/.controlzero/cache/

The next guard() call will re-fetch keys and bundle from the backend.

Common errors​

SymptomCodeFix
API key rejected (401) / HostedAuthError on startupE1101The key is unknown, revoked, or expired. Generate a new key at Settings -> API Keys and set CONTROLZERO_API_KEY. If you just rotated a key, confirm the running process picked up the new value.
No API key foundE1103Export CONTROLZERO_API_KEY=cz_live_... or run controlzero install <agent> --api-key=....
API key format invalidE1104The key must be cz_live_<64 hex> or cz_test_<64 hex>. Copy it exactly, no quotes or URL-encoding.

When to contact support​

Open a support ticket when:

  • A reason_code you do not recognize keeps appearing.
  • The bundle on disk is older than a published version and refresh() does not pull a newer one.
  • You see BUNDLE_TAMPERED without an explanation.

Include in the ticket:

  1. The SDK language and version (e.g., python 1.4.6, @controlzero/sdk 1.8.2).
  2. The audit row reason_code, tool, method, and policy_id columns for the failing call.
  3. The policy YAML (or a sanitised excerpt) of the rule that should match.
  4. A minimal repro: the guard() call shape with arguments redacted to keys only.
  5. Whether refresh() returned True (bundle changed) or False (no change) after the last dashboard publish.