controlzero 1.9.4__tar.gz → 1.9.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {controlzero-1.9.4 → controlzero-1.9.5}/CHANGELOG.md +35 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/PKG-INFO +18 -8
- {controlzero-1.9.4 → controlzero-1.9.5}/README.md +17 -7
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/__init__.py +1 -1
- controlzero-1.9.5/controlzero/cli/__main__.py +13 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/hosts/kiro.py +7 -4
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/main.py +275 -7
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/claude-code.yaml +15 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/pyproject.toml +1 -1
- controlzero-1.9.5/tests/test_kiro_cli_e2e.py +297 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/.gitignore +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/Dockerfile.test +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/LICENSE +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/_internal/__init__.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/_internal/action_aliases.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/_internal/action_validator.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/_internal/bundle.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/_internal/credential_hook.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/_internal/credential_scanner.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/_internal/credentials_data/__init__.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/_internal/credentials_data/built_in.yaml +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/_internal/dlp_scanner.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/_internal/enforcer.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/_internal/hook_extractors.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/_internal/tool_extractors.json +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/_internal/types.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/audit_local.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/audit_remote.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/canonical.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/__init__.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/_secrets.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/console.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/debug_bundle.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/doctor.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/hosts/__init__.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/hosts/antigravity.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/hosts/base.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/hosts/claude_code.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/hosts/codex_cli.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/hosts/gemini_cli.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/hosts/unknown.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/kiro_adapter.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/migrate.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/spool_cmd.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/telemetry_consent.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/antigravity/hooks.json +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/antigravity.yaml +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/autogen.yaml +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/codex-cli.yaml +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/cost-cap.yaml +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/crewai.yaml +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/cursor.yaml +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/gemini-cli.yaml +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/generic.yaml +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/kiro/ide-file-save.kiro.hook +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/kiro/ide-pre-tool-use.kiro.hook +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/kiro/ide-prompt-submit.kiro.hook +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/kiro/kiro.yaml +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/langchain.yaml +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/mcp.yaml +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/rag.yaml +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/client.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/device.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/enrollment.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/error_codes.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/error_codes.yaml +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/errors.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/hitl/__init__.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/hitl/grant_protocol.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/hitl/mock.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/hitl/pending_approval.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/hitl/secret_leak_guard.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/hitl/status.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/hooks/__init__.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/hooks/tool_output_handler.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/hosted_policy.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/__init__.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/anthropic.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/autogen.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/braintrust.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/crewai/__init__.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/crewai/agent.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/crewai/crew.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/crewai/task.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/crewai/tool.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/google.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/google_adk/__init__.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/google_adk/agent.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/google_adk/tool.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/langchain/__init__.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/langchain/agent.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/langchain/callbacks.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/langchain/chain.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/langchain/graph.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/langchain/modern.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/langchain/tool.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/langfuse.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/litellm.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/openai.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/pydantic_ai.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/vercel_ai.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/layout_migration.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/policy_loader.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/spool/__init__.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/spool/_compress.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/spool/_constants.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/spool/_crc32c.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/spool/_crypto.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/spool/_frame.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/spool/_keyring.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/spool/_metrics.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/spool/_spool.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/spool/_state.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/spool/_uploader.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/spool/cz-audit-v1.dict +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/tamper.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/tracecontext.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/examples/hello_world.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/_fixtures/jcs_args_hash_vectors.json +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/conftest.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/integrations/__init__.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/integrations/test_google.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/parity/action_aliases.json +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/spool/__init__.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/spool/conftest.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/spool/test_spool_cli.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/spool/test_spool_concurrency.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/spool/test_spool_conformance.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/spool/test_spool_core.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/spool/test_spool_crash.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/spool/test_spool_diskfull.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/spool/test_spool_durable_default_tamper.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/spool/test_spool_keychain_dek.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/spool/test_spool_sink_wiring.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/spool/test_spool_transcript_localack.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/spool/test_spool_uploader.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_action_aliases.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_action_canonicalization.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_action_validator_t86.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_agent_name_env.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_antigravity_adapter.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_antigravity_hook_check.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_antigravity_install.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_api_key_mask.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_audit_remote.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_audit_remote_sdk_version.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_audit_sink_isolation.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_bundle_parser.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_bundle_translate.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_canonical_phase1a.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_cli_carve_out.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_cli_debug_bundle.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_cli_extractor_integration.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_cli_hook.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_cli_hosted_refresh.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_cli_init.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_cli_init_templates.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_cli_tail.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_cli_test.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_cli_validate.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_coding_agent_hooks.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_conditions.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_conformance.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_console.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_credential_hook.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_default_action.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_device.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_dlp_scanner.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_doctor.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_engine_version_consistency.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_enrollment.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_env_dump_438.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_epic_1247_bryan_acceptance.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_error_codes.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_errors_e_codes.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_fail_closed_eval.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_glob_matching.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hitl_5d_email_install.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hitl_6a_cli_flag.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hitl_6a_exceptions.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hitl_6a_get_secret_hitl.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hitl_6a_mock_backend.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hitl_6a_pending_approval.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hitl_6a_request_approval.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hitl_6a_secret_leak_guard.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hitl_6a_wait.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hitl_conformance.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hitl_phase2b_protocol.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hitl_reason_codes.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hitl_validator_keys.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hook_extractors.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hosted_local_audit_1247.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hosted_policy_e2e.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hosts_adapter.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hybrid_mode_strict.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hybrid_mode_warn.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_install_hook_command.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_install_hooks.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_kiro_adapter.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_kiro_hook_templates.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_kiro_install.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_layout_migration_t101.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_layout_parity_t102.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_local_mode_dict.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_local_mode_file_json.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_local_mode_file_yaml.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_log_fallback_stderr.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_log_options_ignored_hosted.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_log_rotation.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_migrate.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_min_sdk_version_gate.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_multi_client_per_project_175.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_no_policy_no_key.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_observe_mode_1247.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_package_rename_shim.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_policy_engine_version_phase1b.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_policy_freshness.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_policy_settings.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_policy_source_audit.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_quarantine.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_reason_code.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_refresh.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_secrets.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_sql_semantic_class.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_synthetic_policy_id_t79.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_t103_precedence.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_t104_cache_gc.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_t108_local_override_audit.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_t96_single_audit_log.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_t99_install_prefetch_bundle.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_tamper.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_tamper_behavior.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_tamper_hook.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_telemetry_consent.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_tracecontext.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_unsafe_int_boundary.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.5}/tools/cz-kiro-adapter +0 -0
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.5 -- 2026-06-16 (Kiro CLI -> GA, epic gh#877)
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **Kiro CLI (`kiro-cli chat`) promoted BETA -> GA.** The CLI surface routes
|
|
8
|
+
the full event JSON (snake_case `tool_name` / `tool_input`) through the same
|
|
9
|
+
`controlzero hook-check` -> policy engine -> audit path Claude Code / Codex /
|
|
10
|
+
Gemini ship on, with argument-level deny rules and `agent="kiro"`
|
|
11
|
+
(`canonical_source="kiro_cli"`) attribution. The **Kiro IDE** surface stays
|
|
12
|
+
**limited preview**: upstream Kiro #6188 (open through IDE v0.12) delivers an
|
|
13
|
+
empty payload to IDE `preToolUse` hooks, so argument-level pre-tool blocking
|
|
14
|
+
is impossible IDE-side; the IDE Pre Tool Use hook still ships disabled.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **`controlzero kiro verify`** -- install-validation self-test. Confirms the
|
|
19
|
+
Kiro CLI hook is wired into `settings.json` AND fires a synthetic known-deny
|
|
20
|
+
payload through the real hook to prove it blocks (exit 2). Exit 0 = installed
|
|
21
|
+
and enforcing; exit 1 = a problem (not wired, or a deny was not blocked).
|
|
22
|
+
|
|
23
|
+
- **Strict mode for the Kiro CLI hook-check path (`CZ_KIRO_CLI_STRICT`).** When
|
|
24
|
+
set (the Kiro CLI installer wires it on by default), `hook-check` fails
|
|
25
|
+
**closed** (exit 2, with a cause-specific diagnostic) on empty stdin,
|
|
26
|
+
malformed JSON, a missing `tool_name`, or an installed-but-unreadable local
|
|
27
|
+
policy -- distinguishing "policy unreadable" from "no policy installed". The
|
|
28
|
+
Kiro CLI always delivers a well-formed payload, so such a payload is an
|
|
29
|
+
anomaly, not a benign passthrough. The default for every other host stays
|
|
30
|
+
fail-open so an agent is never bricked by a partial payload.
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- Tightened the Kiro adapter docstring: IDE post-tool-use carries the tool
|
|
35
|
+
**name + result only** (`toolArgs` arrives empty, #6188), not the arguments.
|
|
36
|
+
|
|
3
37
|
## 1.9.4 -- 2026-06-16 (actionable E1101 / API-key-rejected message, #1254, epic gh#1247)
|
|
4
38
|
|
|
5
39
|
### Fixed
|
|
@@ -97,6 +131,7 @@ second-opinion). It is deliberately **narrow and gated**:
|
|
|
97
131
|
bundle translator -> `PolicySettings`. Local YAML policies may set
|
|
98
132
|
`settings.default_on_empty`. The dashboard observe-mode indicator +
|
|
99
133
|
CLI `status` line remain a separate frontend/CLI follow-up.
|
|
134
|
+
|
|
100
135
|
## 1.9.1 -- 2026-06-16 (hosted-mode local audit log P0, epic gh#1247)
|
|
101
136
|
|
|
102
137
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: controlzero
|
|
3
|
-
Version: 1.9.
|
|
3
|
+
Version: 1.9.5
|
|
4
4
|
Summary: AI agent governance: policies, audit, and observability for tool calls. Works locally with no signup.
|
|
5
5
|
Project-URL: Homepage, https://controlzero.ai
|
|
6
6
|
Project-URL: Documentation, https://docs.controlzero.ai
|
|
@@ -129,7 +129,7 @@ Templates available (`controlzero init -t <name>`):
|
|
|
129
129
|
- `autogen` — AutoGen starter policy
|
|
130
130
|
- `codex-cli` — Codex CLI hook starter
|
|
131
131
|
- `gemini-cli` — Gemini CLI hook starter
|
|
132
|
-
- `kiro` — Kiro (AWS
|
|
132
|
+
- `kiro` — Kiro (AWS) hook starter (CLI: GA; IDE: limited preview)
|
|
133
133
|
- `antigravity` — Google Antigravity (IDE + `agy` CLI) hook starter (Beta)
|
|
134
134
|
|
|
135
135
|
## Loading a policy
|
|
@@ -225,14 +225,27 @@ to the Control Zero backend so your team can see it on the dashboard.
|
|
|
225
225
|
|
|
226
226
|
## Local audit log
|
|
227
227
|
|
|
228
|
-
|
|
229
|
-
|
|
228
|
+
Every decision (allow **and** deny) is written to a local audit log in **every
|
|
229
|
+
mode** — local, hybrid, and hosted. The local log is never skipped, so
|
|
230
|
+
`controlzero tail`, `cz debug-bundle`, and the tamper hash-chain always have a
|
|
231
|
+
record to read.
|
|
230
232
|
|
|
231
233
|
```bash
|
|
232
234
|
controlzero tail
|
|
233
235
|
```
|
|
234
236
|
|
|
235
|
-
|
|
237
|
+
Default paths:
|
|
238
|
+
|
|
239
|
+
- **Local / unenrolled mode** (no API key): `./controlzero.log`, with daily
|
|
240
|
+
rotation and 30-day retention.
|
|
241
|
+
- **Hosted mode** (`CONTROLZERO_API_KEY` set): `~/.controlzero/audit.log` when you
|
|
242
|
+
do not pass an explicit `log_path`. Local audit is written **in addition to**
|
|
243
|
+
the remote dashboard sink, not instead of it — the remote sink is layered on
|
|
244
|
+
top. In hosted mode, PII and financial DLP `matched_text` is redacted from the
|
|
245
|
+
local plaintext row (the secret category is already hashed); the remote sink
|
|
246
|
+
keeps full fidelity.
|
|
247
|
+
|
|
248
|
+
Configure rotation via the client (honoured in any mode):
|
|
236
249
|
|
|
237
250
|
```python
|
|
238
251
|
cz = Client(
|
|
@@ -245,9 +258,6 @@ cz = Client(
|
|
|
245
258
|
)
|
|
246
259
|
```
|
|
247
260
|
|
|
248
|
-
When `CONTROLZERO_API_KEY` is set, audit ships to the remote dashboard and
|
|
249
|
-
these `log_*` options are ignored with a warning.
|
|
250
|
-
|
|
251
261
|
## Hybrid mode
|
|
252
262
|
|
|
253
263
|
Default (T103, 2026-05-12): when `CONTROLZERO_API_KEY` is set, the
|
|
@@ -75,7 +75,7 @@ Templates available (`controlzero init -t <name>`):
|
|
|
75
75
|
- `autogen` — AutoGen starter policy
|
|
76
76
|
- `codex-cli` — Codex CLI hook starter
|
|
77
77
|
- `gemini-cli` — Gemini CLI hook starter
|
|
78
|
-
- `kiro` — Kiro (AWS
|
|
78
|
+
- `kiro` — Kiro (AWS) hook starter (CLI: GA; IDE: limited preview)
|
|
79
79
|
- `antigravity` — Google Antigravity (IDE + `agy` CLI) hook starter (Beta)
|
|
80
80
|
|
|
81
81
|
## Loading a policy
|
|
@@ -171,14 +171,27 @@ to the Control Zero backend so your team can see it on the dashboard.
|
|
|
171
171
|
|
|
172
172
|
## Local audit log
|
|
173
173
|
|
|
174
|
-
|
|
175
|
-
|
|
174
|
+
Every decision (allow **and** deny) is written to a local audit log in **every
|
|
175
|
+
mode** — local, hybrid, and hosted. The local log is never skipped, so
|
|
176
|
+
`controlzero tail`, `cz debug-bundle`, and the tamper hash-chain always have a
|
|
177
|
+
record to read.
|
|
176
178
|
|
|
177
179
|
```bash
|
|
178
180
|
controlzero tail
|
|
179
181
|
```
|
|
180
182
|
|
|
181
|
-
|
|
183
|
+
Default paths:
|
|
184
|
+
|
|
185
|
+
- **Local / unenrolled mode** (no API key): `./controlzero.log`, with daily
|
|
186
|
+
rotation and 30-day retention.
|
|
187
|
+
- **Hosted mode** (`CONTROLZERO_API_KEY` set): `~/.controlzero/audit.log` when you
|
|
188
|
+
do not pass an explicit `log_path`. Local audit is written **in addition to**
|
|
189
|
+
the remote dashboard sink, not instead of it — the remote sink is layered on
|
|
190
|
+
top. In hosted mode, PII and financial DLP `matched_text` is redacted from the
|
|
191
|
+
local plaintext row (the secret category is already hashed); the remote sink
|
|
192
|
+
keeps full fidelity.
|
|
193
|
+
|
|
194
|
+
Configure rotation via the client (honoured in any mode):
|
|
182
195
|
|
|
183
196
|
```python
|
|
184
197
|
cz = Client(
|
|
@@ -191,9 +204,6 @@ cz = Client(
|
|
|
191
204
|
)
|
|
192
205
|
```
|
|
193
206
|
|
|
194
|
-
When `CONTROLZERO_API_KEY` is set, audit ships to the remote dashboard and
|
|
195
|
-
these `log_*` options are ignored with a warning.
|
|
196
|
-
|
|
197
207
|
## Hybrid mode
|
|
198
208
|
|
|
199
209
|
Default (T103, 2026-05-12): when `CONTROLZERO_API_KEY` is set, the
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Enable ``python -m controlzero.cli`` to run the Control Zero CLI.
|
|
2
|
+
|
|
3
|
+
Mirrors the ``controlzero`` console-script entry point. Used by
|
|
4
|
+
``controlzero kiro verify`` to invoke hook-check as a subprocess in a way
|
|
5
|
+
that works even when the console script is not on PATH (e.g. CI, venvs).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from controlzero.cli.main import cli
|
|
11
|
+
|
|
12
|
+
if __name__ == "__main__":
|
|
13
|
+
cli()
|
|
@@ -22,10 +22,13 @@ correct.
|
|
|
22
22
|
stdin shape before piping to hook-check, and sets ``CZ_KIRO_SURFACE=ide``
|
|
23
23
|
so KiroIDEAdapter claims the call.
|
|
24
24
|
|
|
25
|
-
Known upstream bug (kiro #7375 / #7408 / #7500): IDE ``preToolUse``
|
|
26
|
-
delivers ``USER_PROMPT={}`` with no tool context, so IDE
|
|
27
|
-
pre-tool policy is bounded
|
|
28
|
-
|
|
25
|
+
Known upstream bug (kiro #6188 / #7375 / #7408 / #7500): IDE ``preToolUse``
|
|
26
|
+
currently delivers ``USER_PROMPT={}`` with no tool context, so IDE
|
|
27
|
+
argument-level pre-tool policy is bounded. Prompt-submit carries the raw
|
|
28
|
+
prompt text; post-tool-use carries the tool *name + result only* (``toolArgs``
|
|
29
|
+
arrives empty, #6188), so IDE post-tool data does not include the tool's
|
|
30
|
+
arguments. See docs/integrations/kiro-hook-payloads.md. Pin the validated
|
|
31
|
+
Kiro version.
|
|
29
32
|
|
|
30
33
|
Note on adapter ordering: Claude Code matches PascalCase ``hook_event_name``
|
|
31
34
|
(``PreToolUse``), Kiro CLI matches camelCase (``preToolUse``) -- byte-disjoint,
|
|
@@ -686,21 +686,51 @@ def hook_check(policy: Optional[str]):
|
|
|
686
686
|
Designed for Claude Code's PreToolUse hook system. Other agents that send a
|
|
687
687
|
similar JSON-on-stdin protocol can reuse this command.
|
|
688
688
|
"""
|
|
689
|
+
# Strict mode (#1249 item 3): by default the hook-check CLI fails OPEN
|
|
690
|
+
# (exit 0) on empty-stdin / malformed-JSON / missing tool_name so a
|
|
691
|
+
# benign or truncated payload never bricks the host agent. That default
|
|
692
|
+
# is correct for hosts that can deliver malformed payloads (e.g. the
|
|
693
|
+
# Kiro IDE upstream empty-payload bug). The Kiro *CLI*, by contrast,
|
|
694
|
+
# always delivers a well-formed snake_case payload, so for that surface a
|
|
695
|
+
# missing tool_name is an anomaly that should be a deny, not a silent
|
|
696
|
+
# bypass. ``CZ_KIRO_CLI_STRICT`` (recommended default-on for Kiro CLI
|
|
697
|
+
# installs) flips those three cases to fail CLOSED (exit 2) with a
|
|
698
|
+
# specific diagnostic that distinguishes the cause. It does NOT change
|
|
699
|
+
# the hosted/tamper paths, which already fail closed.
|
|
700
|
+
_strict = _hook_check_strict()
|
|
701
|
+
|
|
689
702
|
# Read stdin -- Claude Code passes the tool payload here
|
|
690
703
|
try:
|
|
691
704
|
raw = sys.stdin.read()
|
|
692
705
|
except (KeyboardInterrupt, EOFError):
|
|
693
706
|
# No payload: pass through (do not break the agent)
|
|
694
|
-
sys.exit(0)
|
|
707
|
+
sys.exit(2 if _strict else 0)
|
|
695
708
|
|
|
696
709
|
if not raw.strip():
|
|
697
|
-
# Empty stdin
|
|
710
|
+
# Empty stdin.
|
|
711
|
+
if _strict:
|
|
712
|
+
click.echo(
|
|
713
|
+
"controlzero: empty hook payload on stdin; blocking "
|
|
714
|
+
"(CZ_KIRO_CLI_STRICT). The Kiro CLI always sends a payload -- "
|
|
715
|
+
"an empty one means the hook was misfired or stdin was lost.",
|
|
716
|
+
err=True,
|
|
717
|
+
)
|
|
718
|
+
sys.exit(2)
|
|
698
719
|
sys.exit(0)
|
|
699
720
|
|
|
700
721
|
try:
|
|
701
722
|
payload = json.loads(raw)
|
|
702
723
|
except json.JSONDecodeError as e:
|
|
703
|
-
# Malformed payload
|
|
724
|
+
# Malformed payload.
|
|
725
|
+
if _strict:
|
|
726
|
+
click.echo(
|
|
727
|
+
f"controlzero: hook payload is not JSON ({e}); blocking "
|
|
728
|
+
f"(CZ_KIRO_CLI_STRICT). A well-formed payload is expected from "
|
|
729
|
+
f"the Kiro CLI -- refusing to fail open on a parse error.",
|
|
730
|
+
err=True,
|
|
731
|
+
)
|
|
732
|
+
sys.exit(2)
|
|
733
|
+
# Default: log to stderr and pass through. Never brick the agent.
|
|
704
734
|
click.echo(f"controlzero: hook payload is not JSON ({e}); allowing", err=True)
|
|
705
735
|
sys.exit(0)
|
|
706
736
|
|
|
@@ -727,6 +757,14 @@ def hook_check(policy: Optional[str]):
|
|
|
727
757
|
tool_name = payload.get("tool_name") or payload.get("toolName") or ""
|
|
728
758
|
tool_args = payload.get("tool_input") or payload.get("toolInput") or {}
|
|
729
759
|
if not tool_name:
|
|
760
|
+
if _strict:
|
|
761
|
+
click.echo(
|
|
762
|
+
"controlzero: hook payload missing tool_name; blocking "
|
|
763
|
+
"(CZ_KIRO_CLI_STRICT). A security hook that cannot see which "
|
|
764
|
+
"tool is running must not fail open.",
|
|
765
|
+
err=True,
|
|
766
|
+
)
|
|
767
|
+
sys.exit(2)
|
|
730
768
|
click.echo("controlzero: hook payload missing tool_name; allowing", err=True)
|
|
731
769
|
sys.exit(0)
|
|
732
770
|
|
|
@@ -993,7 +1031,25 @@ def hook_check(policy: Optional[str]):
|
|
|
993
1031
|
log_path=str(GLOBAL_AUDIT_PATH),
|
|
994
1032
|
)
|
|
995
1033
|
except (PolicyLoadError, PolicyValidationError, PermissionError, OSError) as e:
|
|
996
|
-
# Bad/unreadable policy file: log + allow (do not silently
|
|
1034
|
+
# Bad/unreadable policy file. Default: log + allow (do not silently
|
|
1035
|
+
# break the agent). Strict mode: fail closed with a diagnostic that
|
|
1036
|
+
# distinguishes an unreadable/parse-broken policy from "no policy
|
|
1037
|
+
# installed" (which is the BUNDLE_MISSING path above and is governed
|
|
1038
|
+
# by default_on_missing). An installed-but-unreadable policy is an
|
|
1039
|
+
# operator error a security hook should not silently allow through.
|
|
1040
|
+
if _strict:
|
|
1041
|
+
click.echo(
|
|
1042
|
+
f"controlzero: local policy is present but unreadable/invalid "
|
|
1043
|
+
f"({e}); blocking (CZ_KIRO_CLI_STRICT). This is distinct from "
|
|
1044
|
+
f"'no policy installed' -- fix or re-install the policy file.",
|
|
1045
|
+
err=True,
|
|
1046
|
+
)
|
|
1047
|
+
_emit_decision(
|
|
1048
|
+
effect="deny",
|
|
1049
|
+
reason=f"[Control Zero] Local policy unreadable: {e}",
|
|
1050
|
+
reason_code=REASON_CODE_BUNDLE_MISSING,
|
|
1051
|
+
)
|
|
1052
|
+
sys.exit(2)
|
|
997
1053
|
click.echo(f"controlzero: policy file invalid ({e}); allowing", err=True)
|
|
998
1054
|
sys.exit(0)
|
|
999
1055
|
except Exception as e: # noqa: BLE001
|
|
@@ -1247,6 +1303,28 @@ def _resolve_default_on_missing() -> str:
|
|
|
1247
1303
|
return DEFAULT_BUNDLE_ON_MISSING
|
|
1248
1304
|
|
|
1249
1305
|
|
|
1306
|
+
def _hook_check_strict() -> bool:
|
|
1307
|
+
"""True if the hook-check CLI should fail CLOSED (exit 2) on a
|
|
1308
|
+
malformed/empty/missing payload or an unreadable local policy
|
|
1309
|
+
instead of the default fail-open (exit 0).
|
|
1310
|
+
|
|
1311
|
+
Controlled by ``CZ_KIRO_CLI_STRICT`` (truthy: 1/true/yes/on). The
|
|
1312
|
+
name is Kiro-scoped because the Kiro CLI is the surface this is
|
|
1313
|
+
recommended-on for: it always delivers a well-formed snake_case
|
|
1314
|
+
payload, so any empty/malformed/tool_name-less payload reaching the
|
|
1315
|
+
hook is an anomaly that should be a deny, not a silent bypass.
|
|
1316
|
+
``controlzero kiro init`` enables it on Kiro CLI installs; other
|
|
1317
|
+
hosts (which can legitimately deliver partial payloads) keep the
|
|
1318
|
+
fail-open default so the agent is never bricked. Off when unset.
|
|
1319
|
+
"""
|
|
1320
|
+
return (os.environ.get("CZ_KIRO_CLI_STRICT") or "").strip().lower() in (
|
|
1321
|
+
"1",
|
|
1322
|
+
"true",
|
|
1323
|
+
"yes",
|
|
1324
|
+
"on",
|
|
1325
|
+
)
|
|
1326
|
+
|
|
1327
|
+
|
|
1250
1328
|
def _append_event(event: dict) -> None:
|
|
1251
1329
|
"""Append a structured lifecycle event to ~/.controlzero/audit.log.
|
|
1252
1330
|
|
|
@@ -2067,8 +2145,10 @@ def install_claude_code(force: bool, merge: bool, settings: Optional[str], api_k
|
|
|
2067
2145
|
click.echo("")
|
|
2068
2146
|
click.echo("[Control Zero] Installed successfully.")
|
|
2069
2147
|
click.echo("")
|
|
2070
|
-
click.echo("
|
|
2071
|
-
click.echo("
|
|
2148
|
+
click.echo(" Mode: OBSERVE-ONLY (audit on, enforcement off)")
|
|
2149
|
+
click.echo(" Every tool call is logged to the audit log; nothing is blocked yet.")
|
|
2150
|
+
click.echo(" This is the intended starting posture -- watch first, then enforce.")
|
|
2151
|
+
click.echo(" To enforce, add deny rules above the catch-all in your policy.")
|
|
2072
2152
|
click.echo("")
|
|
2073
2153
|
click.echo("Test it:")
|
|
2074
2154
|
click.echo(" 1. Open Claude Code in any project")
|
|
@@ -3134,7 +3214,12 @@ _KIRO_IDE_HOOKS = (
|
|
|
3134
3214
|
# tool use + prompt submit cover the block point, audit, and prompt scan.
|
|
3135
3215
|
_KIRO_CLI_EVENTS = ("preToolUse", "postToolUse", "userPromptSubmit")
|
|
3136
3216
|
|
|
3137
|
-
|
|
3217
|
+
# Strict-by-default for Kiro CLI installs (#1249 item 3): the Kiro CLI always
|
|
3218
|
+
# delivers a well-formed snake_case payload, so an empty/malformed/tool_name-
|
|
3219
|
+
# less payload reaching the hook is an anomaly that should fail CLOSED rather
|
|
3220
|
+
# than silently allow. CZ_KIRO_CLI_STRICT=1 is set inline on the hook command
|
|
3221
|
+
# so it applies only to this surface (other hosts keep the fail-open default).
|
|
3222
|
+
_KIRO_CLI_HOOK_COMMAND = "CZ_KIRO_CLI_STRICT=1 controlzero hook-check"
|
|
3138
3223
|
_KIRO_IDE_HOOK_TEMPLATE_DIR = TEMPLATE_DIR / "kiro"
|
|
3139
3224
|
|
|
3140
3225
|
|
|
@@ -3467,6 +3552,7 @@ def kiro_init(
|
|
|
3467
3552
|
click.echo("")
|
|
3468
3553
|
click.echo("Test it:")
|
|
3469
3554
|
if surface in ("cli", "both"):
|
|
3555
|
+
click.echo(" controlzero kiro verify -> confirm the CLI hook is wired and blocks")
|
|
3470
3556
|
click.echo(" Kiro CLI: kiro-cli chat -> every tool call shows '[Control Zero] ...'")
|
|
3471
3557
|
if surface in ("ide", "both"):
|
|
3472
3558
|
click.echo(" Kiro IDE: reload the workspace so Kiro picks up .kiro/hooks/")
|
|
@@ -3492,6 +3578,188 @@ def kiro_init(
|
|
|
3492
3578
|
_print_api_key_status(api_key)
|
|
3493
3579
|
|
|
3494
3580
|
|
|
3581
|
+
def _kiro_cli_installed_command(settings_path: Path) -> Optional[str]:
|
|
3582
|
+
"""Return the EXACT `controlzero hook-check` command string Kiro will run
|
|
3583
|
+
for preToolUse, read from the installed settings.json, or None if our hook
|
|
3584
|
+
is not wired. `kiro verify` runs THIS string (through a shell, the way Kiro
|
|
3585
|
+
does) so the self-test catches PATH / shell / inline-env / Windows
|
|
3586
|
+
resolution failures that an in-process invocation would mask."""
|
|
3587
|
+
if not settings_path.exists():
|
|
3588
|
+
return None
|
|
3589
|
+
try:
|
|
3590
|
+
existing = json.loads(settings_path.read_text(encoding="utf-8"))
|
|
3591
|
+
except (json.JSONDecodeError, OSError):
|
|
3592
|
+
return None
|
|
3593
|
+
if not isinstance(existing, dict):
|
|
3594
|
+
return None
|
|
3595
|
+
bucket = (existing.get("hooks") or {}).get("preToolUse") or []
|
|
3596
|
+
if not isinstance(bucket, list):
|
|
3597
|
+
return None
|
|
3598
|
+
for block in bucket:
|
|
3599
|
+
if not _kiro_cli_block_is_ours(block):
|
|
3600
|
+
continue
|
|
3601
|
+
for h in block.get("hooks") or []:
|
|
3602
|
+
cmd = h.get("command") if isinstance(h, dict) else None
|
|
3603
|
+
if isinstance(cmd, str) and "controlzero hook-check" in cmd:
|
|
3604
|
+
return cmd
|
|
3605
|
+
return None
|
|
3606
|
+
|
|
3607
|
+
|
|
3608
|
+
def _kiro_cli_hook_installed(settings_path: Path) -> bool:
|
|
3609
|
+
"""True if our `controlzero hook-check` block is wired into the Kiro CLI
|
|
3610
|
+
settings.json preToolUse bucket. Mirrors the install idempotency check."""
|
|
3611
|
+
return _kiro_cli_installed_command(settings_path) is not None
|
|
3612
|
+
|
|
3613
|
+
|
|
3614
|
+
@kiro.command("verify")
|
|
3615
|
+
@click.option(
|
|
3616
|
+
"--settings",
|
|
3617
|
+
type=click.Path(),
|
|
3618
|
+
default=None,
|
|
3619
|
+
help="Path to Kiro CLI settings.json. Defaults to ~/.kiro/settings.json.",
|
|
3620
|
+
)
|
|
3621
|
+
def kiro_verify(settings: Optional[str]) -> None:
|
|
3622
|
+
"""Self-test the Kiro CLI hook: confirm it is wired AND actually blocks.
|
|
3623
|
+
|
|
3624
|
+
Fires a synthetic known-deny tool call through the real
|
|
3625
|
+
`controlzero hook-check` path (the exact command Kiro CLI invokes) and
|
|
3626
|
+
asserts it exits 2 (block). Also fires a result-only postToolUse payload
|
|
3627
|
+
and asserts it does NOT spuriously block. Prints a green/red summary.
|
|
3628
|
+
|
|
3629
|
+
Exit 0 = the hook is installed and enforcing; exit 1 = a problem was
|
|
3630
|
+
found (not wired, or a deny payload was not blocked). This is the
|
|
3631
|
+
install-validation command for a Kiro CLI GA install.
|
|
3632
|
+
"""
|
|
3633
|
+
import subprocess
|
|
3634
|
+
import tempfile
|
|
3635
|
+
|
|
3636
|
+
settings_path = (
|
|
3637
|
+
Path(settings) if settings else Path.home() / ".kiro" / "settings.json"
|
|
3638
|
+
)
|
|
3639
|
+
|
|
3640
|
+
ok = True
|
|
3641
|
+
|
|
3642
|
+
# Check 1: is our hook wired into the CLI settings.json? Capture the EXACT
|
|
3643
|
+
# command string Kiro will run so checks 2/3 exercise it verbatim.
|
|
3644
|
+
installed_cmd = _kiro_cli_installed_command(settings_path)
|
|
3645
|
+
if installed_cmd is not None:
|
|
3646
|
+
click.echo(f" [PASS] Kiro CLI hook wired in {settings_path}")
|
|
3647
|
+
else:
|
|
3648
|
+
ok = False
|
|
3649
|
+
click.echo(
|
|
3650
|
+
f" [FAIL] Kiro CLI hook NOT found in {settings_path}. "
|
|
3651
|
+
"Run `controlzero kiro init --surface cli` first.",
|
|
3652
|
+
err=True,
|
|
3653
|
+
)
|
|
3654
|
+
|
|
3655
|
+
# Check 2 + 3: fire synthetic payloads against a deterministic deny policy
|
|
3656
|
+
# (a sentinel deny + catch-all allow) so the result does not depend on the
|
|
3657
|
+
# user's installed policy. We run the EXACT installed command string through
|
|
3658
|
+
# the same shell Kiro uses -- so a `controlzero`-not-on-PATH, a non-POSIX
|
|
3659
|
+
# shell, or an unsupported inline `VAR=1 cmd` on Windows is CAUGHT here
|
|
3660
|
+
# rather than silently passing. When the hook is not installed we fall back
|
|
3661
|
+
# to the in-process module form purely to still exercise the engine, and we
|
|
3662
|
+
# SAY SO, because that fallback does not prove Kiro can resolve the command.
|
|
3663
|
+
sentinel = "controlzero_kiro_verify_sentinel"
|
|
3664
|
+
use_real_cmd = installed_cmd is not None
|
|
3665
|
+
if not use_real_cmd:
|
|
3666
|
+
click.echo(
|
|
3667
|
+
" [WARN] hook not installed; running the engine via "
|
|
3668
|
+
"`python -m controlzero.cli` -- this proves the policy blocks but "
|
|
3669
|
+
"NOT that Kiro can resolve the installed command. Install first "
|
|
3670
|
+
"for a full verify.",
|
|
3671
|
+
err=True,
|
|
3672
|
+
)
|
|
3673
|
+
with tempfile.TemporaryDirectory() as td:
|
|
3674
|
+
pol = Path(td) / "verify-policy.yaml"
|
|
3675
|
+
pol.write_text(
|
|
3676
|
+
yaml.safe_dump(
|
|
3677
|
+
{
|
|
3678
|
+
"version": "1",
|
|
3679
|
+
"rules": [
|
|
3680
|
+
{"deny": sentinel, "reason": "kiro verify self-test deny"},
|
|
3681
|
+
{"allow": "*"},
|
|
3682
|
+
],
|
|
3683
|
+
}
|
|
3684
|
+
),
|
|
3685
|
+
encoding="utf-8",
|
|
3686
|
+
)
|
|
3687
|
+
|
|
3688
|
+
def _run(payload: dict) -> int:
|
|
3689
|
+
env = dict(os.environ)
|
|
3690
|
+
env["CZ_KIRO_CLI_STRICT"] = "1"
|
|
3691
|
+
if use_real_cmd:
|
|
3692
|
+
# Run the literal installed string through a shell, exactly as
|
|
3693
|
+
# Kiro executes its `type: "command"` hooks. Append --policy so
|
|
3694
|
+
# the decision is deterministic regardless of the user's policy.
|
|
3695
|
+
# The `CZ_KIRO_CLI_STRICT=1` prefix already in the string is
|
|
3696
|
+
# harmless to re-set via env; the shell honors both.
|
|
3697
|
+
cmd = f'{installed_cmd} --policy "{pol}"'
|
|
3698
|
+
proc = subprocess.run(
|
|
3699
|
+
cmd,
|
|
3700
|
+
shell=True,
|
|
3701
|
+
input=json.dumps(payload),
|
|
3702
|
+
text=True,
|
|
3703
|
+
capture_output=True,
|
|
3704
|
+
env=env,
|
|
3705
|
+
)
|
|
3706
|
+
else:
|
|
3707
|
+
proc = subprocess.run(
|
|
3708
|
+
[sys.executable, "-m", "controlzero.cli", "hook-check",
|
|
3709
|
+
"--policy", str(pol)],
|
|
3710
|
+
input=json.dumps(payload),
|
|
3711
|
+
text=True,
|
|
3712
|
+
capture_output=True,
|
|
3713
|
+
env=env,
|
|
3714
|
+
)
|
|
3715
|
+
return proc.returncode
|
|
3716
|
+
|
|
3717
|
+
deny_payload = {
|
|
3718
|
+
"hook_event_name": "preToolUse",
|
|
3719
|
+
"tool_name": sentinel,
|
|
3720
|
+
"tool_input": {"command": "rm -rf /"},
|
|
3721
|
+
}
|
|
3722
|
+
rc_deny = _run(deny_payload)
|
|
3723
|
+
if rc_deny == 2:
|
|
3724
|
+
click.echo(" [PASS] synthetic preToolUse deny -> blocked (exit 2)")
|
|
3725
|
+
else:
|
|
3726
|
+
ok = False
|
|
3727
|
+
click.echo(
|
|
3728
|
+
f" [FAIL] synthetic preToolUse deny was NOT blocked "
|
|
3729
|
+
f"(exit {rc_deny}, expected 2). The hook is not enforcing.",
|
|
3730
|
+
err=True,
|
|
3731
|
+
)
|
|
3732
|
+
|
|
3733
|
+
# postToolUse carries a result, not a fresh pre-tool decision; a deny
|
|
3734
|
+
# rule on the tool name should still not let a result-only event ride
|
|
3735
|
+
# as a block surprise. We send the sentinel under postToolUse and only
|
|
3736
|
+
# assert it does not error out unexpectedly (exit 0 or 2 are both valid
|
|
3737
|
+
# given the deny rule; a crash / other code is the failure).
|
|
3738
|
+
result_payload = {
|
|
3739
|
+
"hook_event_name": "postToolUse",
|
|
3740
|
+
"tool_name": "fs_read",
|
|
3741
|
+
"tool_input": {},
|
|
3742
|
+
"tool_response": {"ok": True},
|
|
3743
|
+
}
|
|
3744
|
+
rc_post = _run(result_payload)
|
|
3745
|
+
if rc_post == 0:
|
|
3746
|
+
click.echo(" [PASS] benign postToolUse result -> not blocked (exit 0)")
|
|
3747
|
+
else:
|
|
3748
|
+
ok = False
|
|
3749
|
+
click.echo(
|
|
3750
|
+
f" [FAIL] benign postToolUse result unexpectedly returned "
|
|
3751
|
+
f"exit {rc_post} (expected 0).",
|
|
3752
|
+
err=True,
|
|
3753
|
+
)
|
|
3754
|
+
|
|
3755
|
+
click.echo("")
|
|
3756
|
+
if ok:
|
|
3757
|
+
click.echo("[Control Zero] Kiro CLI verify: PASS -- hook installed and enforcing.")
|
|
3758
|
+
sys.exit(0)
|
|
3759
|
+
click.echo("[Control Zero] Kiro CLI verify: FAIL -- see findings above.", err=True)
|
|
3760
|
+
sys.exit(1)
|
|
3761
|
+
|
|
3762
|
+
|
|
3495
3763
|
# Register the `controlzero debug ...` subcommand group. Defined in
|
|
3496
3764
|
# controlzero.cli.debug_bundle to keep the bundle-inspection logic
|
|
3497
3765
|
# isolated from the existing CLI body. Today the group exposes the
|
|
@@ -5,6 +5,21 @@
|
|
|
5
5
|
# ALLOW-BY-DEFAULT and lists explicit deny rules. The goal: visible audit
|
|
6
6
|
# from day one, with a few opinionated denies you can extend.
|
|
7
7
|
#
|
|
8
|
+
# MODE: OBSERVE-ONLY (audit on, enforcement off).
|
|
9
|
+
# The catch-all `allow: '*'` at the BOTTOM of this file means every tool
|
|
10
|
+
# call is allowed AND logged. Nothing is blocked yet -- this is the
|
|
11
|
+
# intended starting posture: watch first, then enforce. To enforce, add
|
|
12
|
+
# deny rules ABOVE the catch-all (examples are commented below). You are
|
|
13
|
+
# not yet blocking anything; you are recording everything.
|
|
14
|
+
#
|
|
15
|
+
# HEADS UP -- local default vs hosted default differ on purpose:
|
|
16
|
+
# This LOCAL template ends with `allow: '*'`, so an unmatched tool is
|
|
17
|
+
# ALLOWED (observe-only). A HOSTED/dashboard policy has NO catch-all and
|
|
18
|
+
# defaults to DENY on an unmatched tool (`default_action: deny`). So a
|
|
19
|
+
# hosted "block deletes" policy will also block Bash/Read/etc unless you
|
|
20
|
+
# add a catch-all `allow: '*'` rule or set `default_action: allow`.
|
|
21
|
+
# See the "Enforcement Behavior" concept doc (default_action / NO_RULE_MATCH).
|
|
22
|
+
#
|
|
8
23
|
# Where this file lives:
|
|
9
24
|
# ~/.controlzero/policy.yaml
|
|
10
25
|
#
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "controlzero"
|
|
7
|
-
version = "1.9.
|
|
7
|
+
version = "1.9.5"
|
|
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"}
|