controlzero 1.13.4__tar.gz → 1.13.6__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.4 → controlzero-1.13.6}/CHANGELOG.md +51 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/PKG-INFO +1 -1
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/__init__.py +1 -1
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/enforcement.py +31 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/hosts/codex_cli.py +15 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/kiro_adapter.py +10 -2
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/main.py +52 -3
- controlzero-1.13.6/controlzero/cli/templates/kiro/ide-pre-tool-use.kiro.hook +15 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/client.py +8 -2
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/hitl/secret_leak_guard.py +5 -1
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/hook_integrity.py +10 -4
- {controlzero-1.13.4 → controlzero-1.13.6}/pyproject.toml +1 -1
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_cli_hook.py +9 -3
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_coding_agent_hooks.py +16 -8
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_hitl_6a_request_approval.py +1 -1
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_hitl_6a_secret_leak_guard.py +8 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_hitl_phase2b_protocol.py +54 -0
- controlzero-1.13.6/tests/test_hook_command_pinning_1705.py +326 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_hook_integrity.py +21 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_hosts_adapter.py +43 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_install_hook_command.py +21 -14
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_install_hooks.py +38 -29
- controlzero-1.13.4/controlzero/cli/templates/kiro/ide-pre-tool-use.kiro.hook +0 -15
- {controlzero-1.13.4 → controlzero-1.13.6}/.gitignore +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/Dockerfile.test +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/LICENSE +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/README.md +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/__main__.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/_internal/__init__.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/_internal/_messages.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/_internal/action_aliases.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/_internal/action_validator.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/_internal/bundle.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/_internal/condition_ops.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/_internal/credential_hook.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/_internal/credential_scanner.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/_internal/credentials_data/__init__.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/_internal/credentials_data/built_in.yaml +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/_internal/dlp_scanner.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/_internal/enforcer.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/_internal/hook_extractors.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/_internal/io_capture_producer.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/_internal/source_labels.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/_internal/tool_extractors.json +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/_internal/types.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/audit_local.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/audit_remote.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/canonical.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/__init__.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/__main__.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/_secrets.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/agent_config.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/console.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/debug_bundle.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/doctor.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/exec_cmd.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/hook_entry.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/hosts/__init__.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/hosts/antigravity.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/hosts/base.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/hosts/claude_code.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/hosts/cursor.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/hosts/cursor_cli.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/hosts/gemini_cli.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/hosts/kiro.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/hosts/unknown.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/migrate.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/output.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/spool_cmd.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/telemetry_consent.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/templates/antigravity/hooks.json +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/templates/antigravity.yaml +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/templates/autogen.yaml +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/templates/claude-code.yaml +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/templates/codex-cli.yaml +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/templates/cost-cap.yaml +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/templates/crewai.yaml +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/templates/cursor.yaml +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/templates/gemini-cli.yaml +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/templates/generic.yaml +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/templates/kiro/ide-file-save.kiro.hook +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/templates/kiro/ide-prompt-submit.kiro.hook +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/templates/kiro/kiro.yaml +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/templates/langchain.yaml +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/templates/mcp.yaml +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/templates/rag.yaml +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/cli/update_cmd.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/device.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/enrollment.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/error_codes.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/error_codes.yaml +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/errors.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/hitl/__init__.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/hitl/grant_protocol.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/hitl/mock.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/hitl/pending_approval.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/hitl/status.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/hooks/__init__.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/hooks/tool_output_handler.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/hosted_policy.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/integrations/__init__.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/integrations/anthropic.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/integrations/autogen.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/integrations/braintrust.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/integrations/crewai/__init__.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/integrations/crewai/agent.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/integrations/crewai/crew.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/integrations/crewai/task.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/integrations/crewai/tool.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/integrations/google.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/integrations/google_adk/__init__.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/integrations/google_adk/agent.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/integrations/google_adk/tool.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/integrations/langchain/__init__.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/integrations/langchain/agent.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/integrations/langchain/callbacks.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/integrations/langchain/chain.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/integrations/langchain/graph.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/integrations/langchain/modern.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/integrations/langchain/tool.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/integrations/langfuse.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/integrations/litellm.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/integrations/openai.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/integrations/pydantic_ai.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/integrations/vercel_ai.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/layout_migration.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/policy_loader.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/secrets/__init__.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/secrets/reference.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/secrets/resolver.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/secrets/secretstr.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/spool/__init__.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/spool/_compress.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/spool/_constants.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/spool/_crc32c.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/spool/_crypto.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/spool/_frame.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/spool/_keyring.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/spool/_metrics.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/spool/_spool.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/spool/_state.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/spool/_uploader.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/spool/cz-audit-v1.dict +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/tamper.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/controlzero/tracecontext.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/examples/hello_world.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/_fixtures/antigravity-envelope-expected.json +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/_fixtures/jcs_args_hash_vectors.json +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/conftest.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/fixtures/adapter-contracts/antigravity.json +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/fixtures/adapter-contracts/claude_code.json +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/fixtures/adapter-contracts/codex_cli.json +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/fixtures/adapter-contracts/cursor.json +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/fixtures/adapter-contracts/gemini_cli.json +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/fixtures/adapter-contracts/kiro.json +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/integrations/__init__.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/integrations/test_google.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/parity/action_aliases.json +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/spool/__init__.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/spool/conftest.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/spool/test_spool_cli.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/spool/test_spool_concurrency.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/spool/test_spool_conformance.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/spool/test_spool_core.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/spool/test_spool_crash.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/spool/test_spool_diskfull.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/spool/test_spool_durable_default_tamper.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/spool/test_spool_keychain_dek.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/spool/test_spool_sink_wiring.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/spool/test_spool_transcript_localack.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/spool/test_spool_uploader.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_action_aliases.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_action_canonicalization.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_action_validator_t86.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_adapter_contracts.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_adapter_interface.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_agent_name_env.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_agy_docs_no_exit2_1373.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_antigravity_adapter.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_antigravity_envelope_parity.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_antigravity_ga_blockers_1248.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_antigravity_hook_check.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_antigravity_install.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_antigravity_posttooluse_observe_58.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_antigravity_tool_vocab_1303.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_api_key_mask.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_audit_project_id_attribution_pr2.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_audit_remote.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_audit_remote_io_capture.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_audit_remote_sdk_version.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_audit_sink_isolation.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_bundle_cache_freshness_1303.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_bundle_parser.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_bundle_translate.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_canonical_phase1a.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_capability_matrix_doc.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_cli_carve_out.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_cli_debug_bundle.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_cli_extractor_integration.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_cli_hook_entry.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_cli_hosted_refresh.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_cli_init.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_cli_init_templates.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_cli_tail.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_cli_test.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_cli_validate.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_condition_operators_1495.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_conditions.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_config_format_parity_1303.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_conformance.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_console.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_credential_hook.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_cross_adapter_shell_enforcement.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_cursor_adapter.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_default_action.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_deny_rule_reconciliation_1011.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_deny_wins_default_catchall_1620.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_device.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_dlp_mask.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_dlp_scanner.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_doctor.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_engine_version_consistency.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_enrollment.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_env_dump_438.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_epic_1247_bryan_acceptance.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_error_codes.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_errors_e_codes.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_fail_closed_eval.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_failopen_1303.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_glob_matching.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_hitl_5d_email_install.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_hitl_6a_cli_flag.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_hitl_6a_exceptions.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_hitl_6a_get_secret_hitl.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_hitl_6a_mock_backend.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_hitl_6a_pending_approval.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_hitl_6a_wait.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_hitl_conformance.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_hitl_reason_codes.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_hitl_validator_keys.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_hook_extractors.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_hook_mask_failclosed.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_hosted_local_audit_1247.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_hosted_local_shadow_warn_1265.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_hosted_policy_e2e.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_hybrid_mode_strict.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_hybrid_mode_warn.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_io_capture_producer.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_kiro_adapter.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_kiro_cli_e2e.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_kiro_cli_hook_pin_1265.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_kiro_cli_v210_hooks.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_kiro_hook_templates.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_kiro_install.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_kiro_install_standardisation.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_layout_migration_t101.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_layout_parity_t102.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_local_mode_dict.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_local_mode_file_json.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_local_mode_file_yaml.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_localized_reason.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_log_fallback_stderr.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_log_options_ignored_hosted.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_log_rotation.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_migrate.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_min_sdk_version_gate.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_multi_client_per_project_175.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_no_policy_no_key.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_observe_mode_1247.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_package_rename_shim.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_part3_active_policy_count_1303.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_per_client_isolation_1011.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_policy_engine_version_phase1b.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_policy_freshness.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_policy_load_failclosed_f3.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_policy_settings.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_policy_source_audit.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_pr4_install_messaging.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_pr4_output.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_pr4_status.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_probe_audit_isolation.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_quarantine.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_reason_code.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_refresh.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_secrets.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_secrets_reference_resolver.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_shell_semantic_class.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_source_labels.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_sql_semantic_class.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_synthetic_policy_id_t79.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_t103_precedence.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_t104_cache_gc.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_t108_local_override_audit.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_t96_single_audit_log.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_t99_install_prefetch_bundle.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_tamper.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_tamper_behavior.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_tamper_hook.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_telemetry_consent.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_tracecontext.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_unsafe_int_boundary.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_update_cmd.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_upgrade_nudge.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tests/test_version_aware_fail_closed.py +0 -0
- {controlzero-1.13.4 → controlzero-1.13.6}/tools/cz-kiro-adapter +0 -0
|
@@ -1,5 +1,56 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.13.6 (2026-07-12): interpreter-pinned hook command (P0)
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **Claude Code / Gemini CLI / Codex CLI hook command is now interpreter-pinned,
|
|
8
|
+
not PATH-dependent (#1705, P0).** `controlzero install` wired the PreToolUse
|
|
9
|
+
hook as a bare `controlzero hook-check`; if a stale/older `controlzero`
|
|
10
|
+
binary (pre-`hook-check`) sat earlier on PATH than the interpreter that ran
|
|
11
|
+
install, the hook resolved to the wrong binary, errored with `invalid
|
|
12
|
+
choice: 'hook-check'`, and Claude Code HARD-BLOCKED every tool call. The
|
|
13
|
+
installer now writes `"<sys.executable>" -m controlzero hook-check` (the
|
|
14
|
+
same PATH-immune form #1265 shipped for Cursor / kiro-cli). The legacy bare
|
|
15
|
+
form is still recognised by hook-integrity / `verify` for back-compat, and
|
|
16
|
+
`verify` now WARNs (does not fail) when it finds a pre-#1705 bare command
|
|
17
|
+
still installed, recommending a re-`install` to migrate.
|
|
18
|
+
|
|
19
|
+
## 1.13.5 (2026-07-06): HITL key-authed approval path + kiro-cli hook misattribution fix
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- **SDK-driven HITL now works for key-authed agents (#1601, P0).**
|
|
24
|
+
`request_approval` POST `/api/approval-requests` and poll GET
|
|
25
|
+
`/api/approval-requests/{id}` sat under the browser/session `userAuth`
|
|
26
|
+
chain, so a `cz_live` API key got a 401 and HITL was non-functional for
|
|
27
|
+
real agents. Added a key-authed POST/GET `/v1/sdk/approval-requests`
|
|
28
|
+
surface (mirrors the already-working `/api/grants` precedent); org and
|
|
29
|
+
project resolve from the key, and the project is pinned to the key on
|
|
30
|
+
both create AND poll (an agent cannot target or read another project's
|
|
31
|
+
approval requests). The Python SDK now points at the key-authed path.
|
|
32
|
+
- **E1709 leak-guard false-positive on ordinary English (#1601, P1).** The
|
|
33
|
+
entropy heuristic (`len>=24` + `entropy>3.5`) flagged plain text like the
|
|
34
|
+
default reason `"function requires approval"`, and
|
|
35
|
+
`enforce`/`guard_with_approval` swallowed the resulting
|
|
36
|
+
`SecretValueLeakInPayload` into a misleading `HITL_BACKEND_UNREACHABLE`
|
|
37
|
+
deny. The entropy heuristic is now gated on no-whitespace (a real
|
|
38
|
+
credential is a contiguous token), and the leak error propagates
|
|
39
|
+
distinctly instead of degrading to a backend-unreachable deny.
|
|
40
|
+
- **Kiro CLI hooks misattributed to Codex CLI, mis-scoring the audit source
|
|
41
|
+
(#1655).** Kiro CLI is built on Codex and exports `CODEX_HOME`/
|
|
42
|
+
`CODEX_CLI`, so `CodexCLIAdapter.claim()` was winning on those env hints
|
|
43
|
+
before `KiroCLIAdapter` could check. `CodexCLIAdapter` now defers when
|
|
44
|
+
`hook_event_name` is a Kiro camelCase event (`preToolUse`, `postToolUse`,
|
|
45
|
+
`userPromptSubmit`, `agentSpawn`, `stop`) -- byte-disjoint from Codex's
|
|
46
|
+
PascalCase `PreToolUse`.
|
|
47
|
+
- **Kiro IDE pre-tool-use hook UX.** The opaque "empty/unparseable IDE
|
|
48
|
+
payload; blocking" message now names the upstream bug (#7375/#7408/#7500),
|
|
49
|
+
explains the cause, and directs the user to disable the hook in Kiro IDE
|
|
50
|
+
Settings > Hooks. The shipped `ide-pre-tool-use.kiro.hook` template
|
|
51
|
+
name/description now leads with "[DO NOT ENABLE: blocks all tool calls]"
|
|
52
|
+
instead of burying the warning.
|
|
53
|
+
|
|
3
54
|
## 1.13.4 (2026-07-05): deny-wins fail-open fix (P0)
|
|
4
55
|
|
|
5
56
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: controlzero
|
|
3
|
-
Version: 1.13.
|
|
3
|
+
Version: 1.13.6
|
|
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
|
|
@@ -859,3 +859,34 @@ def installed_command(adapter: str, home: Optional[Path] = None, cwd: Optional[P
|
|
|
859
859
|
return _first_cz_command(_load_json(settings), ("preToolUse",))
|
|
860
860
|
return None
|
|
861
861
|
return None
|
|
862
|
+
|
|
863
|
+
|
|
864
|
+
# ---------------------------------------------------------------------------
|
|
865
|
+
# Bare, PATH-shadowable hook command detection (#1705).
|
|
866
|
+
# ---------------------------------------------------------------------------
|
|
867
|
+
|
|
868
|
+
# Adapters whose installer now emits the interpreter-pinned
|
|
869
|
+
# `"<sys.executable>" -m controlzero hook-check` form (#1705). A bare
|
|
870
|
+
# `controlzero hook-check` found installed for one of these is either a
|
|
871
|
+
# pre-#1705 install or a hand-edited config -- both are PATH-shadowable (a
|
|
872
|
+
# stale/older `controlzero` earlier on PATH silently resolves instead of the
|
|
873
|
+
# interpreter that ran install) and should be flagged so the operator
|
|
874
|
+
# re-runs `controlzero install` to migrate. Cursor / Kiro CLI / Kiro v3
|
|
875
|
+
# already pinned before #1705 (#1265); Antigravity intentionally still emits
|
|
876
|
+
# the bare form and is not covered by this check.
|
|
877
|
+
BARE_HOOK_COMMAND_ADAPTERS = ("claude_code", "gemini_cli", "codex_cli")
|
|
878
|
+
|
|
879
|
+
# The literal bare command these adapters wrote before #1705.
|
|
880
|
+
LEGACY_BARE_HOOK_CHECK_COMMAND = "controlzero hook-check"
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
def is_bare_shadowable_hook_command(command: Optional[str]) -> bool:
|
|
884
|
+
"""True if ``command`` is exactly the historical bare ``controlzero
|
|
885
|
+
hook-check`` form (#1705): PATH-dependent, so a stale/older
|
|
886
|
+
``controlzero`` binary earlier on PATH can silently resolve instead of
|
|
887
|
+
the interpreter that ran install. The current installer emits the
|
|
888
|
+
interpreter-pinned ``"<sys.executable>" -m controlzero hook-check`` form
|
|
889
|
+
instead; this only flags configs that predate that fix (or were
|
|
890
|
+
hand-edited back to bare). ``None`` (no command installed) is not bare.
|
|
891
|
+
"""
|
|
892
|
+
return command == LEGACY_BARE_HOOK_CHECK_COMMAND
|
|
@@ -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
|
|
@@ -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
|
|
@@ -1320,6 +1320,7 @@ def verify_cmd(as_json: bool, report: bool, prove: bool):
|
|
|
1320
1320
|
if prove:
|
|
1321
1321
|
sys.exit(_run_prove_enforcement(as_json))
|
|
1322
1322
|
|
|
1323
|
+
from controlzero.cli import enforcement
|
|
1323
1324
|
from controlzero.hook_integrity import (
|
|
1324
1325
|
baseline_exists,
|
|
1325
1326
|
check_hook_integrity,
|
|
@@ -1332,6 +1333,15 @@ def verify_cmd(as_json: bool, report: bool, prove: bool):
|
|
|
1332
1333
|
has_baseline = baseline_exists(GLOBAL_POLICY_DIR)
|
|
1333
1334
|
tamper = bool(hook_findings or env_findings)
|
|
1334
1335
|
|
|
1336
|
+
# #1705: a bare, PATH-shadowable `controlzero hook-check` predates the
|
|
1337
|
+
# interpreter-pinned fix (or was hand-edited back to bare). Not tamper --
|
|
1338
|
+
# it is a recognised, working CZ hook -- so it does not affect `tamper` /
|
|
1339
|
+
# the exit code, only surfaces as a WARN recommending re-install.
|
|
1340
|
+
bare_adapters = [
|
|
1341
|
+
a for a in enforcement.BARE_HOOK_COMMAND_ADAPTERS
|
|
1342
|
+
if enforcement.is_bare_shadowable_hook_command(enforcement.installed_command(a))
|
|
1343
|
+
]
|
|
1344
|
+
|
|
1335
1345
|
if tamper and report:
|
|
1336
1346
|
_report_hook_tamper(hook_findings, env_findings)
|
|
1337
1347
|
|
|
@@ -1342,6 +1352,7 @@ def verify_cmd(as_json: bool, report: bool, prove: bool):
|
|
|
1342
1352
|
"baseline_tampered": integrity.baseline_tampered,
|
|
1343
1353
|
"hook_findings": hook_findings,
|
|
1344
1354
|
"env_findings": env_findings,
|
|
1355
|
+
"bare_hook_command_adapters": bare_adapters,
|
|
1345
1356
|
}, indent=2))
|
|
1346
1357
|
else:
|
|
1347
1358
|
click.echo("Control Zero tamper-resistance verify")
|
|
@@ -1364,6 +1375,14 @@ def verify_cmd(as_json: bool, report: bool, prove: bool):
|
|
|
1364
1375
|
click.echo(f" - [env] {f['name']}: {f['reason']}")
|
|
1365
1376
|
else:
|
|
1366
1377
|
click.secho(" status : OK (hooks intact)", fg="green")
|
|
1378
|
+
for a in bare_adapters:
|
|
1379
|
+
click.secho(
|
|
1380
|
+
f" ~ WARNING: {a} hook command is the bare, PATH-shadowable "
|
|
1381
|
+
"'controlzero hook-check' form -- re-run `controlzero install "
|
|
1382
|
+
f"{a.replace('_', '-')}` to migrate to the interpreter-pinned "
|
|
1383
|
+
"form (#1705).",
|
|
1384
|
+
fg="yellow",
|
|
1385
|
+
)
|
|
1367
1386
|
|
|
1368
1387
|
if not has_baseline:
|
|
1369
1388
|
sys.exit(4)
|
|
@@ -1427,6 +1446,29 @@ _ANTIGRAVITY_PRETOOL_MATCHER = "*" # agy special-cases "*" (NOT RE2-compiled
|
|
|
1427
1446
|
_KIRO_POSTTOOL_MATCHER = "*" # observe-only postToolUse match-all
|
|
1428
1447
|
|
|
1429
1448
|
|
|
1449
|
+
def _pinned_hook_check_command() -> str:
|
|
1450
|
+
"""Interpreter-pinned `controlzero hook-check` command for Claude Code /
|
|
1451
|
+
Gemini CLI / Codex CLI (#1705).
|
|
1452
|
+
|
|
1453
|
+
A bare ``controlzero hook-check`` is PATH-dependent: the agent's hook
|
|
1454
|
+
subshell resolves ``controlzero`` against ITS PATH at hook-execution time,
|
|
1455
|
+
not against the interpreter that ran ``install``. If a stale/older
|
|
1456
|
+
``controlzero`` binary (one whose only subcommands are
|
|
1457
|
+
``setup``/``status``/``uninstall``, i.e. pre-``hook-check``) sits earlier
|
|
1458
|
+
on PATH than the one installed into this interpreter's venv, the hook
|
|
1459
|
+
resolves to the wrong binary, prints ``invalid choice: 'hook-check'``,
|
|
1460
|
+
exits non-zero, and the agent HARD-BLOCKS every tool call -- bricking the
|
|
1461
|
+
session. This actually happened (#1705).
|
|
1462
|
+
|
|
1463
|
+
Pinning ``sys.executable -m controlzero`` -- the same fix #1265 shipped
|
|
1464
|
+
for Cursor / kiro-cli -- makes the agent invoke the EXACT interpreter that
|
|
1465
|
+
ran install, which provably has ``hook-check``, regardless of PATH or venv
|
|
1466
|
+
activation. Double-quoted so a space in the interpreter path survives
|
|
1467
|
+
/bin/sh and cmd.exe.
|
|
1468
|
+
"""
|
|
1469
|
+
return f'"{sys.executable}" -m controlzero hook-check'
|
|
1470
|
+
|
|
1471
|
+
|
|
1430
1472
|
@cli.command("hook-check")
|
|
1431
1473
|
@click.option(
|
|
1432
1474
|
"--policy",
|
|
@@ -3736,7 +3778,10 @@ def install_claude_code(force: bool, merge: bool, settings: Optional[str], api_k
|
|
|
3736
3778
|
# env-prefix was a Windows-portability bug (PowerShell / cmd.exe cannot
|
|
3737
3779
|
# parse the syntax) AND placed the cleartext key into settings.json on
|
|
3738
3780
|
# disk. Both go away here. See 2026-05-12 incident.
|
|
3739
|
-
|
|
3781
|
+
# Interpreter-pinned, not a bare `controlzero hook-check` -- see
|
|
3782
|
+
# _pinned_hook_check_command() (#1705: a stale controlzero earlier on
|
|
3783
|
+
# PATH otherwise hard-blocks every tool call).
|
|
3784
|
+
hook_command = _pinned_hook_check_command()
|
|
3740
3785
|
new_block = {
|
|
3741
3786
|
"matcher": _CLAUDE_CODE_PRETOOL_MATCHER,
|
|
3742
3787
|
"hooks": [
|
|
@@ -3913,7 +3958,9 @@ def install_gemini_cli(force: bool, merge: bool, settings: Optional[str], api_ke
|
|
|
3913
3958
|
# _write_api_key_config above). The legacy bash-only env-prefix
|
|
3914
3959
|
# broke Windows; see the claude-code install path for the full
|
|
3915
3960
|
# rationale.
|
|
3916
|
-
|
|
3961
|
+
# Interpreter-pinned, not a bare `controlzero hook-check` (#1705); see
|
|
3962
|
+
# _pinned_hook_check_command().
|
|
3963
|
+
hook_command = _pinned_hook_check_command()
|
|
3917
3964
|
|
|
3918
3965
|
# Gemini CLI expects a nested structure:
|
|
3919
3966
|
# { matcher: "regex", hooks: [{ type: "command", command: "...", timeout: N }] }
|
|
@@ -4113,7 +4160,9 @@ def install_codex_cli(force: bool, merge: bool, config: Optional[str], api_key:
|
|
|
4113
4160
|
# _write_api_key_config above). The legacy bash-only env-prefix
|
|
4114
4161
|
# broke Windows; see the claude-code install path for the full
|
|
4115
4162
|
# rationale.
|
|
4116
|
-
|
|
4163
|
+
# Interpreter-pinned, not a bare `controlzero hook-check` (#1705); see
|
|
4164
|
+
# _pinned_hook_check_command().
|
|
4165
|
+
hook_command = _pinned_hook_check_command()
|
|
4117
4166
|
|
|
4118
4167
|
new_hook_entry = {
|
|
4119
4168
|
"matcher": _CODEX_PRETOOL_MATCHER,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"enabled": false,
|
|
3
|
+
"name": "Control Zero - Pre Tool Use [DO NOT ENABLE: blocks all tool calls]",
|
|
4
|
+
"description": "WARNING: DO NOT ENABLE THIS HOOK. Upstream Kiro IDE bug #7375/#7408/#7500: preToolUse hooks receive empty tool context (USER_PROMPT={}). With no context this hook fails closed and blocks EVERY tool call. If you enabled this hook and Kiro is now blocking all tools, disable it here immediately. The 'Pre Tool Use' event type is correct for pre-tool governance -- this hook is disabled until Kiro ships the fix. The Kiro CLI preToolUse surface works correctly and governs tool calls by default. Re-enable only after Kiro IDE delivers tool args to preToolUse hooks (or set CZ_KIRO_UNKNOWN_PAYLOAD=open in the hook environment to accept reduced governance). See docs/integrations/kiro.md.",
|
|
5
|
+
"version": "1",
|
|
6
|
+
"when": {
|
|
7
|
+
"type": "preToolUse",
|
|
8
|
+
"toolTypes": ["*"]
|
|
9
|
+
},
|
|
10
|
+
"then": {
|
|
11
|
+
"type": "runCommand",
|
|
12
|
+
"command": "cz-kiro-adapter preToolUse",
|
|
13
|
+
"timeout": 5
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -59,6 +59,7 @@ from controlzero.errors import (
|
|
|
59
59
|
HybridModeError,
|
|
60
60
|
PolicyLoadError,
|
|
61
61
|
PolicyValidationError,
|
|
62
|
+
SecretValueLeakInPayload,
|
|
62
63
|
)
|
|
63
64
|
from controlzero.hitl import raise_on_leak
|
|
64
65
|
from controlzero.policy_loader import load_policy, PolicySettings
|
|
@@ -709,7 +710,7 @@ class Client:
|
|
|
709
710
|
"X-CZ-Requestor-Email": email,
|
|
710
711
|
}
|
|
711
712
|
|
|
712
|
-
url = f"{get_api_url()}/
|
|
713
|
+
url = f"{get_api_url()}/v1/sdk/approval-requests"
|
|
713
714
|
|
|
714
715
|
try:
|
|
715
716
|
resp = httpx.post(url, json=body, headers=headers, timeout=10.0)
|
|
@@ -1051,7 +1052,7 @@ class Client:
|
|
|
1051
1052
|
"get_secret_poll_fn requires an API key"
|
|
1052
1053
|
)
|
|
1053
1054
|
|
|
1054
|
-
url = f"{get_api_url()}/
|
|
1055
|
+
url = f"{get_api_url()}/v1/sdk/approval-requests/{request_id}"
|
|
1055
1056
|
headers = {
|
|
1056
1057
|
"Authorization": f"Bearer {self._api_key}",
|
|
1057
1058
|
"Accept": "application/json",
|
|
@@ -1401,6 +1402,11 @@ class Client:
|
|
|
1401
1402
|
except GrantProtocolError as exc:
|
|
1402
1403
|
# denied / expired / revoked / timeout / backend-unreachable
|
|
1403
1404
|
return self._synthetic_grant_deny(exc.reason_code, str(exc))
|
|
1405
|
+
except SecretValueLeakInPayload:
|
|
1406
|
+
# A secret-shaped value in the grant reason/payload is a caller/security
|
|
1407
|
+
# error, NOT a backend outage. Surface E1709 clearly instead of a synthetic
|
|
1408
|
+
# DENY mislabeled HITL_BACKEND_UNREACHABLE.
|
|
1409
|
+
raise
|
|
1404
1410
|
except Exception: # noqa: BLE001
|
|
1405
1411
|
# Defense-in-depth: any unanticipated error still fails
|
|
1406
1412
|
# closed rather than leaking an allow.
|
|
@@ -141,7 +141,11 @@ def is_likely_secret_value(value: str) -> bool:
|
|
|
141
141
|
return True
|
|
142
142
|
|
|
143
143
|
# Length + entropy floor.
|
|
144
|
-
|
|
144
|
+
# A real credential is a single contiguous token. Natural-language reason/message
|
|
145
|
+
# text contains whitespace and must NOT trip the value-leak scan (E1709 false
|
|
146
|
+
# positive on e.g. the default reason "function requires approval"). Genuine secrets
|
|
147
|
+
# embedded in an arg value are contiguous, so this keeps recall on real credentials.
|
|
148
|
+
if len(value) >= _ENTROPY_LEN_FLOOR and not any(ch.isspace() for ch in value):
|
|
145
149
|
if _shannon_entropy(value) > _ENTROPY_BITS_PER_CHAR_FLOOR:
|
|
146
150
|
return True
|
|
147
151
|
|
|
@@ -63,12 +63,18 @@ from typing import Iterable, Optional
|
|
|
63
63
|
from controlzero.tamper import load_or_create_secret, sign_bundle, verify_bundle
|
|
64
64
|
|
|
65
65
|
# The CZ hook command is recognised by this substring on every host. Both
|
|
66
|
-
# the bare ``controlzero hook-check`` form
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
# does the Kiro IDE adapter command (``-m controlzero.cli.kiro_adapter``).
|
|
66
|
+
# the bare ``controlzero hook-check`` form and the interpreter-pinned
|
|
67
|
+
# ``"<py>" -m controlzero hook-check [--strict]`` form contain it, as does
|
|
68
|
+
# the Kiro IDE adapter command (``-m controlzero.cli.kiro_adapter``).
|
|
70
69
|
# Keeping the marker broad means a renamed-but-still-CZ command still
|
|
71
70
|
# matches, so an attacker cannot evade the baseline by reformatting.
|
|
71
|
+
#
|
|
72
|
+
# As of #1705, Claude Code / Gemini CLI / Codex CLI installs also emit the
|
|
73
|
+
# interpreter-pinned form (previously only Cursor / Kiro CLI / Kiro v3 did;
|
|
74
|
+
# Antigravity still emits bare). The bare form is kept recognised here on
|
|
75
|
+
# purpose: an install from BEFORE #1705 (or a hand-edited config) still
|
|
76
|
+
# carries it, and this marker is what lets `verify` / `uninstall` / a
|
|
77
|
+
# re-`install` find and migrate it rather than treating it as untracked.
|
|
72
78
|
HOOK_COMMAND_MARKERS = ("controlzero hook-check", "controlzero.cli.kiro_adapter")
|
|
73
79
|
|
|
74
80
|
BASELINE_FILE_NAME = "hook_baseline.json"
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "controlzero"
|
|
7
|
-
version = "1.13.
|
|
7
|
+
version = "1.13.6"
|
|
8
8
|
description = "AI agent governance: policies, audit, and observability for tool calls. Works locally with no signup."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "Apache-2.0"}
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
"""Tests for `controlzero hook-check` and `controlzero install claude-code`."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import sys
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
|
|
6
7
|
from click.testing import CliRunner
|
|
7
8
|
|
|
8
9
|
from controlzero.cli.main import cli
|
|
9
10
|
|
|
11
|
+
# #1705: the installer wires the interpreter-pinned
|
|
12
|
+
# `"<sys.executable>" -m controlzero hook-check` command, not a bare
|
|
13
|
+
# `controlzero hook-check` (PATH-shadowable).
|
|
14
|
+
PINNED_HOOK_COMMAND = f'"{sys.executable}" -m controlzero hook-check'
|
|
15
|
+
|
|
10
16
|
|
|
11
17
|
def write_policy(tmp_path: Path, rules: list) -> Path:
|
|
12
18
|
p = tmp_path / "policy.yaml"
|
|
@@ -189,7 +195,7 @@ def test_install_claude_code_creates_files(tmp_path, monkeypatch):
|
|
|
189
195
|
settings = json.loads(settings_path.read_text())
|
|
190
196
|
pre_tool = settings["hooks"]["PreToolUse"]
|
|
191
197
|
assert any(
|
|
192
|
-
h.get("command") ==
|
|
198
|
+
h.get("command") == PINNED_HOOK_COMMAND
|
|
193
199
|
for block in pre_tool
|
|
194
200
|
for h in block.get("hooks", [])
|
|
195
201
|
)
|
|
@@ -216,7 +222,7 @@ def test_install_claude_code_idempotent(tmp_path, monkeypatch):
|
|
|
216
222
|
h
|
|
217
223
|
for block in pre_tool
|
|
218
224
|
for h in block.get("hooks", [])
|
|
219
|
-
if h.get("command") ==
|
|
225
|
+
if h.get("command") == PINNED_HOOK_COMMAND
|
|
220
226
|
]
|
|
221
227
|
assert len(cz_hooks) == 1, f"expected 1 cz hook, got {len(cz_hooks)}"
|
|
222
228
|
|
|
@@ -267,7 +273,7 @@ def test_install_claude_code_preserves_other_hooks(tmp_path, monkeypatch):
|
|
|
267
273
|
for h in block.get("hooks", [])
|
|
268
274
|
]
|
|
269
275
|
assert "some-other-tool" in commands
|
|
270
|
-
assert
|
|
276
|
+
assert PINNED_HOOK_COMMAND in commands
|
|
271
277
|
|
|
272
278
|
|
|
273
279
|
def test_install_claude_code_skips_existing_policy(tmp_path, monkeypatch):
|
|
@@ -39,6 +39,7 @@ from __future__ import annotations
|
|
|
39
39
|
import importlib
|
|
40
40
|
import json
|
|
41
41
|
import os
|
|
42
|
+
import sys
|
|
42
43
|
from pathlib import Path
|
|
43
44
|
|
|
44
45
|
import yaml
|
|
@@ -48,6 +49,12 @@ from controlzero._internal.enforcer import PolicyEvaluator
|
|
|
48
49
|
from controlzero.cli.main import cli
|
|
49
50
|
from controlzero.policy_loader import load_policy
|
|
50
51
|
|
|
52
|
+
# #1705: the installer now wires the interpreter-pinned
|
|
53
|
+
# `"<sys.executable>" -m controlzero hook-check` command, not a bare
|
|
54
|
+
# `controlzero hook-check` (PATH-shadowable -- see main.py's
|
|
55
|
+
# _pinned_hook_check_command()).
|
|
56
|
+
PINNED_HOOK_COMMAND = f'"{sys.executable}" -m controlzero hook-check'
|
|
57
|
+
|
|
51
58
|
|
|
52
59
|
# ---------------------------------------------------------------------------
|
|
53
60
|
# Helpers
|
|
@@ -710,12 +717,13 @@ class TestApiKeyOption:
|
|
|
710
717
|
pre_tool = settings["hooks"]["PreToolUse"]
|
|
711
718
|
assert len(pre_tool) == 1
|
|
712
719
|
hook_cmd = pre_tool[0]["hooks"][0]["command"]
|
|
713
|
-
# 1.5.2 portability fix: the
|
|
714
|
-
# `controlzero hook-check`. The api key flows through
|
|
720
|
+
# 1.5.2 portability fix: the api key flows through
|
|
715
721
|
# ~/.controlzero/config.yaml (see _write_api_key_config) so it
|
|
716
722
|
# is no longer embedded in settings.json (Windows shells could
|
|
717
|
-
# not parse the bash-only env-prefix syntax).
|
|
718
|
-
|
|
723
|
+
# not parse the bash-only env-prefix syntax). #1705: the command
|
|
724
|
+
# is the interpreter-pinned form, not a bare `controlzero
|
|
725
|
+
# hook-check` (PATH-shadowable).
|
|
726
|
+
assert hook_cmd == PINNED_HOOK_COMMAND
|
|
719
727
|
assert "cz_live_" not in hook_cmd
|
|
720
728
|
assert "CONTROLZERO_API_KEY=" not in hook_cmd
|
|
721
729
|
|
|
@@ -739,8 +747,8 @@ class TestApiKeyOption:
|
|
|
739
747
|
# Nested format: check inside the hooks array
|
|
740
748
|
inner_hooks = before_tool[0].get("hooks", [])
|
|
741
749
|
assert len(inner_hooks) == 1
|
|
742
|
-
# 1.5.2:
|
|
743
|
-
assert inner_hooks[0]["command"] ==
|
|
750
|
+
# 1.5.2: key flows via config.yaml. #1705: interpreter-pinned command.
|
|
751
|
+
assert inner_hooks[0]["command"] == PINNED_HOOK_COMMAND
|
|
744
752
|
assert "cz_test_" not in inner_hooks[0]["command"]
|
|
745
753
|
|
|
746
754
|
def test_install_codex_cli_with_api_key_updates_hooks_json(
|
|
@@ -763,8 +771,8 @@ class TestApiKeyOption:
|
|
|
763
771
|
assert len(pre_tool) == 1
|
|
764
772
|
inner_hooks = pre_tool[0].get("hooks", [])
|
|
765
773
|
assert len(inner_hooks) == 1
|
|
766
|
-
# 1.5.2:
|
|
767
|
-
assert inner_hooks[0]["command"] ==
|
|
774
|
+
# 1.5.2: key flows via config.yaml. #1705: interpreter-pinned command.
|
|
775
|
+
assert inner_hooks[0]["command"] == PINNED_HOOK_COMMAND
|
|
768
776
|
assert "cz_live_" not in inner_hooks[0]["command"]
|
|
769
777
|
|
|
770
778
|
def test_install_with_invalid_api_key_exits_with_error(
|
|
@@ -207,7 +207,7 @@ class TestHappyPath:
|
|
|
207
207
|
assert kwargs["headers"]["X-CZ-Requestor-Email"] == "alice@example.com"
|
|
208
208
|
assert kwargs["headers"]["Content-Type"] == "application/json"
|
|
209
209
|
assert post.call_args[0][0] == (
|
|
210
|
-
"https://api.fake.test/
|
|
210
|
+
"https://api.fake.test/v1/sdk/approval-requests"
|
|
211
211
|
)
|
|
212
212
|
|
|
213
213
|
body = kwargs["json"]
|
|
@@ -65,6 +65,14 @@ class TestIsLikelySecretValue:
|
|
|
65
65
|
assert len(value) == 32
|
|
66
66
|
assert is_likely_secret_value(value) is True
|
|
67
67
|
|
|
68
|
+
def test_natural_language_reasons_return_false(self):
|
|
69
|
+
for value in (
|
|
70
|
+
"function requires approval",
|
|
71
|
+
"requested by SDK",
|
|
72
|
+
"please approve deploying the billing service to production",
|
|
73
|
+
):
|
|
74
|
+
assert is_likely_secret_value(value) is False
|
|
75
|
+
|
|
68
76
|
def test_sk_prefix_returns_true(self):
|
|
69
77
|
# Short on purpose: prefix check must fire even when the
|
|
70
78
|
# entropy floor wouldn't.
|
|
@@ -33,6 +33,8 @@ import httpx
|
|
|
33
33
|
import pytest
|
|
34
34
|
import respx
|
|
35
35
|
|
|
36
|
+
from controlzero.errors import SecretValueLeakInPayload
|
|
37
|
+
|
|
36
38
|
import controlzero.audit_remote as audit_remote_module
|
|
37
39
|
from controlzero import Client
|
|
38
40
|
from controlzero.hitl import grant_protocol as gp
|
|
@@ -319,6 +321,23 @@ def _approval_decision(client, monkeypatch):
|
|
|
319
321
|
monkeypatch.setattr(client, "guard", _fake_guard)
|
|
320
322
|
|
|
321
323
|
|
|
324
|
+
def _approval_decision_with_reason(client, monkeypatch, reason):
|
|
325
|
+
from controlzero._internal.enforcer import PolicyDecision
|
|
326
|
+
|
|
327
|
+
def _fake_guard(tool, args=None, method="*", raise_on_deny=False, context=None):
|
|
328
|
+
d = PolicyDecision(
|
|
329
|
+
effect="deny",
|
|
330
|
+
policy_id="rule:hitl:1",
|
|
331
|
+
reason=reason,
|
|
332
|
+
reason_code="HITL_REQUIRES_APPROVAL",
|
|
333
|
+
)
|
|
334
|
+
d.requires_approval = True
|
|
335
|
+
d.approval_action = "delete_file"
|
|
336
|
+
return d
|
|
337
|
+
|
|
338
|
+
monkeypatch.setattr(client, "guard", _fake_guard)
|
|
339
|
+
|
|
340
|
+
|
|
322
341
|
@pytest.mark.asyncio
|
|
323
342
|
async def test_guard_with_approval_backward_compat_no_hitl(monkeypatch, tmp_path):
|
|
324
343
|
# When guard() returns no requires_approval, the result is the
|
|
@@ -339,6 +358,41 @@ async def test_guard_with_approval_post_fails_denies(monkeypatch, tmp_path):
|
|
|
339
358
|
assert decision.reason_code == "HITL_BACKEND_UNREACHABLE"
|
|
340
359
|
|
|
341
360
|
|
|
361
|
+
@pytest.mark.asyncio
|
|
362
|
+
async def test_guard_with_approval_default_reason_does_not_raise_e1709(monkeypatch, tmp_path):
|
|
363
|
+
client = _make_client(monkeypatch, tmp_path)
|
|
364
|
+
_approval_decision_with_reason(client, monkeypatch, "function requires approval")
|
|
365
|
+
with respx.mock(base_url=_API_URL) as mock:
|
|
366
|
+
mock.post("/api/grants").mock(
|
|
367
|
+
return_value=httpx.Response(200, json={"grant_id": "g1"})
|
|
368
|
+
)
|
|
369
|
+
mock.get("/api/grants/g1").mock(
|
|
370
|
+
return_value=httpx.Response(200, json={"state": "approved"})
|
|
371
|
+
)
|
|
372
|
+
decision = await client.guard_with_approval(
|
|
373
|
+
"delete_file", {"p": 1}, retry_guard=False
|
|
374
|
+
)
|
|
375
|
+
assert decision.effect == "allow"
|
|
376
|
+
assert decision.reason_code == "HITL_GRANT_APPROVED"
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
@pytest.mark.asyncio
|
|
380
|
+
async def test_guard_with_approval_secret_leak_raises_e1709(monkeypatch, tmp_path):
|
|
381
|
+
client = _make_client(monkeypatch, tmp_path)
|
|
382
|
+
_approval_decision_with_reason(
|
|
383
|
+
client,
|
|
384
|
+
monkeypatch,
|
|
385
|
+
"0123456789abcdef0123456789abcdef",
|
|
386
|
+
)
|
|
387
|
+
with respx.mock(base_url=_API_URL, assert_all_called=False) as mock:
|
|
388
|
+
post = mock.post("/api/grants").mock(
|
|
389
|
+
return_value=httpx.Response(200, json={"grant_id": "g1"})
|
|
390
|
+
)
|
|
391
|
+
with pytest.raises(SecretValueLeakInPayload):
|
|
392
|
+
await client.guard_with_approval("delete_file", {"p": 1})
|
|
393
|
+
assert post.call_count == 0
|
|
394
|
+
|
|
395
|
+
|
|
342
396
|
@pytest.mark.asyncio
|
|
343
397
|
async def test_guard_with_approval_denied(monkeypatch, tmp_path):
|
|
344
398
|
client = _make_client(monkeypatch, tmp_path)
|