controlzero 1.13.5__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.5 → controlzero-1.13.6}/CHANGELOG.md +51 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/PKG-INFO +1 -1
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/__init__.py +1 -1
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/enforcement.py +31 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/main.py +52 -3
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/hook_integrity.py +10 -4
- {controlzero-1.13.5 → controlzero-1.13.6}/pyproject.toml +1 -1
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_cli_hook.py +9 -3
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_coding_agent_hooks.py +16 -8
- controlzero-1.13.6/tests/test_hook_command_pinning_1705.py +326 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hook_integrity.py +21 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_install_hook_command.py +21 -14
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_install_hooks.py +38 -29
- {controlzero-1.13.5 → controlzero-1.13.6}/.gitignore +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/Dockerfile.test +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/LICENSE +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/README.md +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/__main__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/__init__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/_messages.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/action_aliases.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/action_validator.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/bundle.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/condition_ops.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/credential_hook.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/credential_scanner.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/credentials_data/__init__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/credentials_data/built_in.yaml +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/dlp_scanner.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/enforcer.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/hook_extractors.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/io_capture_producer.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/source_labels.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/tool_extractors.json +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/types.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/audit_local.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/audit_remote.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/canonical.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/__init__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/__main__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/_secrets.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/agent_config.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/console.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/debug_bundle.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/doctor.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/exec_cmd.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/hook_entry.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/hosts/__init__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/hosts/antigravity.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/hosts/base.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/hosts/claude_code.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/hosts/codex_cli.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/hosts/cursor.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/hosts/cursor_cli.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/hosts/gemini_cli.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/hosts/kiro.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/hosts/unknown.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/kiro_adapter.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/migrate.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/output.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/spool_cmd.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/telemetry_consent.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/antigravity/hooks.json +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/antigravity.yaml +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/autogen.yaml +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/claude-code.yaml +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/codex-cli.yaml +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/cost-cap.yaml +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/crewai.yaml +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/cursor.yaml +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/gemini-cli.yaml +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/generic.yaml +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/kiro/ide-file-save.kiro.hook +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/kiro/ide-pre-tool-use.kiro.hook +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/kiro/ide-prompt-submit.kiro.hook +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/kiro/kiro.yaml +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/langchain.yaml +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/mcp.yaml +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/rag.yaml +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/update_cmd.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/client.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/device.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/enrollment.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/error_codes.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/error_codes.yaml +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/errors.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/hitl/__init__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/hitl/grant_protocol.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/hitl/mock.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/hitl/pending_approval.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/hitl/secret_leak_guard.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/hitl/status.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/hooks/__init__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/hooks/tool_output_handler.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/hosted_policy.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/__init__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/anthropic.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/autogen.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/braintrust.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/crewai/__init__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/crewai/agent.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/crewai/crew.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/crewai/task.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/crewai/tool.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/google.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/google_adk/__init__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/google_adk/agent.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/google_adk/tool.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/langchain/__init__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/langchain/agent.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/langchain/callbacks.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/langchain/chain.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/langchain/graph.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/langchain/modern.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/langchain/tool.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/langfuse.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/litellm.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/openai.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/pydantic_ai.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/vercel_ai.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/layout_migration.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/policy_loader.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/secrets/__init__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/secrets/reference.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/secrets/resolver.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/secrets/secretstr.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/spool/__init__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/spool/_compress.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/spool/_constants.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/spool/_crc32c.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/spool/_crypto.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/spool/_frame.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/spool/_keyring.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/spool/_metrics.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/spool/_spool.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/spool/_state.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/spool/_uploader.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/spool/cz-audit-v1.dict +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/tamper.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/tracecontext.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/examples/hello_world.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/_fixtures/antigravity-envelope-expected.json +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/_fixtures/jcs_args_hash_vectors.json +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/conftest.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/fixtures/adapter-contracts/antigravity.json +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/fixtures/adapter-contracts/claude_code.json +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/fixtures/adapter-contracts/codex_cli.json +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/fixtures/adapter-contracts/cursor.json +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/fixtures/adapter-contracts/gemini_cli.json +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/fixtures/adapter-contracts/kiro.json +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/integrations/__init__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/integrations/test_google.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/parity/action_aliases.json +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/spool/__init__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/spool/conftest.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/spool/test_spool_cli.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/spool/test_spool_concurrency.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/spool/test_spool_conformance.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/spool/test_spool_core.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/spool/test_spool_crash.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/spool/test_spool_diskfull.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/spool/test_spool_durable_default_tamper.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/spool/test_spool_keychain_dek.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/spool/test_spool_sink_wiring.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/spool/test_spool_transcript_localack.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/spool/test_spool_uploader.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_action_aliases.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_action_canonicalization.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_action_validator_t86.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_adapter_contracts.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_adapter_interface.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_agent_name_env.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_agy_docs_no_exit2_1373.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_antigravity_adapter.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_antigravity_envelope_parity.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_antigravity_ga_blockers_1248.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_antigravity_hook_check.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_antigravity_install.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_antigravity_posttooluse_observe_58.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_antigravity_tool_vocab_1303.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_api_key_mask.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_audit_project_id_attribution_pr2.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_audit_remote.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_audit_remote_io_capture.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_audit_remote_sdk_version.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_audit_sink_isolation.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_bundle_cache_freshness_1303.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_bundle_parser.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_bundle_translate.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_canonical_phase1a.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_capability_matrix_doc.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_cli_carve_out.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_cli_debug_bundle.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_cli_extractor_integration.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_cli_hook_entry.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_cli_hosted_refresh.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_cli_init.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_cli_init_templates.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_cli_tail.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_cli_test.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_cli_validate.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_condition_operators_1495.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_conditions.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_config_format_parity_1303.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_conformance.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_console.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_credential_hook.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_cross_adapter_shell_enforcement.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_cursor_adapter.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_default_action.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_deny_rule_reconciliation_1011.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_deny_wins_default_catchall_1620.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_device.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_dlp_mask.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_dlp_scanner.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_doctor.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_engine_version_consistency.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_enrollment.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_env_dump_438.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_epic_1247_bryan_acceptance.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_error_codes.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_errors_e_codes.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_fail_closed_eval.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_failopen_1303.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_glob_matching.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hitl_5d_email_install.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hitl_6a_cli_flag.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hitl_6a_exceptions.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hitl_6a_get_secret_hitl.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hitl_6a_mock_backend.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hitl_6a_pending_approval.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hitl_6a_request_approval.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hitl_6a_secret_leak_guard.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hitl_6a_wait.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hitl_conformance.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hitl_phase2b_protocol.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hitl_reason_codes.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hitl_validator_keys.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hook_extractors.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hook_mask_failclosed.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hosted_local_audit_1247.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hosted_local_shadow_warn_1265.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hosted_policy_e2e.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hosts_adapter.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hybrid_mode_strict.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hybrid_mode_warn.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_io_capture_producer.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_kiro_adapter.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_kiro_cli_e2e.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_kiro_cli_hook_pin_1265.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_kiro_cli_v210_hooks.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_kiro_hook_templates.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_kiro_install.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_kiro_install_standardisation.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_layout_migration_t101.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_layout_parity_t102.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_local_mode_dict.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_local_mode_file_json.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_local_mode_file_yaml.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_localized_reason.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_log_fallback_stderr.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_log_options_ignored_hosted.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_log_rotation.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_migrate.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_min_sdk_version_gate.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_multi_client_per_project_175.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_no_policy_no_key.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_observe_mode_1247.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_package_rename_shim.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_part3_active_policy_count_1303.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_per_client_isolation_1011.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_policy_engine_version_phase1b.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_policy_freshness.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_policy_load_failclosed_f3.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_policy_settings.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_policy_source_audit.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_pr4_install_messaging.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_pr4_output.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_pr4_status.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_probe_audit_isolation.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_quarantine.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_reason_code.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_refresh.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_secrets.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_secrets_reference_resolver.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_shell_semantic_class.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_source_labels.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_sql_semantic_class.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_synthetic_policy_id_t79.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_t103_precedence.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_t104_cache_gc.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_t108_local_override_audit.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_t96_single_audit_log.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_t99_install_prefetch_bundle.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_tamper.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_tamper_behavior.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_tamper_hook.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_telemetry_consent.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_tracecontext.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_unsafe_int_boundary.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_update_cmd.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_upgrade_nudge.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_version_aware_fail_closed.py +0 -0
- {controlzero-1.13.5 → 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
|
|
@@ -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,
|
|
@@ -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(
|