controlzero 1.13.3__tar.gz → 1.13.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {controlzero-1.13.3 → controlzero-1.13.5}/.gitignore +4 -1
- {controlzero-1.13.3 → controlzero-1.13.5}/CHANGELOG.md +20 -1
- {controlzero-1.13.3 → controlzero-1.13.5}/PKG-INFO +1 -1
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/__init__.py +1 -1
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/action_validator.py +1 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/bundle.py +128 -17
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/enforcer.py +31 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/tool_extractors.json +4 -1
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/audit_remote.py +23 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/agent_config.py +1 -1
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/hosts/codex_cli.py +15 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/kiro_adapter.py +11 -3
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/main.py +444 -141
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/gemini-cli.yaml +2 -1
- controlzero-1.13.5/controlzero/cli/templates/kiro/ide-pre-tool-use.kiro.hook +15 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/kiro/kiro.yaml +4 -3
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/client.py +17 -2
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/hitl/secret_leak_guard.py +5 -1
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/hook_integrity.py +1 -1
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/hosted_policy.py +91 -10
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/policy_loader.py +19 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/pyproject.toml +1 -1
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_cli_extractor_integration.py +43 -0
- controlzero-1.13.5/tests/test_cross_adapter_shell_enforcement.py +335 -0
- controlzero-1.13.5/tests/test_deny_wins_default_catchall_1620.py +130 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_dlp_mask.py +1 -1
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hitl_6a_request_approval.py +1 -1
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hitl_6a_secret_leak_guard.py +8 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hitl_phase2b_protocol.py +54 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hitl_reason_codes.py +6 -3
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hook_integrity.py +1 -1
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hosts_adapter.py +43 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_kiro_cli_e2e.py +12 -4
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_kiro_cli_v210_hooks.py +16 -6
- controlzero-1.13.5/tests/test_kiro_install_standardisation.py +218 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_synthetic_policy_id_t79.py +8 -6
- controlzero-1.13.5/tests/test_version_aware_fail_closed.py +211 -0
- controlzero-1.13.3/controlzero/cli/templates/kiro/ide-pre-tool-use.kiro.hook +0 -15
- {controlzero-1.13.3 → controlzero-1.13.5}/Dockerfile.test +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/LICENSE +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/README.md +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/__main__.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/__init__.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/_messages.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/action_aliases.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/condition_ops.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/credential_hook.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/credential_scanner.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/credentials_data/__init__.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/credentials_data/built_in.yaml +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/dlp_scanner.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/hook_extractors.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/io_capture_producer.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/source_labels.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/types.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/audit_local.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/canonical.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/__init__.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/__main__.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/_secrets.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/console.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/debug_bundle.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/doctor.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/enforcement.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/exec_cmd.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/hook_entry.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/hosts/__init__.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/hosts/antigravity.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/hosts/base.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/hosts/claude_code.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/hosts/cursor.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/hosts/cursor_cli.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/hosts/gemini_cli.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/hosts/kiro.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/hosts/unknown.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/migrate.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/output.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/spool_cmd.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/telemetry_consent.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/antigravity/hooks.json +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/antigravity.yaml +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/autogen.yaml +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/claude-code.yaml +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/codex-cli.yaml +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/cost-cap.yaml +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/crewai.yaml +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/cursor.yaml +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/generic.yaml +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/kiro/ide-file-save.kiro.hook +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/kiro/ide-prompt-submit.kiro.hook +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/langchain.yaml +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/mcp.yaml +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/rag.yaml +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/update_cmd.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/device.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/enrollment.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/error_codes.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/error_codes.yaml +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/errors.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/hitl/__init__.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/hitl/grant_protocol.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/hitl/mock.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/hitl/pending_approval.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/hitl/status.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/hooks/__init__.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/hooks/tool_output_handler.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/__init__.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/anthropic.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/autogen.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/braintrust.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/crewai/__init__.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/crewai/agent.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/crewai/crew.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/crewai/task.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/crewai/tool.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/google.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/google_adk/__init__.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/google_adk/agent.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/google_adk/tool.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/langchain/__init__.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/langchain/agent.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/langchain/callbacks.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/langchain/chain.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/langchain/graph.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/langchain/modern.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/langchain/tool.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/langfuse.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/litellm.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/openai.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/pydantic_ai.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/vercel_ai.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/layout_migration.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/secrets/__init__.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/secrets/reference.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/secrets/resolver.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/secrets/secretstr.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/spool/__init__.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/spool/_compress.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/spool/_constants.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/spool/_crc32c.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/spool/_crypto.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/spool/_frame.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/spool/_keyring.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/spool/_metrics.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/spool/_spool.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/spool/_state.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/spool/_uploader.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/spool/cz-audit-v1.dict +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/tamper.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/tracecontext.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/examples/hello_world.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/_fixtures/antigravity-envelope-expected.json +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/_fixtures/jcs_args_hash_vectors.json +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/conftest.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/fixtures/adapter-contracts/antigravity.json +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/fixtures/adapter-contracts/claude_code.json +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/fixtures/adapter-contracts/codex_cli.json +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/fixtures/adapter-contracts/cursor.json +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/fixtures/adapter-contracts/gemini_cli.json +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/fixtures/adapter-contracts/kiro.json +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/integrations/__init__.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/integrations/test_google.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/parity/action_aliases.json +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/spool/__init__.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/spool/conftest.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/spool/test_spool_cli.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/spool/test_spool_concurrency.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/spool/test_spool_conformance.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/spool/test_spool_core.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/spool/test_spool_crash.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/spool/test_spool_diskfull.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/spool/test_spool_durable_default_tamper.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/spool/test_spool_keychain_dek.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/spool/test_spool_sink_wiring.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/spool/test_spool_transcript_localack.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/spool/test_spool_uploader.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_action_aliases.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_action_canonicalization.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_action_validator_t86.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_adapter_contracts.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_adapter_interface.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_agent_name_env.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_agy_docs_no_exit2_1373.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_antigravity_adapter.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_antigravity_envelope_parity.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_antigravity_ga_blockers_1248.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_antigravity_hook_check.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_antigravity_install.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_antigravity_posttooluse_observe_58.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_antigravity_tool_vocab_1303.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_api_key_mask.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_audit_project_id_attribution_pr2.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_audit_remote.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_audit_remote_io_capture.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_audit_remote_sdk_version.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_audit_sink_isolation.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_bundle_cache_freshness_1303.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_bundle_parser.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_bundle_translate.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_canonical_phase1a.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_capability_matrix_doc.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_cli_carve_out.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_cli_debug_bundle.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_cli_hook.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_cli_hook_entry.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_cli_hosted_refresh.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_cli_init.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_cli_init_templates.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_cli_tail.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_cli_test.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_cli_validate.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_coding_agent_hooks.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_condition_operators_1495.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_conditions.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_config_format_parity_1303.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_conformance.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_console.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_credential_hook.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_cursor_adapter.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_default_action.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_deny_rule_reconciliation_1011.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_device.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_dlp_scanner.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_doctor.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_engine_version_consistency.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_enrollment.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_env_dump_438.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_epic_1247_bryan_acceptance.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_error_codes.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_errors_e_codes.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_fail_closed_eval.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_failopen_1303.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_glob_matching.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hitl_5d_email_install.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hitl_6a_cli_flag.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hitl_6a_exceptions.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hitl_6a_get_secret_hitl.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hitl_6a_mock_backend.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hitl_6a_pending_approval.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hitl_6a_wait.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hitl_conformance.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hitl_validator_keys.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hook_extractors.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hook_mask_failclosed.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hosted_local_audit_1247.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hosted_local_shadow_warn_1265.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hosted_policy_e2e.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hybrid_mode_strict.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hybrid_mode_warn.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_install_hook_command.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_install_hooks.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_io_capture_producer.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_kiro_adapter.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_kiro_cli_hook_pin_1265.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_kiro_hook_templates.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_kiro_install.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_layout_migration_t101.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_layout_parity_t102.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_local_mode_dict.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_local_mode_file_json.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_local_mode_file_yaml.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_localized_reason.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_log_fallback_stderr.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_log_options_ignored_hosted.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_log_rotation.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_migrate.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_min_sdk_version_gate.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_multi_client_per_project_175.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_no_policy_no_key.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_observe_mode_1247.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_package_rename_shim.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_part3_active_policy_count_1303.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_per_client_isolation_1011.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_policy_engine_version_phase1b.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_policy_freshness.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_policy_load_failclosed_f3.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_policy_settings.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_policy_source_audit.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_pr4_install_messaging.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_pr4_output.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_pr4_status.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_probe_audit_isolation.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_quarantine.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_reason_code.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_refresh.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_secrets.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_secrets_reference_resolver.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_shell_semantic_class.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_source_labels.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_sql_semantic_class.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_t103_precedence.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_t104_cache_gc.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_t108_local_override_audit.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_t96_single_audit_log.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_t99_install_prefetch_bundle.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_tamper.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_tamper_behavior.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_tamper_hook.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_telemetry_consent.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_tracecontext.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_unsafe_int_boundary.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_update_cmd.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_upgrade_nudge.py +0 -0
- {controlzero-1.13.3 → controlzero-1.13.5}/tools/cz-kiro-adapter +0 -0
|
@@ -70,7 +70,6 @@ firebase-service-account*.json
|
|
|
70
70
|
firebase-adminsdk-*.json
|
|
71
71
|
serviceAccountKey.json
|
|
72
72
|
cz-service-account.json
|
|
73
|
-
firebase-credentials.json/
|
|
74
73
|
google-services.json
|
|
75
74
|
GoogleService-Info.plist
|
|
76
75
|
!*.pub
|
|
@@ -261,3 +260,7 @@ doc_local/
|
|
|
261
260
|
|
|
262
261
|
# Antigravity (agy) CLI local workspace
|
|
263
262
|
.antigravitycli/
|
|
263
|
+
|
|
264
|
+
# HITL canary local config (never commit secrets)
|
|
265
|
+
hitl-canary.env
|
|
266
|
+
*.canary.env
|
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.13.4 (2026-07-05): deny-wins fail-open fix (P0)
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **Explicit deny now wins -- a default allow-* no longer shadows an attached
|
|
8
|
+
deny (#1623/#1620).** The bundle translator sorted policies by `priority`
|
|
9
|
+
ASCENDING, so a priority-0 catch-all `allow-*` was evaluated before a
|
|
10
|
+
higher-priority attached `deny`, and a denied action (e.g. `rm -rf`) fell
|
|
11
|
+
OPEN. The translator now sorts DESCENDING (higher priority first), matching
|
|
12
|
+
the server-side policy engine, so an explicit deny is honored. This is the
|
|
13
|
+
hard gate for the fail-open reported on 1.13.3 and below.
|
|
14
|
+
|
|
15
|
+
### Notes
|
|
16
|
+
|
|
17
|
+
- The backend now stamps `min_sdk_version = 1.13.4` on any bundle containing a
|
|
18
|
+
deny rule, so an SDK below 1.13.4 fails CLOSED (`BundleRequiresNewerSDKError`,
|
|
19
|
+
E1712) rather than silently under-enforcing. Upgrade to 1.13.4 to keep deny
|
|
20
|
+
rules enforced.
|
|
21
|
+
|
|
3
22
|
## 1.13.3 (2026-06-29): `verify --prove` enforcement-robustness layer
|
|
4
23
|
|
|
5
24
|
### Added
|
|
@@ -181,7 +200,7 @@
|
|
|
181
200
|
server-side before persist (#1433), and captured payloads are sealed under a
|
|
182
201
|
per-record DEK.
|
|
183
202
|
|
|
184
|
-
- **DLP `mask` action enforced in the SDK (#1278
|
|
203
|
+
- **DLP `mask` action enforced in the SDK (#1278 acme checklist #6).**
|
|
185
204
|
The `mask` action now redacts matched sensitive spans in the tool arguments
|
|
186
205
|
(modify-and-proceed) instead of only being recorded as a finding. When a DLP
|
|
187
206
|
rule matches with `action="mask"` on an `allow` decision, the enforcer
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: controlzero
|
|
3
|
-
Version: 1.13.
|
|
3
|
+
Version: 1.13.5
|
|
4
4
|
Summary: AI agent governance: policies, audit, and observability for tool calls. Works locally with no signup.
|
|
5
5
|
Project-URL: Homepage, https://controlzero.ai
|
|
6
6
|
Project-URL: Documentation, https://docs.controlzero.ai
|
|
@@ -39,6 +39,7 @@ _CANONICAL_TOOLS: set[str] = {
|
|
|
39
39
|
# Bash aliases
|
|
40
40
|
"bash", "shell", "ShellTool", "run_shell_command",
|
|
41
41
|
"PowerShell", "powershell", "Shell",
|
|
42
|
+
"execute_bash", "local_shell", "run_terminal_cmd",
|
|
42
43
|
# http aliases
|
|
43
44
|
"fetch", "web_fetch", "WebFetch", "HTTPRequest", "request",
|
|
44
45
|
# web_search aliases
|
|
@@ -430,37 +430,138 @@ def _parse_version_tuple(v: str) -> tuple:
|
|
|
430
430
|
return tuple(nums) + (1, "")
|
|
431
431
|
|
|
432
432
|
|
|
433
|
-
def
|
|
434
|
-
"""
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
433
|
+
def sdk_below_floor(payload: dict, sdk_version: str) -> Optional[tuple[str, str]]:
|
|
434
|
+
"""Return ``(required, actual)`` when ``sdk_version`` is BELOW the bundle's
|
|
435
|
+
``metadata.min_sdk_version`` floor, else ``None``.
|
|
436
|
+
|
|
437
|
+
The floor is stamped by the backend as the MAX over every min-SDK floor the
|
|
438
|
+
bundle's rules require -- the #602 selector floor AND the capability floors
|
|
439
|
+
(any deny rule -> 1.13.4, any tagged {op,value} condition -> 1.13.0). An SDK
|
|
440
|
+
below the floor SILENTLY MIS-ENFORCES those rules (the confirmed #1011 rm-rf
|
|
441
|
+
fail-open on a stale 1.12.0), so the caller must fail loud/closed rather than
|
|
442
|
+
under-enforce.
|
|
443
|
+
|
|
444
|
+
Bundles without ``metadata.min_sdk_version`` return ``None`` (back-compat:
|
|
445
|
+
every bundle minted before #602 omits the field). No-op fast path covers
|
|
446
|
+
99% of bundles in the field.
|
|
444
447
|
"""
|
|
445
448
|
metadata = payload.get("metadata")
|
|
446
449
|
if not isinstance(metadata, dict):
|
|
447
|
-
return
|
|
450
|
+
return None
|
|
448
451
|
required = metadata.get("min_sdk_version")
|
|
449
452
|
if not isinstance(required, str) or not required:
|
|
453
|
+
return None
|
|
454
|
+
if _parse_version_tuple(sdk_version) >= _parse_version_tuple(required):
|
|
455
|
+
return None
|
|
456
|
+
return required, sdk_version
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def check_min_sdk_version(payload: dict, sdk_version: str) -> None:
|
|
460
|
+
"""Raise :class:`BundleRequiresNewerSDKError` if the bundle declares a
|
|
461
|
+
higher floor than ``sdk_version``.
|
|
462
|
+
|
|
463
|
+
This is the low-level HARD-refusal predicate, kept for direct callers and
|
|
464
|
+
back-compat. The hosted load path does NOT call this directly -- it uses
|
|
465
|
+
:func:`sdk_below_floor` + posture (see hosted_policy) so an allow-by-default
|
|
466
|
+
bundle degrades-with-a-loud-warning instead of hard-refusing, while a
|
|
467
|
+
deny-by-default bundle fails closed.
|
|
468
|
+
"""
|
|
469
|
+
below = sdk_below_floor(payload, sdk_version)
|
|
470
|
+
if below is None:
|
|
450
471
|
return
|
|
472
|
+
required, actual = below
|
|
451
473
|
# Lazy import: errors -> _internal cycle is avoided because
|
|
452
474
|
# this is invoked at runtime, after both modules finished loading.
|
|
453
475
|
from controlzero.errors import BundleRequiresNewerSDKError
|
|
454
476
|
|
|
455
|
-
if _parse_version_tuple(sdk_version) >= _parse_version_tuple(required):
|
|
456
|
-
return
|
|
457
477
|
raise BundleRequiresNewerSDKError(
|
|
458
478
|
required=required,
|
|
459
|
-
actual=
|
|
479
|
+
actual=actual,
|
|
460
480
|
upgrade_command="pip install -U controlzero",
|
|
461
481
|
)
|
|
462
482
|
|
|
463
483
|
|
|
484
|
+
# Canonical upgrade command surfaced in every below-floor message. Per-language
|
|
485
|
+
# (mirrors BundleRequiresNewerSDKError.upgrade_command); the reason_code and
|
|
486
|
+
# enforcement_status are the byte-identical cross-SDK fields.
|
|
487
|
+
SDK_UPGRADE_COMMAND = "pip install -U controlzero"
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def below_floor_deny_reason(required: str, actual: str, upgrade_command: str) -> str:
|
|
491
|
+
"""User-facing reason on a fail-closed deny caused by an under-floor SDK."""
|
|
492
|
+
return (
|
|
493
|
+
f"[Control Zero] Your SDK {actual} is below the minimum {required} this "
|
|
494
|
+
f"policy requires. Enforcement cannot be guaranteed -- blocking as a "
|
|
495
|
+
f"precaution (deny-by-default posture). Upgrade to restore enforcement: "
|
|
496
|
+
f"{upgrade_command}"
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def below_floor_degraded_warning(required: str, actual: str, upgrade_command: str) -> str:
|
|
501
|
+
"""Loud stderr banner when an under-floor SDK ALLOWS under an allow/observe
|
|
502
|
+
posture -- enforcement is degraded, never silent."""
|
|
503
|
+
return (
|
|
504
|
+
f"[Control Zero] ENFORCEMENT DEGRADED: your SDK {actual} is below the "
|
|
505
|
+
f"minimum {required} this policy requires (allow-by-default posture) -- "
|
|
506
|
+
f"this policy's deny rules may NOT be applied. Upgrade to restore "
|
|
507
|
+
f"enforcement: {upgrade_command}"
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def make_below_floor_policy(
|
|
512
|
+
required: str,
|
|
513
|
+
actual: str,
|
|
514
|
+
upgrade_command: str = SDK_UPGRADE_COMMAND,
|
|
515
|
+
) -> dict:
|
|
516
|
+
"""Build the synthetic deny-all local-policy used when a deny-by-default
|
|
517
|
+
bundle is loaded by an SDK BELOW its min_sdk_version floor.
|
|
518
|
+
|
|
519
|
+
Same shape as :func:`translate_to_local_policy` so downstream
|
|
520
|
+
:func:`controlzero.policy_loader.load_policy` accepts it unchanged. Every
|
|
521
|
+
tool call is denied with reason_code ``SDK_BELOW_FLOOR`` and
|
|
522
|
+
``settings.enforcement_status = "fail_closed"`` -- the drift is loud on
|
|
523
|
+
every audit row, never a silent under-enforce.
|
|
524
|
+
"""
|
|
525
|
+
return {
|
|
526
|
+
"version": "1",
|
|
527
|
+
"rules": [
|
|
528
|
+
{
|
|
529
|
+
"effect": "deny",
|
|
530
|
+
"action": "*",
|
|
531
|
+
"id": "synthetic:SDK_BELOW_FLOOR",
|
|
532
|
+
"reason": below_floor_deny_reason(required, actual, upgrade_command),
|
|
533
|
+
"reason_code": "SDK_BELOW_FLOOR",
|
|
534
|
+
}
|
|
535
|
+
],
|
|
536
|
+
"settings": {
|
|
537
|
+
"default_action": "deny",
|
|
538
|
+
"default_on_missing": "deny",
|
|
539
|
+
"default_on_tamper": "warn",
|
|
540
|
+
"enforcement_status": "fail_closed",
|
|
541
|
+
},
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
def bundle_is_deny_posture(payload: dict) -> bool:
|
|
546
|
+
"""Whether the bundle's resolved default posture is deny-by-default.
|
|
547
|
+
|
|
548
|
+
Used ONLY to decide the below-floor behaviour: a deny-by-default bundle
|
|
549
|
+
fails CLOSED (deny-all) when the SDK is under the floor; an allow/observe
|
|
550
|
+
bundle degrades-with-a-loud-warning (the #1011 case: allow-by-default + stale
|
|
551
|
+
SDK must scream, not silently allow). Missing / unknown default_action
|
|
552
|
+
coerces to the canonical ``deny`` -- the safe direction.
|
|
553
|
+
"""
|
|
554
|
+
from controlzero._internal.enforcer import (
|
|
555
|
+
DEFAULT_BUNDLE_ACTION,
|
|
556
|
+
VALID_DEFAULT_ACTIONS,
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
da = payload.get("default_action")
|
|
560
|
+
if da not in VALID_DEFAULT_ACTIONS:
|
|
561
|
+
da = DEFAULT_BUNDLE_ACTION
|
|
562
|
+
return da == "deny"
|
|
563
|
+
|
|
564
|
+
|
|
464
565
|
# --- Schema translation ----------------------------------------------------
|
|
465
566
|
|
|
466
567
|
|
|
@@ -521,8 +622,14 @@ def translate_to_local_policy(
|
|
|
521
622
|
either ``{"deny": "<tool>"}``, ``{"allow": "<tool>"}`` or
|
|
522
623
|
``{"effect": "<deny|allow|warn|audit>", "action": "<tool>"}``.
|
|
523
624
|
|
|
524
|
-
Policies are sorted by ``priority``
|
|
525
|
-
|
|
625
|
+
Policies are sorted by ``priority`` DESCENDING (higher number = higher
|
|
626
|
+
precedence = evaluated first; first-match-wins), so SDKs in every language
|
|
627
|
+
-- and the policy engine, which already sorts descending -- produce
|
|
628
|
+
identical decisions from identical input (#1620 cross-engine parity). The
|
|
629
|
+
auto-attached onboarding "Default policy" catch-all is pinned to the lowest
|
|
630
|
+
priority (``math.MinInt32``) by the backend, so it sorts LAST and never
|
|
631
|
+
shadows a user-attached deny. (Before #1620 this sort was ASCENDING, which
|
|
632
|
+
put the priority-0 default catch-all FIRST -- the core-promise fail-open.)
|
|
526
633
|
|
|
527
634
|
Schema 1.1 (#228 Phase 2) adds three top-level enforcement-default
|
|
528
635
|
knobs -- ``default_action``, ``default_on_missing``, and
|
|
@@ -580,9 +687,13 @@ def translate_to_local_policy(
|
|
|
580
687
|
# latter must fail CLOSED, never observe-allow.
|
|
581
688
|
raw_policies = payload.get("policies")
|
|
582
689
|
policies = raw_policies or []
|
|
690
|
+
# DESCENDING (#1620): higher priority evaluated first, matching the policy
|
|
691
|
+
# engine. `sorted` is stable, so equal-priority policies keep the backend's
|
|
692
|
+
# emit order (which already places the catch-all last within its tier).
|
|
583
693
|
policies = sorted(
|
|
584
694
|
[p for p in policies if isinstance(p, dict)],
|
|
585
695
|
key=lambda p: int(p.get("priority", 100)),
|
|
696
|
+
reverse=True,
|
|
586
697
|
)
|
|
587
698
|
|
|
588
699
|
flat: list[dict] = []
|
|
@@ -107,6 +107,14 @@ REASON_CODE_HITL_IDENTITY_NOT_IN_ORG = "HITL_IDENTITY_NOT_IN_ORG"
|
|
|
107
107
|
REASON_CODE_HITL_IDENTITY_REQUIRED = "HITL_IDENTITY_REQUIRED"
|
|
108
108
|
REASON_CODE_HITL_IDENTITY_CLAIM_REJECTED = "HITL_IDENTITY_CLAIM_REJECTED"
|
|
109
109
|
REASON_CODE_HITL_ARGS_HASH_MISMATCH = "HITL_ARGS_HASH_MISMATCH"
|
|
110
|
+
# Version-aware fail-closed enforcement (the #1011 stale-SDK fail-open). Stamped
|
|
111
|
+
# when the running SDK is BELOW the bundle's metadata.min_sdk_version -- the
|
|
112
|
+
# floor the backend derives from the rule capabilities the bundle uses (deny
|
|
113
|
+
# rules / condition operators). On a deny-by-default posture the SDK fails
|
|
114
|
+
# CLOSED with this code; on allow/observe posture it allows but flags every row
|
|
115
|
+
# with enforcement_status=degraded_below_floor. Either way the drift is VISIBLE,
|
|
116
|
+
# never a silent under-enforce.
|
|
117
|
+
REASON_CODE_SDK_BELOW_FLOOR = "SDK_BELOW_FLOOR"
|
|
110
118
|
|
|
111
119
|
VALID_REASON_CODES = frozenset({
|
|
112
120
|
REASON_CODE_RULE_MATCH,
|
|
@@ -128,6 +136,24 @@ VALID_REASON_CODES = frozenset({
|
|
|
128
136
|
REASON_CODE_HITL_IDENTITY_REQUIRED,
|
|
129
137
|
REASON_CODE_HITL_IDENTITY_CLAIM_REJECTED,
|
|
130
138
|
REASON_CODE_HITL_ARGS_HASH_MISMATCH,
|
|
139
|
+
REASON_CODE_SDK_BELOW_FLOOR,
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
# Enforcement status (version-aware fail-closed telemetry). A per-decision
|
|
143
|
+
# machine-readable summary of WHETHER the SDK is actually enforcing, surfaced on
|
|
144
|
+
# the audit/telemetry wire so the dashboard can show, per connected machine,
|
|
145
|
+
# green (enforcing) vs a degraded/fail-closed banner. Absent on the wire ==
|
|
146
|
+
# "enforcing" (lean additive contract). Kept in lockstep across all three SDKs.
|
|
147
|
+
ENFORCEMENT_STATUS_ENFORCING = "enforcing"
|
|
148
|
+
ENFORCEMENT_STATUS_DEGRADED_BELOW_FLOOR = "degraded_below_floor"
|
|
149
|
+
ENFORCEMENT_STATUS_FAIL_CLOSED = "fail_closed"
|
|
150
|
+
ENFORCEMENT_STATUS_BUNDLE_MISSING = "bundle_missing"
|
|
151
|
+
|
|
152
|
+
VALID_ENFORCEMENT_STATUSES = frozenset({
|
|
153
|
+
ENFORCEMENT_STATUS_ENFORCING,
|
|
154
|
+
ENFORCEMENT_STATUS_DEGRADED_BELOW_FLOOR,
|
|
155
|
+
ENFORCEMENT_STATUS_FAIL_CLOSED,
|
|
156
|
+
ENFORCEMENT_STATUS_BUNDLE_MISSING,
|
|
131
157
|
})
|
|
132
158
|
|
|
133
159
|
# Synthetic policy_id sentinels (T79 / the deny-deny postmortem,
|
|
@@ -158,6 +184,10 @@ SYNTHETIC_ENGINE_UNAVAILABLE = "synthetic:ENGINE_UNAVAILABLE"
|
|
|
158
184
|
# policy_id -- e.g. an old field SDK or a CLI fail-closed path. SDKs do not
|
|
159
185
|
# emit it; it is listed here so downstream consumers recognize it.
|
|
160
186
|
SYNTHETIC_UNATTRIBUTED_DENY = "synthetic:UNATTRIBUTED_DENY"
|
|
187
|
+
# Version-aware fail-closed: the deny-all synthetic policy_id stamped on every
|
|
188
|
+
# row when a deny-posture bundle is loaded by an SDK BELOW its min_sdk_version
|
|
189
|
+
# floor. Pairs with reason_code=SDK_BELOW_FLOOR.
|
|
190
|
+
SYNTHETIC_SDK_BELOW_FLOOR = "synthetic:SDK_BELOW_FLOOR"
|
|
161
191
|
|
|
162
192
|
VALID_SYNTHETIC_POLICY_IDS = frozenset({
|
|
163
193
|
SYNTHETIC_NO_RULE_MATCH,
|
|
@@ -168,6 +198,7 @@ VALID_SYNTHETIC_POLICY_IDS = frozenset({
|
|
|
168
198
|
SYNTHETIC_QUARANTINE,
|
|
169
199
|
SYNTHETIC_ENGINE_UNAVAILABLE,
|
|
170
200
|
SYNTHETIC_UNATTRIBUTED_DENY,
|
|
201
|
+
SYNTHETIC_SDK_BELOW_FLOOR,
|
|
171
202
|
})
|
|
172
203
|
|
|
173
204
|
# Canonical bundle-level default values. These must stay in lockstep
|
|
@@ -454,6 +454,29 @@ def _build_wire_entry(
|
|
|
454
454
|
if entry.get(key) is True:
|
|
455
455
|
wire[key] = True
|
|
456
456
|
|
|
457
|
+
# Version-aware fail-closed telemetry (#1011). enforcement_status is the
|
|
458
|
+
# per-decision "is this machine actually enforcing" summary (fail_closed /
|
|
459
|
+
# degraded_below_floor / bundle_missing); client_version above already
|
|
460
|
+
# carries the running SDK version. bundle_age_s + last_sync_ts (bundle
|
|
461
|
+
# freshness) are forwarded when the caller supplies them. Each is emitted
|
|
462
|
+
# ONLY when present so the common enforcing row stays byte-identical
|
|
463
|
+
# (absent enforcement_status == "enforcing"); older backends ignore the
|
|
464
|
+
# additive keys. This is the exact wire shape the machines/agents dashboard
|
|
465
|
+
# consumes to show per-machine SDK version + bundle age + enforcement state.
|
|
466
|
+
_enf = entry.get("enforcement_status")
|
|
467
|
+
if _enf:
|
|
468
|
+
wire["enforcement_status"] = str(_enf)
|
|
469
|
+
for _fresh_key in ("bundle_age_s", "last_sync_ts"):
|
|
470
|
+
_fv = entry.get(_fresh_key)
|
|
471
|
+
if _fv is None:
|
|
472
|
+
continue
|
|
473
|
+
try:
|
|
474
|
+
_fn = float(_fv)
|
|
475
|
+
except (TypeError, ValueError, OverflowError):
|
|
476
|
+
continue
|
|
477
|
+
if math.isfinite(_fn) and _fn >= 0:
|
|
478
|
+
wire[_fresh_key] = int(_fn)
|
|
479
|
+
|
|
457
480
|
# Final batch-poison backstop (#439 codex P1-b): every value that goes on
|
|
458
481
|
# the wire -- including the raw top-level forwarded fields above (the
|
|
459
482
|
# credential_* envelope copies entry[key] verbatim) and any future field
|
|
@@ -180,7 +180,7 @@ def per_agent_api_key(
|
|
|
180
180
|
ONLY the per-agent ``integrations/<canonical_source>/config.yaml`` key, or
|
|
181
181
|
None. hook-check uses it to make a surface's installed key AUTHORITATIVE
|
|
182
182
|
over a stale ambient ``CONTROLZERO_API_KEY`` (#1011 follow-up): a surface
|
|
183
|
-
wired by ``install <agent> --api-key``
|
|
183
|
+
wired by ``install <agent> --api-key`` (e.g. ``install kiro --api-key``) must
|
|
184
184
|
attribute its audit to the project it was installed for, never to whatever
|
|
185
185
|
key happens to be exported in the shell. Never raises.
|
|
186
186
|
"""
|
|
@@ -35,6 +35,15 @@ from controlzero.cli.hosts.base import CoverageDecl, CZDecision, HostAdapter
|
|
|
35
35
|
# fail-open backstop).
|
|
36
36
|
_CODEX_CLI_CLIENTS = ("codex-cli", "codex_cli", "codex")
|
|
37
37
|
|
|
38
|
+
# Kiro CLI v3 is built on the Codex codebase and exports CODEX_HOME/CODEX_CLI.
|
|
39
|
+
# Its hooks send camelCase hook_event_name values (preToolUse, postToolUse,
|
|
40
|
+
# userPromptSubmit) -- byte-disjoint from Codex's PascalCase (PreToolUse).
|
|
41
|
+
# When we see a camelCase event, defer to KiroCLIAdapter rather than claiming
|
|
42
|
+
# the call based on CODEX_HOME/CODEX_CLI env hints alone (Bug #1655).
|
|
43
|
+
_KIRO_CLI_CAMEL_EVENTS = frozenset(
|
|
44
|
+
{"preToolUse", "postToolUse", "userPromptSubmit", "agentSpawn", "stop"}
|
|
45
|
+
)
|
|
46
|
+
|
|
38
47
|
|
|
39
48
|
class CodexCLIAdapter(HostAdapter):
|
|
40
49
|
name = "codex_cli"
|
|
@@ -66,6 +75,12 @@ class CodexCLIAdapter(HostAdapter):
|
|
|
66
75
|
# pinned host's adapter (not this one) claims the call.
|
|
67
76
|
if self._pinned_elsewhere(env, _CODEX_CLI_CLIENTS):
|
|
68
77
|
return False
|
|
78
|
+
# Kiro CLI exports CODEX_HOME/CODEX_CLI (built on Codex codebase) but
|
|
79
|
+
# sends camelCase hook_event_name values (preToolUse vs Codex's
|
|
80
|
+
# PascalCase PreToolUse). Defer to KiroCLIAdapter when we see a
|
|
81
|
+
# camelCase Kiro event so audit logs show "kiro_cli" not "codex_cli".
|
|
82
|
+
if isinstance(payload, dict) and payload.get("hook_event_name") in _KIRO_CLI_CAMEL_EVENTS:
|
|
83
|
+
return False
|
|
69
84
|
for hint in self._ENV_HINTS:
|
|
70
85
|
if env.get(hint):
|
|
71
86
|
return True
|
|
@@ -13,7 +13,7 @@ IDE surface, NOT the Kiro CLI's ``userPromptSubmit``), e.g.::
|
|
|
13
13
|
|
|
14
14
|
cz-kiro-adapter preToolUse
|
|
15
15
|
|
|
16
|
-
This module is the packaged entry point: ``controlzero kiro
|
|
16
|
+
This module is the packaged entry point: ``controlzero install kiro`` wires the
|
|
17
17
|
IDE hooks to the ``cz-kiro-adapter`` console script declared in pyproject, so
|
|
18
18
|
it resolves on PATH after ``pip install controlzero`` with no separate copy
|
|
19
19
|
step. The repository ``tools/cz-kiro-adapter`` script delegates here so dev
|
|
@@ -134,9 +134,17 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
134
134
|
file=sys.stderr,
|
|
135
135
|
)
|
|
136
136
|
return 0
|
|
137
|
+
# Upstream Kiro IDE bug #7375/#7408/#7500: preToolUse hooks receive
|
|
138
|
+
# USER_PROMPT={} with no tool context. The 'Control Zero - Pre Tool Use'
|
|
139
|
+
# hook in Kiro IDE settings ships DISABLED to prevent this. If you are
|
|
140
|
+
# seeing this message, you have enabled that hook. Disable it in Kiro
|
|
141
|
+
# IDE Settings > Hooks to restore normal tool execution.
|
|
137
142
|
print(
|
|
138
|
-
"controlzero(kiro): empty
|
|
139
|
-
"(
|
|
143
|
+
f"controlzero(kiro): {event} hook received empty payload "
|
|
144
|
+
"(upstream Kiro IDE bug #7375/#7408/#7500 -- preToolUse delivers "
|
|
145
|
+
"USER_PROMPT={}). Failing closed. FIX: disable the "
|
|
146
|
+
"'Control Zero - Pre Tool Use' hook in Kiro IDE Settings > Hooks. "
|
|
147
|
+
"Set CZ_KIRO_UNKNOWN_PAYLOAD=open in the hook env to allow instead.",
|
|
140
148
|
file=sys.stderr,
|
|
141
149
|
)
|
|
142
150
|
return 2
|