controlzero 1.13.5__tar.gz → 1.13.7__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.7}/CHANGELOG.md +51 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/PKG-INFO +2 -1
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/__init__.py +1 -1
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/audit_remote.py +5 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/enforcement.py +31 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/main.py +170 -3
- controlzero-1.13.7/controlzero/cli/setup_wizard.py +481 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/spool_cmd.py +2 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/client.py +4 -3
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/enrollment.py +8 -1
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/hitl/grant_protocol.py +3 -2
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/hook_integrity.py +10 -4
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/hosted_policy.py +93 -2
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/spool/_keyring.py +65 -5
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/tamper.py +2 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/pyproject.toml +6 -1
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/spool/test_spool_keychain_dek.py +142 -7
- controlzero-1.13.7/tests/test_api_url_config.py +72 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_cli_hook.py +9 -3
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_coding_agent_hooks.py +16 -8
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hitl_5d_email_install.py +1 -1
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hitl_6a_request_approval.py +1 -1
- controlzero-1.13.7/tests/test_hook_command_pinning_1705.py +326 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hook_integrity.py +21 -0
- controlzero-1.13.7/tests/test_install_api_url.py +69 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_install_hook_command.py +21 -14
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_install_hooks.py +38 -29
- controlzero-1.13.7/tests/test_setup_wizard.py +196 -0
- controlzero-1.13.7/tests/test_tls_ca_bundle.py +112 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/.gitignore +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/Dockerfile.test +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/LICENSE +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/README.md +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/__main__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/__init__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/_messages.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/action_aliases.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/action_validator.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/bundle.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/condition_ops.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/credential_hook.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/credential_scanner.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/credentials_data/__init__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/credentials_data/built_in.yaml +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/dlp_scanner.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/enforcer.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/hook_extractors.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/io_capture_producer.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/source_labels.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/tool_extractors.json +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/types.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/audit_local.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/canonical.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/__init__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/__main__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/_secrets.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/agent_config.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/console.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/debug_bundle.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/doctor.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/exec_cmd.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/hook_entry.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/hosts/__init__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/hosts/antigravity.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/hosts/base.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/hosts/claude_code.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/hosts/codex_cli.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/hosts/cursor.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/hosts/cursor_cli.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/hosts/gemini_cli.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/hosts/kiro.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/hosts/unknown.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/kiro_adapter.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/migrate.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/output.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/telemetry_consent.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/antigravity/hooks.json +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/antigravity.yaml +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/autogen.yaml +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/claude-code.yaml +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/codex-cli.yaml +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/cost-cap.yaml +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/crewai.yaml +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/cursor.yaml +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/gemini-cli.yaml +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/generic.yaml +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/kiro/ide-file-save.kiro.hook +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/kiro/ide-pre-tool-use.kiro.hook +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/kiro/ide-prompt-submit.kiro.hook +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/kiro/kiro.yaml +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/langchain.yaml +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/mcp.yaml +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/rag.yaml +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/update_cmd.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/device.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/error_codes.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/error_codes.yaml +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/errors.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/hitl/__init__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/hitl/mock.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/hitl/pending_approval.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/hitl/secret_leak_guard.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/hitl/status.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/hooks/__init__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/hooks/tool_output_handler.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/__init__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/anthropic.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/autogen.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/braintrust.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/crewai/__init__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/crewai/agent.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/crewai/crew.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/crewai/task.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/crewai/tool.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/google.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/google_adk/__init__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/google_adk/agent.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/google_adk/tool.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/langchain/__init__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/langchain/agent.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/langchain/callbacks.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/langchain/chain.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/langchain/graph.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/langchain/modern.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/langchain/tool.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/langfuse.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/litellm.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/openai.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/pydantic_ai.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/vercel_ai.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/layout_migration.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/policy_loader.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/secrets/__init__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/secrets/reference.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/secrets/resolver.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/secrets/secretstr.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/spool/__init__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/spool/_compress.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/spool/_constants.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/spool/_crc32c.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/spool/_crypto.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/spool/_frame.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/spool/_metrics.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/spool/_spool.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/spool/_state.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/spool/_uploader.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/spool/cz-audit-v1.dict +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/tracecontext.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/examples/hello_world.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/_fixtures/antigravity-envelope-expected.json +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/_fixtures/jcs_args_hash_vectors.json +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/conftest.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/fixtures/adapter-contracts/antigravity.json +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/fixtures/adapter-contracts/claude_code.json +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/fixtures/adapter-contracts/codex_cli.json +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/fixtures/adapter-contracts/cursor.json +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/fixtures/adapter-contracts/gemini_cli.json +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/fixtures/adapter-contracts/kiro.json +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/integrations/__init__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/integrations/test_google.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/parity/action_aliases.json +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/spool/__init__.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/spool/conftest.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/spool/test_spool_cli.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/spool/test_spool_concurrency.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/spool/test_spool_conformance.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/spool/test_spool_core.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/spool/test_spool_crash.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/spool/test_spool_diskfull.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/spool/test_spool_durable_default_tamper.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/spool/test_spool_sink_wiring.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/spool/test_spool_transcript_localack.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/spool/test_spool_uploader.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_action_aliases.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_action_canonicalization.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_action_validator_t86.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_adapter_contracts.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_adapter_interface.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_agent_name_env.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_agy_docs_no_exit2_1373.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_antigravity_adapter.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_antigravity_envelope_parity.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_antigravity_ga_blockers_1248.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_antigravity_hook_check.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_antigravity_install.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_antigravity_posttooluse_observe_58.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_antigravity_tool_vocab_1303.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_api_key_mask.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_audit_project_id_attribution_pr2.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_audit_remote.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_audit_remote_io_capture.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_audit_remote_sdk_version.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_audit_sink_isolation.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_bundle_cache_freshness_1303.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_bundle_parser.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_bundle_translate.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_canonical_phase1a.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_capability_matrix_doc.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_cli_carve_out.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_cli_debug_bundle.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_cli_extractor_integration.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_cli_hook_entry.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_cli_hosted_refresh.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_cli_init.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_cli_init_templates.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_cli_tail.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_cli_test.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_cli_validate.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_condition_operators_1495.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_conditions.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_config_format_parity_1303.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_conformance.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_console.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_credential_hook.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_cross_adapter_shell_enforcement.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_cursor_adapter.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_default_action.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_deny_rule_reconciliation_1011.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_deny_wins_default_catchall_1620.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_device.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_dlp_mask.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_dlp_scanner.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_doctor.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_engine_version_consistency.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_enrollment.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_env_dump_438.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_epic_1247_bryan_acceptance.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_error_codes.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_errors_e_codes.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_fail_closed_eval.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_failopen_1303.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_glob_matching.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hitl_6a_cli_flag.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hitl_6a_exceptions.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hitl_6a_get_secret_hitl.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hitl_6a_mock_backend.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hitl_6a_pending_approval.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hitl_6a_secret_leak_guard.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hitl_6a_wait.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hitl_conformance.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hitl_phase2b_protocol.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hitl_reason_codes.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hitl_validator_keys.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hook_extractors.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hook_mask_failclosed.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hosted_local_audit_1247.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hosted_local_shadow_warn_1265.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hosted_policy_e2e.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hosts_adapter.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hybrid_mode_strict.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hybrid_mode_warn.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_io_capture_producer.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_kiro_adapter.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_kiro_cli_e2e.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_kiro_cli_hook_pin_1265.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_kiro_cli_v210_hooks.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_kiro_hook_templates.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_kiro_install.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_kiro_install_standardisation.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_layout_migration_t101.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_layout_parity_t102.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_local_mode_dict.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_local_mode_file_json.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_local_mode_file_yaml.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_localized_reason.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_log_fallback_stderr.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_log_options_ignored_hosted.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_log_rotation.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_migrate.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_min_sdk_version_gate.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_multi_client_per_project_175.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_no_policy_no_key.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_observe_mode_1247.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_package_rename_shim.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_part3_active_policy_count_1303.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_per_client_isolation_1011.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_policy_engine_version_phase1b.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_policy_freshness.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_policy_load_failclosed_f3.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_policy_settings.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_policy_source_audit.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_pr4_install_messaging.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_pr4_output.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_pr4_status.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_probe_audit_isolation.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_quarantine.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_reason_code.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_refresh.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_secrets.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_secrets_reference_resolver.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_shell_semantic_class.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_source_labels.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_sql_semantic_class.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_synthetic_policy_id_t79.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_t103_precedence.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_t104_cache_gc.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_t108_local_override_audit.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_t96_single_audit_log.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_t99_install_prefetch_bundle.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_tamper.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_tamper_behavior.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_tamper_hook.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_telemetry_consent.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_tracecontext.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_unsafe_int_boundary.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_update_cmd.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_upgrade_nudge.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_version_aware_fail_closed.py +0 -0
- {controlzero-1.13.5 → controlzero-1.13.7}/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.7
|
|
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
|
|
@@ -28,6 +28,7 @@ Requires-Dist: httpx>=0.25.0
|
|
|
28
28
|
Requires-Dist: loguru>=0.7.0
|
|
29
29
|
Requires-Dist: pydantic>=2.0.0
|
|
30
30
|
Requires-Dist: pyyaml>=6.0
|
|
31
|
+
Requires-Dist: questionary<3,>=2.0.0
|
|
31
32
|
Requires-Dist: rfc8785<0.2,>=0.1.4
|
|
32
33
|
Requires-Dist: rich>=13.0.0
|
|
33
34
|
Requires-Dist: zstandard>=0.23.0
|
|
@@ -18,6 +18,7 @@ Design constraints:
|
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
20
|
from __future__ import annotations
|
|
21
|
+
from .hosted_policy import tls_verify
|
|
21
22
|
|
|
22
23
|
import getpass
|
|
23
24
|
import json
|
|
@@ -968,6 +969,7 @@ class RemoteAuditSink(_SpoolWiringMixin):
|
|
|
968
969
|
content=body,
|
|
969
970
|
headers=headers,
|
|
970
971
|
timeout=FLUSH_TIMEOUT_S,
|
|
972
|
+
verify=tls_verify(),
|
|
971
973
|
)
|
|
972
974
|
if resp.status_code == 401:
|
|
973
975
|
raise _AuthExpiredError("server returned 401")
|
|
@@ -1034,6 +1036,7 @@ class RemoteAuditSink(_SpoolWiringMixin):
|
|
|
1034
1036
|
content=body,
|
|
1035
1037
|
headers=headers,
|
|
1036
1038
|
timeout=FLUSH_TIMEOUT_S,
|
|
1039
|
+
verify=tls_verify(),
|
|
1037
1040
|
)
|
|
1038
1041
|
return post
|
|
1039
1042
|
|
|
@@ -1192,6 +1195,7 @@ class BearerAuditSink(_SpoolWiringMixin):
|
|
|
1192
1195
|
content=body,
|
|
1193
1196
|
headers=headers,
|
|
1194
1197
|
timeout=FLUSH_TIMEOUT_S,
|
|
1198
|
+
verify=tls_verify(),
|
|
1195
1199
|
)
|
|
1196
1200
|
if resp.status_code == 401:
|
|
1197
1201
|
raise _AuthExpiredError("server returned 401")
|
|
@@ -1244,6 +1248,7 @@ class BearerAuditSink(_SpoolWiringMixin):
|
|
|
1244
1248
|
content=body,
|
|
1245
1249
|
headers=headers,
|
|
1246
1250
|
timeout=FLUSH_TIMEOUT_S,
|
|
1251
|
+
verify=tls_verify(),
|
|
1247
1252
|
)
|
|
1248
1253
|
return post
|
|
1249
1254
|
|
|
@@ -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
|
|
@@ -270,6 +270,43 @@ def init(template: str, out: str, force: bool):
|
|
|
270
270
|
click.echo(f" 4. Use it in code: Client(policy_file='{out_path}')")
|
|
271
271
|
|
|
272
272
|
|
|
273
|
+
@cli.command("setup", hidden=True)
|
|
274
|
+
@click.option("--api-url", default=None,
|
|
275
|
+
help="Self-hosted / air-gap backend URL or host (e.g. https://cz.internal). "
|
|
276
|
+
"Presence selects self-hosted mode; persisted to config.yaml.")
|
|
277
|
+
@click.option("--api-key", default=None,
|
|
278
|
+
help="Control Zero API key (cz_live_/cz_test_). Env: CONTROLZERO_API_KEY.")
|
|
279
|
+
@click.option("--agent", "agents", multiple=True,
|
|
280
|
+
help="Coding agent to wire (repeatable): claude-code, codex-cli, gemini-cli, "
|
|
281
|
+
"kiro, cursor, antigravity. Env: CONTROLZERO_SETUP_AGENTS (comma list).")
|
|
282
|
+
@click.option("--tls-ca-bundle", default=None,
|
|
283
|
+
help="CA bundle path for a self-signed air-gap endpoint. Persisted to config.")
|
|
284
|
+
@click.option("--email", default=None, help="Your work email (Approvals attribution).")
|
|
285
|
+
@click.option("--yes", "assume_yes", is_flag=True,
|
|
286
|
+
help="Non-interactive: resolve from flags/env, never prompt.")
|
|
287
|
+
@click.pass_context
|
|
288
|
+
def setup(ctx, api_url, api_key, agents, tls_ca_bundle, email, assume_yes):
|
|
289
|
+
"""Guided onboarding: pick SaaS vs self-hosted, validate, wire agent hooks.
|
|
290
|
+
|
|
291
|
+
Interactive in a TTY; degrades to flags/env (exit 2 on missing input) in
|
|
292
|
+
CI/non-TTY. Hidden/beta -- existing install commands are unchanged.
|
|
293
|
+
"""
|
|
294
|
+
from controlzero.cli.setup_wizard import Flags, run_setup
|
|
295
|
+
|
|
296
|
+
code = run_setup(
|
|
297
|
+
ctx,
|
|
298
|
+
Flags(
|
|
299
|
+
api_url=api_url,
|
|
300
|
+
api_key=api_key,
|
|
301
|
+
agents=tuple(agents),
|
|
302
|
+
tls_ca_bundle=tls_ca_bundle,
|
|
303
|
+
email=email,
|
|
304
|
+
assume_yes=assume_yes,
|
|
305
|
+
),
|
|
306
|
+
)
|
|
307
|
+
sys.exit(code)
|
|
308
|
+
|
|
309
|
+
|
|
273
310
|
@cli.command()
|
|
274
311
|
@click.argument("policy_file", type=click.Path(exists=True), default="controlzero.yaml")
|
|
275
312
|
def validate(policy_file: str):
|
|
@@ -1320,6 +1357,7 @@ def verify_cmd(as_json: bool, report: bool, prove: bool):
|
|
|
1320
1357
|
if prove:
|
|
1321
1358
|
sys.exit(_run_prove_enforcement(as_json))
|
|
1322
1359
|
|
|
1360
|
+
from controlzero.cli import enforcement
|
|
1323
1361
|
from controlzero.hook_integrity import (
|
|
1324
1362
|
baseline_exists,
|
|
1325
1363
|
check_hook_integrity,
|
|
@@ -1332,6 +1370,15 @@ def verify_cmd(as_json: bool, report: bool, prove: bool):
|
|
|
1332
1370
|
has_baseline = baseline_exists(GLOBAL_POLICY_DIR)
|
|
1333
1371
|
tamper = bool(hook_findings or env_findings)
|
|
1334
1372
|
|
|
1373
|
+
# #1705: a bare, PATH-shadowable `controlzero hook-check` predates the
|
|
1374
|
+
# interpreter-pinned fix (or was hand-edited back to bare). Not tamper --
|
|
1375
|
+
# it is a recognised, working CZ hook -- so it does not affect `tamper` /
|
|
1376
|
+
# the exit code, only surfaces as a WARN recommending re-install.
|
|
1377
|
+
bare_adapters = [
|
|
1378
|
+
a for a in enforcement.BARE_HOOK_COMMAND_ADAPTERS
|
|
1379
|
+
if enforcement.is_bare_shadowable_hook_command(enforcement.installed_command(a))
|
|
1380
|
+
]
|
|
1381
|
+
|
|
1335
1382
|
if tamper and report:
|
|
1336
1383
|
_report_hook_tamper(hook_findings, env_findings)
|
|
1337
1384
|
|
|
@@ -1342,6 +1389,7 @@ def verify_cmd(as_json: bool, report: bool, prove: bool):
|
|
|
1342
1389
|
"baseline_tampered": integrity.baseline_tampered,
|
|
1343
1390
|
"hook_findings": hook_findings,
|
|
1344
1391
|
"env_findings": env_findings,
|
|
1392
|
+
"bare_hook_command_adapters": bare_adapters,
|
|
1345
1393
|
}, indent=2))
|
|
1346
1394
|
else:
|
|
1347
1395
|
click.echo("Control Zero tamper-resistance verify")
|
|
@@ -1364,6 +1412,14 @@ def verify_cmd(as_json: bool, report: bool, prove: bool):
|
|
|
1364
1412
|
click.echo(f" - [env] {f['name']}: {f['reason']}")
|
|
1365
1413
|
else:
|
|
1366
1414
|
click.secho(" status : OK (hooks intact)", fg="green")
|
|
1415
|
+
for a in bare_adapters:
|
|
1416
|
+
click.secho(
|
|
1417
|
+
f" ~ WARNING: {a} hook command is the bare, PATH-shadowable "
|
|
1418
|
+
"'controlzero hook-check' form -- re-run `controlzero install "
|
|
1419
|
+
f"{a.replace('_', '-')}` to migrate to the interpreter-pinned "
|
|
1420
|
+
"form (#1705).",
|
|
1421
|
+
fg="yellow",
|
|
1422
|
+
)
|
|
1367
1423
|
|
|
1368
1424
|
if not has_baseline:
|
|
1369
1425
|
sys.exit(4)
|
|
@@ -1427,6 +1483,29 @@ _ANTIGRAVITY_PRETOOL_MATCHER = "*" # agy special-cases "*" (NOT RE2-compiled
|
|
|
1427
1483
|
_KIRO_POSTTOOL_MATCHER = "*" # observe-only postToolUse match-all
|
|
1428
1484
|
|
|
1429
1485
|
|
|
1486
|
+
def _pinned_hook_check_command() -> str:
|
|
1487
|
+
"""Interpreter-pinned `controlzero hook-check` command for Claude Code /
|
|
1488
|
+
Gemini CLI / Codex CLI (#1705).
|
|
1489
|
+
|
|
1490
|
+
A bare ``controlzero hook-check`` is PATH-dependent: the agent's hook
|
|
1491
|
+
subshell resolves ``controlzero`` against ITS PATH at hook-execution time,
|
|
1492
|
+
not against the interpreter that ran ``install``. If a stale/older
|
|
1493
|
+
``controlzero`` binary (one whose only subcommands are
|
|
1494
|
+
``setup``/``status``/``uninstall``, i.e. pre-``hook-check``) sits earlier
|
|
1495
|
+
on PATH than the one installed into this interpreter's venv, the hook
|
|
1496
|
+
resolves to the wrong binary, prints ``invalid choice: 'hook-check'``,
|
|
1497
|
+
exits non-zero, and the agent HARD-BLOCKS every tool call -- bricking the
|
|
1498
|
+
session. This actually happened (#1705).
|
|
1499
|
+
|
|
1500
|
+
Pinning ``sys.executable -m controlzero`` -- the same fix #1265 shipped
|
|
1501
|
+
for Cursor / kiro-cli -- makes the agent invoke the EXACT interpreter that
|
|
1502
|
+
ran install, which provably has ``hook-check``, regardless of PATH or venv
|
|
1503
|
+
activation. Double-quoted so a space in the interpreter path survives
|
|
1504
|
+
/bin/sh and cmd.exe.
|
|
1505
|
+
"""
|
|
1506
|
+
return f'"{sys.executable}" -m controlzero hook-check'
|
|
1507
|
+
|
|
1508
|
+
|
|
1430
1509
|
@cli.command("hook-check")
|
|
1431
1510
|
@click.option(
|
|
1432
1511
|
"--policy",
|
|
@@ -3566,6 +3645,45 @@ def _print_api_key_status(api_key: Optional[str]) -> None:
|
|
|
3566
3645
|
click.echo("Tip: Pass --api-key cz_live_xxx to sync audit logs to the dashboard.")
|
|
3567
3646
|
|
|
3568
3647
|
|
|
3648
|
+
def _write_api_url_config(api_url: str) -> None:
|
|
3649
|
+
"""Persist ``api_url`` to ~/.controlzero/config.yaml (0600). The resolver
|
|
3650
|
+
reads it (env > config > default), so a self-hosted / air-gap endpoint set
|
|
3651
|
+
once at install reaches the agent hook WITHOUT exporting CONTROLZERO_API_URL
|
|
3652
|
+
into every hook subprocess (the historical air-gap footgun). Scheme+host
|
|
3653
|
+
only; the SDK strips a trailing slash and appends request paths itself."""
|
|
3654
|
+
GLOBAL_POLICY_DIR.mkdir(parents=True, exist_ok=True)
|
|
3655
|
+
try:
|
|
3656
|
+
os.chmod(GLOBAL_POLICY_DIR, 0o700)
|
|
3657
|
+
except OSError:
|
|
3658
|
+
pass
|
|
3659
|
+
config_data: dict = {}
|
|
3660
|
+
if GLOBAL_CONFIG_PATH.exists():
|
|
3661
|
+
try:
|
|
3662
|
+
existing = yaml.safe_load(GLOBAL_CONFIG_PATH.read_text(encoding="utf-8"))
|
|
3663
|
+
if isinstance(existing, dict):
|
|
3664
|
+
config_data = existing
|
|
3665
|
+
except Exception: # noqa: BLE001
|
|
3666
|
+
pass
|
|
3667
|
+
config_data["api_url"] = api_url.rstrip("/")
|
|
3668
|
+
GLOBAL_CONFIG_PATH.write_text(
|
|
3669
|
+
yaml.safe_dump(config_data, default_flow_style=False),
|
|
3670
|
+
encoding="utf-8",
|
|
3671
|
+
)
|
|
3672
|
+
try:
|
|
3673
|
+
os.chmod(GLOBAL_CONFIG_PATH, 0o600)
|
|
3674
|
+
except OSError:
|
|
3675
|
+
pass
|
|
3676
|
+
|
|
3677
|
+
|
|
3678
|
+
def _api_url_install_callback(ctx, param, value):
|
|
3679
|
+
"""click callback for the shared --api-url install option. Persists the URL
|
|
3680
|
+
at install time; expose_value=False keeps it off the command signature so no
|
|
3681
|
+
install function needs an api_url parameter."""
|
|
3682
|
+
if value:
|
|
3683
|
+
_write_api_url_config(value)
|
|
3684
|
+
return value
|
|
3685
|
+
|
|
3686
|
+
|
|
3569
3687
|
def _bind_api_key_for_install(
|
|
3570
3688
|
api_key: str,
|
|
3571
3689
|
product: str,
|
|
@@ -3641,6 +3759,13 @@ def install():
|
|
|
3641
3759
|
default=None,
|
|
3642
3760
|
help="Your work email. Required for Approvals so the backend knows who's at the keyboard when the API key is shared across a team. Stored at ~/.controlzero/config.yaml.",
|
|
3643
3761
|
)
|
|
3762
|
+
@click.option(
|
|
3763
|
+
"--api-url",
|
|
3764
|
+
default=None,
|
|
3765
|
+
expose_value=False,
|
|
3766
|
+
callback=_api_url_install_callback,
|
|
3767
|
+
help="Base URL of a self-hosted / air-gap Control Zero backend (e.g. https://cz.internal). Persisted to ~/.controlzero/config.yaml so the hook targets it; overrides the SaaS default. env CONTROLZERO_API_URL still wins.",
|
|
3768
|
+
)
|
|
3644
3769
|
def install_claude_code(force: bool, merge: bool, settings: Optional[str], api_key: Optional[str], email: Optional[str]):
|
|
3645
3770
|
"""Install controlzero as a Claude Code PreToolUse hook.
|
|
3646
3771
|
|
|
@@ -3736,7 +3861,10 @@ def install_claude_code(force: bool, merge: bool, settings: Optional[str], api_k
|
|
|
3736
3861
|
# env-prefix was a Windows-portability bug (PowerShell / cmd.exe cannot
|
|
3737
3862
|
# parse the syntax) AND placed the cleartext key into settings.json on
|
|
3738
3863
|
# disk. Both go away here. See 2026-05-12 incident.
|
|
3739
|
-
|
|
3864
|
+
# Interpreter-pinned, not a bare `controlzero hook-check` -- see
|
|
3865
|
+
# _pinned_hook_check_command() (#1705: a stale controlzero earlier on
|
|
3866
|
+
# PATH otherwise hard-blocks every tool call).
|
|
3867
|
+
hook_command = _pinned_hook_check_command()
|
|
3740
3868
|
new_block = {
|
|
3741
3869
|
"matcher": _CLAUDE_CODE_PRETOOL_MATCHER,
|
|
3742
3870
|
"hooks": [
|
|
@@ -3828,6 +3956,13 @@ def install_claude_code(force: bool, merge: bool, settings: Optional[str], api_k
|
|
|
3828
3956
|
default=None,
|
|
3829
3957
|
help="Your work email. Required for Approvals so the backend knows who's at the keyboard when the API key is shared across a team.",
|
|
3830
3958
|
)
|
|
3959
|
+
@click.option(
|
|
3960
|
+
"--api-url",
|
|
3961
|
+
default=None,
|
|
3962
|
+
expose_value=False,
|
|
3963
|
+
callback=_api_url_install_callback,
|
|
3964
|
+
help="Base URL of a self-hosted / air-gap Control Zero backend (e.g. https://cz.internal). Persisted to ~/.controlzero/config.yaml so the hook targets it; overrides the SaaS default. env CONTROLZERO_API_URL still wins.",
|
|
3965
|
+
)
|
|
3831
3966
|
def install_gemini_cli(force: bool, merge: bool, settings: Optional[str], api_key: Optional[str], email: Optional[str]):
|
|
3832
3967
|
"""Install controlzero as a Gemini CLI pre-tool-call hook.
|
|
3833
3968
|
|
|
@@ -3913,7 +4048,9 @@ def install_gemini_cli(force: bool, merge: bool, settings: Optional[str], api_ke
|
|
|
3913
4048
|
# _write_api_key_config above). The legacy bash-only env-prefix
|
|
3914
4049
|
# broke Windows; see the claude-code install path for the full
|
|
3915
4050
|
# rationale.
|
|
3916
|
-
|
|
4051
|
+
# Interpreter-pinned, not a bare `controlzero hook-check` (#1705); see
|
|
4052
|
+
# _pinned_hook_check_command().
|
|
4053
|
+
hook_command = _pinned_hook_check_command()
|
|
3917
4054
|
|
|
3918
4055
|
# Gemini CLI expects a nested structure:
|
|
3919
4056
|
# { matcher: "regex", hooks: [{ type: "command", command: "...", timeout: N }] }
|
|
@@ -4038,6 +4175,13 @@ def install_gemini_cli(force: bool, merge: bool, settings: Optional[str], api_ke
|
|
|
4038
4175
|
default=None,
|
|
4039
4176
|
help="Your work email. Required for Approvals so the backend knows who's at the keyboard when the API key is shared across a team.",
|
|
4040
4177
|
)
|
|
4178
|
+
@click.option(
|
|
4179
|
+
"--api-url",
|
|
4180
|
+
default=None,
|
|
4181
|
+
expose_value=False,
|
|
4182
|
+
callback=_api_url_install_callback,
|
|
4183
|
+
help="Base URL of a self-hosted / air-gap Control Zero backend (e.g. https://cz.internal). Persisted to ~/.controlzero/config.yaml so the hook targets it; overrides the SaaS default. env CONTROLZERO_API_URL still wins.",
|
|
4184
|
+
)
|
|
4041
4185
|
def install_codex_cli(force: bool, merge: bool, config: Optional[str], api_key: Optional[str], email: Optional[str]):
|
|
4042
4186
|
"""Install controlzero as a Codex CLI tool approval hook.
|
|
4043
4187
|
|
|
@@ -4113,7 +4257,9 @@ def install_codex_cli(force: bool, merge: bool, config: Optional[str], api_key:
|
|
|
4113
4257
|
# _write_api_key_config above). The legacy bash-only env-prefix
|
|
4114
4258
|
# broke Windows; see the claude-code install path for the full
|
|
4115
4259
|
# rationale.
|
|
4116
|
-
|
|
4260
|
+
# Interpreter-pinned, not a bare `controlzero hook-check` (#1705); see
|
|
4261
|
+
# _pinned_hook_check_command().
|
|
4262
|
+
hook_command = _pinned_hook_check_command()
|
|
4117
4263
|
|
|
4118
4264
|
new_hook_entry = {
|
|
4119
4265
|
"matcher": _CODEX_PRETOOL_MATCHER,
|
|
@@ -4571,6 +4717,13 @@ def _cursor_enforcement_probe(installed_cmd: str) -> tuple:
|
|
|
4571
4717
|
help="Your work email. Required for Approvals so the backend knows who is "
|
|
4572
4718
|
"at the keyboard when the API key is shared across a team.",
|
|
4573
4719
|
)
|
|
4720
|
+
@click.option(
|
|
4721
|
+
"--api-url",
|
|
4722
|
+
default=None,
|
|
4723
|
+
expose_value=False,
|
|
4724
|
+
callback=_api_url_install_callback,
|
|
4725
|
+
help="Base URL of a self-hosted / air-gap Control Zero backend (e.g. https://cz.internal). Persisted to ~/.controlzero/config.yaml so the hook targets it; overrides the SaaS default. env CONTROLZERO_API_URL still wins.",
|
|
4726
|
+
)
|
|
4574
4727
|
def install_cursor(
|
|
4575
4728
|
force: bool,
|
|
4576
4729
|
merge: bool,
|
|
@@ -4785,6 +4938,13 @@ def install_cursor(
|
|
|
4785
4938
|
default=None,
|
|
4786
4939
|
help="Your work email. Required for Approvals so the backend knows who's at the keyboard when the API key is shared across a team.",
|
|
4787
4940
|
)
|
|
4941
|
+
@click.option(
|
|
4942
|
+
"--api-url",
|
|
4943
|
+
default=None,
|
|
4944
|
+
expose_value=False,
|
|
4945
|
+
callback=_api_url_install_callback,
|
|
4946
|
+
help="Base URL of a self-hosted / air-gap Control Zero backend (e.g. https://cz.internal). Persisted to ~/.controlzero/config.yaml so the hook targets it; overrides the SaaS default. env CONTROLZERO_API_URL still wins.",
|
|
4947
|
+
)
|
|
4788
4948
|
def install_antigravity(
|
|
4789
4949
|
force: bool,
|
|
4790
4950
|
merge: bool,
|
|
@@ -6215,6 +6375,13 @@ def _run_kiro_uninstall(
|
|
|
6215
6375
|
is_flag=True,
|
|
6216
6376
|
help="Print what would change without writing anything to disk.",
|
|
6217
6377
|
)
|
|
6378
|
+
@click.option(
|
|
6379
|
+
"--api-url",
|
|
6380
|
+
default=None,
|
|
6381
|
+
expose_value=False,
|
|
6382
|
+
callback=_api_url_install_callback,
|
|
6383
|
+
help="Base URL of a self-hosted / air-gap Control Zero backend (e.g. https://cz.internal). Persisted to ~/.controlzero/config.yaml so the hook targets it; overrides the SaaS default. env CONTROLZERO_API_URL still wins.",
|
|
6384
|
+
)
|
|
6218
6385
|
def install_kiro(
|
|
6219
6386
|
surface: str,
|
|
6220
6387
|
workspace: Optional[str],
|