controlzero 1.8.1__tar.gz → 1.8.2__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.8.1 → controlzero-1.8.2}/CHANGELOG.md +56 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/PKG-INFO +1 -1
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/__init__.py +1 -1
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/kiro_adapter.py +42 -2
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/main.py +2 -2
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/templates/kiro/ide-file-save.kiro.hook +2 -2
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/templates/kiro/ide-pre-tool-use.kiro.hook +2 -2
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/templates/kiro/ide-prompt-submit.kiro.hook +3 -3
- {controlzero-1.8.1 → controlzero-1.8.2}/pyproject.toml +1 -1
- controlzero-1.8.2/tests/test_kiro_hook_templates.py +324 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/.gitignore +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/Dockerfile.test +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/LICENSE +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/README.md +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/_internal/__init__.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/_internal/action_aliases.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/_internal/action_validator.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/_internal/bundle.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/_internal/credential_hook.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/_internal/credential_scanner.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/_internal/credentials_data/__init__.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/_internal/credentials_data/built_in.yaml +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/_internal/dlp_scanner.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/_internal/enforcer.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/_internal/hook_extractors.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/_internal/tool_extractors.json +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/_internal/types.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/audit_local.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/audit_remote.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/canonical.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/__init__.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/_secrets.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/console.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/debug_bundle.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/doctor.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/hosts/__init__.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/hosts/antigravity.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/hosts/base.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/hosts/claude_code.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/hosts/codex_cli.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/hosts/gemini_cli.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/hosts/kiro.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/hosts/unknown.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/migrate.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/spool_cmd.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/telemetry_consent.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/templates/autogen.yaml +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/templates/claude-code.yaml +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/templates/codex-cli.yaml +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/templates/cost-cap.yaml +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/templates/crewai.yaml +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/templates/cursor.yaml +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/templates/gemini-cli.yaml +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/templates/generic.yaml +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/templates/kiro/kiro.yaml +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/templates/langchain.yaml +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/templates/mcp.yaml +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/templates/rag.yaml +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/client.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/device.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/enrollment.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/error_codes.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/error_codes.yaml +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/errors.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/hitl/__init__.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/hitl/grant_protocol.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/hitl/mock.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/hitl/pending_approval.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/hitl/secret_leak_guard.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/hitl/status.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/hooks/__init__.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/hooks/tool_output_handler.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/hosted_policy.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/__init__.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/anthropic.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/autogen.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/braintrust.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/crewai/__init__.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/crewai/agent.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/crewai/crew.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/crewai/task.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/crewai/tool.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/google.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/google_adk/__init__.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/google_adk/agent.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/google_adk/tool.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/langchain/__init__.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/langchain/agent.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/langchain/callbacks.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/langchain/chain.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/langchain/graph.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/langchain/modern.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/langchain/tool.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/langfuse.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/litellm.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/openai.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/pydantic_ai.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/vercel_ai.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/layout_migration.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/policy_loader.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/spool/__init__.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/spool/_compress.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/spool/_constants.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/spool/_crc32c.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/spool/_crypto.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/spool/_frame.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/spool/_metrics.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/spool/_spool.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/spool/_state.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/spool/_uploader.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/spool/cz-audit-v1.dict +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/tamper.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/tracecontext.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/examples/hello_world.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/_fixtures/jcs_args_hash_vectors.json +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/conftest.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/integrations/__init__.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/integrations/test_google.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/parity/action_aliases.json +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/spool/__init__.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/spool/conftest.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/spool/test_spool_cli.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/spool/test_spool_concurrency.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/spool/test_spool_conformance.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/spool/test_spool_core.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/spool/test_spool_crash.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/spool/test_spool_diskfull.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/spool/test_spool_sink_wiring.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/spool/test_spool_transcript_localack.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/spool/test_spool_uploader.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_action_aliases.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_action_canonicalization.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_action_validator_t86.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_agent_name_env.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_api_key_mask.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_audit_remote.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_audit_remote_sdk_version.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_audit_sink_isolation.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_bundle_parser.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_bundle_translate.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_canonical_phase1a.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_cli_carve_out.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_cli_debug_bundle.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_cli_extractor_integration.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_cli_hook.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_cli_hosted_refresh.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_cli_init.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_cli_init_templates.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_cli_tail.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_cli_test.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_cli_validate.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_coding_agent_hooks.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_conditions.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_conformance.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_console.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_credential_hook.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_default_action.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_device.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_dlp_scanner.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_doctor.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_engine_version_consistency.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_enrollment.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_env_dump_438.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_error_codes.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_errors_e_codes.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_fail_closed_eval.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_glob_matching.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hitl_5d_email_install.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hitl_6a_cli_flag.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hitl_6a_exceptions.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hitl_6a_get_secret_hitl.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hitl_6a_mock_backend.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hitl_6a_pending_approval.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hitl_6a_request_approval.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hitl_6a_secret_leak_guard.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hitl_6a_wait.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hitl_conformance.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hitl_phase2b_protocol.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hitl_reason_codes.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hitl_validator_keys.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hook_extractors.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hosted_policy_e2e.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hosts_adapter.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hybrid_mode_strict.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hybrid_mode_warn.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_install_hook_command.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_install_hooks.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_kiro_adapter.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_kiro_install.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_layout_migration_t101.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_layout_parity_t102.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_local_mode_dict.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_local_mode_file_json.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_local_mode_file_yaml.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_log_fallback_stderr.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_log_options_ignored_hosted.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_log_rotation.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_migrate.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_min_sdk_version_gate.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_multi_client_per_project_175.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_no_policy_no_key.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_package_rename_shim.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_policy_engine_version_phase1b.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_policy_freshness.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_policy_settings.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_policy_source_audit.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_quarantine.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_reason_code.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_refresh.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_secrets.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_sql_semantic_class.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_synthetic_policy_id_t79.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_t103_precedence.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_t104_cache_gc.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_t108_local_override_audit.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_t96_single_audit_log.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_t99_install_prefetch_bundle.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_tamper.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_tamper_behavior.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_tamper_hook.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_telemetry_consent.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_tracecontext.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_unsafe_int_boundary.py +0 -0
- {controlzero-1.8.1 → controlzero-1.8.2}/tools/cz-kiro-adapter +0 -0
|
@@ -1,5 +1,61 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.8.2 -- 2026-06-15 (Kiro IDE hook contract hotfix, epic gh#877)
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
The shipped Kiro IDE `.kiro.hook` templates used several **Kiro CLI** schema
|
|
8
|
+
conventions instead of the **Kiro IDE** ones (the two hook systems have
|
|
9
|
+
different event names and field keys). The most visible symptom was a
|
|
10
|
+
customer-reported P0; auditing the templates against Kiro's IDE schema
|
|
11
|
+
surfaced three more latent defects. All four are fixed, and a new regression
|
|
12
|
+
test (`tests/test_kiro_hook_templates.py`) pins each so they cannot ship again.
|
|
13
|
+
|
|
14
|
+
- **(P0) Prompt-submit hook rejected by the IDE.** `ide-prompt-submit.kiro.hook`
|
|
15
|
+
declared `"when": {"type": "userPromptSubmit"}`. `userPromptSubmit` is the
|
|
16
|
+
Kiro **CLI** event name; the IDE event is **`promptSubmit`**. The invalid
|
|
17
|
+
value made the IDE reject the entire hook file ("Hook file has invalid data
|
|
18
|
+
structure"), so the only active data-carrying IDE hook (prompt-submit, while
|
|
19
|
+
preToolUse ships disabled) never loaded and the IDE integration silently did
|
|
20
|
+
nothing. Now `promptSubmit`, with the matching `cz-kiro-adapter promptSubmit`
|
|
21
|
+
command. (The shim already accepts both spellings, so no behavior change.)
|
|
22
|
+
|
|
23
|
+
- **Pre-tool-use tool filter used the wrong key.** `ide-pre-tool-use.kiro.hook`
|
|
24
|
+
filtered tools with `"patterns": ["*"]`; the Kiro IDE tool filter key for
|
|
25
|
+
`preToolUse`/`postToolUse` is **`toolTypes`** (array). Now `"toolTypes":
|
|
26
|
+
["*"]`, so the hook is schema-correct if/when it is enabled (it still ships
|
|
27
|
+
`enabled: false` pending the upstream empty-payload bug).
|
|
28
|
+
|
|
29
|
+
- **File-save hook passed a tool-event arg + could block saves.**
|
|
30
|
+
`ide-file-save.kiro.hook` ran `cz-kiro-adapter postToolUse`, but a
|
|
31
|
+
`fileEdited` runCommand does not deliver tool context; the adapter would then
|
|
32
|
+
fail closed (block) on the empty payload -- i.e. it could block file saves.
|
|
33
|
+
The command is now `cz-kiro-adapter fileEdited`, and the shim treats file
|
|
34
|
+
events as a post-write **observation**: it synthesizes a `fileSave` tool
|
|
35
|
+
call from whatever `USER_PROMPT` carries, still runs `hook-check` for the
|
|
36
|
+
audit row + secret scan, but **always exits 0** on a file event -- a deny is
|
|
37
|
+
surfaced on stderr as a warning rather than blocking. The file is already
|
|
38
|
+
written, so blocking is pointless; only tool events (preToolUse) gate.
|
|
39
|
+
`when.type` stays the valid `fileEdited` with `patterns`.
|
|
40
|
+
|
|
41
|
+
- **Timeouts were ~83 minutes, not 5 seconds.** All three templates used
|
|
42
|
+
`"timeout": 5000`. Kiro IDE `then.timeout` is in **seconds** (default 60,
|
|
43
|
+
`0` disables), so `5000` meant ~83 minutes. Now `5` (seconds) on all three.
|
|
44
|
+
|
|
45
|
+
The regression test parses every shipped `*.kiro.hook` and asserts: valid
|
|
46
|
+
JSON; `when.type` in Kiro's 11-value IDE event set; tool events use
|
|
47
|
+
`toolTypes` (not `patterns`) and file events use `patterns` (not `toolTypes`);
|
|
48
|
+
`then.timeout` is a small integer (seconds); and the `runCommand` event
|
|
49
|
+
argument is one the `cz-kiro-adapter` shim recognizes.
|
|
50
|
+
|
|
51
|
+
### Known upstream Kiro limits (not fixed here -- tracked upstream)
|
|
52
|
+
|
|
53
|
+
- IDE `preToolUse` delivers `USER_PROMPT={}` (no tool context): kirodotdev/Kiro
|
|
54
|
+
#7375 / #7408 / #7500. Our pre-tool-use IDE hook stays disabled.
|
|
55
|
+
- IDE `postToolUse` delivers `toolArgs: {}` (kirodotdev/Kiro #6188), so
|
|
56
|
+
argument-level post-tool policy is bounded on the IDE surface regardless of
|
|
57
|
+
schema correctness.
|
|
58
|
+
|
|
3
59
|
## 1.8.0 -- 2026-06-15 (Kiro, epic gh#877)
|
|
4
60
|
|
|
5
61
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: controlzero
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.2
|
|
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
|
|
@@ -7,8 +7,9 @@ on stdin like the Kiro CLI. This shim normalises that into the snake_case JSON
|
|
|
7
7
|
shape ``controlzero hook-check`` consumes, marks the surface
|
|
8
8
|
(``CZ_KIRO_SURFACE=ide`` so KiroIDEAdapter claims it), and pipes it through.
|
|
9
9
|
|
|
10
|
-
Wire it as the IDE's preToolUse /
|
|
11
|
-
|
|
10
|
+
Wire it as the IDE's preToolUse / promptSubmit / fileEdited hook command (the
|
|
11
|
+
IDE `when.type` event names -- note prompt-submit is ``promptSubmit`` on the
|
|
12
|
+
IDE surface, NOT the Kiro CLI's ``userPromptSubmit``), e.g.::
|
|
12
13
|
|
|
13
14
|
cz-kiro-adapter preToolUse
|
|
14
15
|
|
|
@@ -37,6 +38,12 @@ import subprocess
|
|
|
37
38
|
import sys
|
|
38
39
|
|
|
39
40
|
|
|
41
|
+
# Kiro IDE file events. These are a post-write OBSERVATION surface (scan the
|
|
42
|
+
# saved file / audit it), never a gate, so the adapter must not fail closed on
|
|
43
|
+
# them -- a file save can't be blocked by missing context.
|
|
44
|
+
_FILE_EVENTS = frozenset({"fileEdited", "fileCreated", "fileDeleted"})
|
|
45
|
+
|
|
46
|
+
|
|
40
47
|
def _event_from_argv(argv: list[str]) -> str:
|
|
41
48
|
# First non-flag arg is the event name; default to preToolUse.
|
|
42
49
|
for a in argv[1:]:
|
|
@@ -66,6 +73,25 @@ def _normalize(event: str, raw: str) -> dict | None:
|
|
|
66
73
|
base["tool_input"] = {"prompt": raw}
|
|
67
74
|
return base
|
|
68
75
|
|
|
76
|
+
if event in _FILE_EVENTS:
|
|
77
|
+
# File events are a POST-WRITE observation, not a gate: a save must
|
|
78
|
+
# never be blocked by missing context, so this branch NEVER returns
|
|
79
|
+
# None (no fail-closed). Kiro IDE does not document a guaranteed
|
|
80
|
+
# structured payload for a file-event runCommand (USER_PROMPT may be a
|
|
81
|
+
# path, a JSON object, or empty), so we synthesize a write-class tool
|
|
82
|
+
# call from whatever arrived and let hook-check scan/audit it.
|
|
83
|
+
base["tool_name"] = "fileSave"
|
|
84
|
+
payload: dict = {}
|
|
85
|
+
text = (raw or "").strip()
|
|
86
|
+
if text:
|
|
87
|
+
try:
|
|
88
|
+
parsed = json.loads(text)
|
|
89
|
+
payload = parsed if isinstance(parsed, dict) else {"raw": text}
|
|
90
|
+
except json.JSONDecodeError:
|
|
91
|
+
payload = {"file_path": text} # most likely a bare path
|
|
92
|
+
base["tool_input"] = payload
|
|
93
|
+
return base
|
|
94
|
+
|
|
69
95
|
raw = (raw or "").strip()
|
|
70
96
|
if not raw:
|
|
71
97
|
return None
|
|
@@ -122,6 +148,20 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
122
148
|
# controlzero not on PATH: never brick the IDE.
|
|
123
149
|
print("controlzero(kiro): controlzero CLI not found; allowing", file=sys.stderr)
|
|
124
150
|
return 0
|
|
151
|
+
|
|
152
|
+
if event in _FILE_EVENTS:
|
|
153
|
+
# OBSERVE-ONLY: a file event is post-write -- the file is already
|
|
154
|
+
# saved, so blocking is pointless and only disrupts the editor. We
|
|
155
|
+
# still run hook-check above for the audit row + secret scan, but a
|
|
156
|
+
# deny is surfaced as a warning and the save is allowed (exit 0).
|
|
157
|
+
if proc.returncode == 2:
|
|
158
|
+
print(
|
|
159
|
+
"controlzero(kiro): file-save policy flagged this write "
|
|
160
|
+
"(post-write, observe-only; not blocking the save)",
|
|
161
|
+
file=sys.stderr,
|
|
162
|
+
)
|
|
163
|
+
return 0
|
|
164
|
+
|
|
125
165
|
return proc.returncode
|
|
126
166
|
|
|
127
167
|
|
|
@@ -2699,8 +2699,8 @@ def uninstall_codex_cli(config: Optional[str]):
|
|
|
2699
2699
|
# is the block point; Prompt Submit scans the prompt; File Save scans writes.
|
|
2700
2700
|
_KIRO_IDE_HOOKS = (
|
|
2701
2701
|
("preToolUse", "controlzero-pre-tool-use", "ide-pre-tool-use.kiro.hook"),
|
|
2702
|
-
("
|
|
2703
|
-
("
|
|
2702
|
+
("promptSubmit", "controlzero-prompt-submit", "ide-prompt-submit.kiro.hook"),
|
|
2703
|
+
("fileEdited", "controlzero-file-save", "ide-file-save.kiro.hook"),
|
|
2704
2704
|
)
|
|
2705
2705
|
|
|
2706
2706
|
# Kiro CLI hook events: command is `controlzero hook-check` directly. Pre/post
|
{controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/templates/kiro/ide-prompt-submit.kiro.hook
RENAMED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
"description": "Control Zero governance: scan each user prompt submitted to Kiro against your policy. Carries data even where the Pre Tool Use payload is bounded by the upstream empty-payload bug.",
|
|
5
5
|
"version": "1",
|
|
6
6
|
"when": {
|
|
7
|
-
"type": "
|
|
7
|
+
"type": "promptSubmit"
|
|
8
8
|
},
|
|
9
9
|
"then": {
|
|
10
10
|
"type": "runCommand",
|
|
11
|
-
"command": "cz-kiro-adapter
|
|
12
|
-
"timeout":
|
|
11
|
+
"command": "cz-kiro-adapter promptSubmit",
|
|
12
|
+
"timeout": 5
|
|
13
13
|
}
|
|
14
14
|
}
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "controlzero"
|
|
7
|
-
version = "1.8.
|
|
7
|
+
version = "1.8.2"
|
|
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"}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"""Regression guard for the shipped Kiro IDE hook templates (epic gh#877).
|
|
2
|
+
|
|
3
|
+
Customer P0 (1.8.0): `ide-prompt-submit.kiro.hook` shipped
|
|
4
|
+
`"when": {"type": "userPromptSubmit"}`. `userPromptSubmit` is NOT a valid
|
|
5
|
+
Kiro IDE hook event -- it is the Kiro *CLI* `hook_event_name` and was copied
|
|
6
|
+
across by mistake. Kiro's IDE hook validator rejects the entire hook file with
|
|
7
|
+
"Hook file has invalid data structure: Hook when.type must be one of: ...",
|
|
8
|
+
so the prompt-submit hook (the only active data-carrying IDE hook while
|
|
9
|
+
preToolUse ships disabled) never loaded and the IDE integration was broken.
|
|
10
|
+
|
|
11
|
+
Schema research (kiro.dev first-party + kirodotdev/Kiro issues #6188/#7375/
|
|
12
|
+
#7408/#7500) surfaced THREE more latent IDE-schema defects beyond the
|
|
13
|
+
prompt-submit event name, all fixed in 1.8.2 and pinned here:
|
|
14
|
+
|
|
15
|
+
A. when.type must be one of the 11 valid Kiro IDE events (the customer bug).
|
|
16
|
+
B. tool events (preToolUse/postToolUse) filter on `toolTypes` (array), NOT
|
|
17
|
+
`patterns`. We had `patterns: ["*"]` on the pre-tool-use hook.
|
|
18
|
+
C. file events (fileEdited/fileCreated/fileDeleted) filter on `patterns`
|
|
19
|
+
(array of globs).
|
|
20
|
+
D. `then.timeout` is SECONDS (default 60, 0 = disabled), so the `5000` we
|
|
21
|
+
shipped meant ~83 minutes, not 5 seconds.
|
|
22
|
+
|
|
23
|
+
Plus the `then.command` (`cz-kiro-adapter <event>`) must name an event the
|
|
24
|
+
shim recognizes.
|
|
25
|
+
|
|
26
|
+
If Kiro adds or renames IDE events/fields upstream, update the constants here
|
|
27
|
+
in lockstep -- that is intentional: this file is the single pin for the IDE
|
|
28
|
+
hook contract.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import json
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
|
|
36
|
+
import pytest
|
|
37
|
+
|
|
38
|
+
from controlzero.cli import kiro_adapter
|
|
39
|
+
|
|
40
|
+
# Kiro IDE hook `when.type` allowed set, verbatim from the IDE validator error:
|
|
41
|
+
# "Hook when.type must be one of: fileEdited, fileCreated, fileDeleted,
|
|
42
|
+
# userTriggered, promptSubmit, agentStop, preToolUse, postToolUse,
|
|
43
|
+
# preTaskExecution, postTaskExecution, sessionStart."
|
|
44
|
+
KIRO_IDE_EVENTS = frozenset(
|
|
45
|
+
{
|
|
46
|
+
"fileEdited",
|
|
47
|
+
"fileCreated",
|
|
48
|
+
"fileDeleted",
|
|
49
|
+
"userTriggered",
|
|
50
|
+
"promptSubmit",
|
|
51
|
+
"agentStop",
|
|
52
|
+
"preToolUse",
|
|
53
|
+
"postToolUse",
|
|
54
|
+
"preTaskExecution",
|
|
55
|
+
"postTaskExecution",
|
|
56
|
+
"sessionStart",
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Tool events filter on `toolTypes`; file events filter on `patterns`. (Kiro
|
|
61
|
+
# first-party docs + real committed .kiro.hook files + issues #7408/#7500.)
|
|
62
|
+
TOOL_EVENTS = frozenset({"preToolUse", "postToolUse"})
|
|
63
|
+
FILE_EVENTS = frozenset({"fileEdited", "fileCreated", "fileDeleted"})
|
|
64
|
+
|
|
65
|
+
# then.timeout is in SECONDS (default 60, 0 = disabled). A sane upper bound for
|
|
66
|
+
# a synchronous governance hook; the catastrophic value we shipped was 5000
|
|
67
|
+
# (~83 min). 120s is generous headroom and still rejects the ms-as-seconds bug.
|
|
68
|
+
MAX_TIMEOUT_SECONDS = 120
|
|
69
|
+
|
|
70
|
+
# Events the `cz-kiro-adapter` shim handles end to end. promptSubmit ->
|
|
71
|
+
# raw-prompt branch; file events -> observe-only synthetic fileSave; pre/post
|
|
72
|
+
# tool -> JSON tool-payload branch. userPromptSubmit is kept ONLY as a
|
|
73
|
+
# back-compat alias for the Kiro CLI event name and must never be an IDE
|
|
74
|
+
# when.type or an IDE command arg.
|
|
75
|
+
ADAPTER_RECOGNIZED_EVENTS = frozenset(
|
|
76
|
+
{"preToolUse", "postToolUse", "promptSubmit", "userPromptSubmit"}
|
|
77
|
+
| set(FILE_EVENTS)
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _templates_dir() -> Path:
|
|
82
|
+
return Path(kiro_adapter.__file__).resolve().parent / "templates" / "kiro"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _shipped_hook_files() -> list[Path]:
|
|
86
|
+
files = sorted(_templates_dir().glob("*.kiro.hook"))
|
|
87
|
+
assert files, "expected at least one shipped *.kiro.hook template"
|
|
88
|
+
return files
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
_HOOK_FILES = _shipped_hook_files()
|
|
92
|
+
_HOOK_IDS = [p.name for p in _HOOK_FILES]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _load(hook_path: Path) -> dict:
|
|
96
|
+
return json.loads(hook_path.read_text(encoding="utf-8"))
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _adapter_event_arg(command: str) -> str:
|
|
100
|
+
"""Extract the event argument from a `cz-kiro-adapter <event> [flags]`
|
|
101
|
+
runCommand string, mirroring kiro_adapter._event_from_argv (first
|
|
102
|
+
non-flag token after the program name)."""
|
|
103
|
+
tokens = command.split()
|
|
104
|
+
assert tokens, f"empty runCommand: {command!r}"
|
|
105
|
+
assert tokens[0] == "cz-kiro-adapter", (
|
|
106
|
+
f"IDE hook command must invoke the cz-kiro-adapter shim, got {command!r}"
|
|
107
|
+
)
|
|
108
|
+
for tok in tokens[1:]:
|
|
109
|
+
if not tok.startswith("-"):
|
|
110
|
+
return tok
|
|
111
|
+
raise AssertionError(f"no event argument in runCommand: {command!r}")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@pytest.mark.parametrize("hook_path", _HOOK_FILES, ids=_HOOK_IDS)
|
|
115
|
+
def test_hook_template_is_valid_json(hook_path: Path):
|
|
116
|
+
"""Every shipped .kiro.hook must be a parseable JSON object."""
|
|
117
|
+
try:
|
|
118
|
+
data = _load(hook_path)
|
|
119
|
+
except json.JSONDecodeError as e: # pragma: no cover - failure path
|
|
120
|
+
pytest.fail(f"{hook_path.name} is not valid JSON: {e}")
|
|
121
|
+
assert isinstance(data, dict), f"{hook_path.name} must be a JSON object"
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@pytest.mark.parametrize("hook_path", _HOOK_FILES, ids=_HOOK_IDS)
|
|
125
|
+
def test_hook_when_type_is_a_valid_kiro_ide_event(hook_path: Path):
|
|
126
|
+
"""FIX A / customer P0: `when.type` MUST be in Kiro's allowed IDE event
|
|
127
|
+
set, or the IDE rejects the whole hook file as invalid."""
|
|
128
|
+
data = _load(hook_path)
|
|
129
|
+
when = data.get("when")
|
|
130
|
+
assert isinstance(when, dict), f"{hook_path.name} missing object `when`"
|
|
131
|
+
when_type = when.get("type")
|
|
132
|
+
assert when_type in KIRO_IDE_EVENTS, (
|
|
133
|
+
f"{hook_path.name}: when.type={when_type!r} is NOT a valid Kiro IDE "
|
|
134
|
+
f"event. Kiro rejects the hook file. Allowed: {sorted(KIRO_IDE_EVENTS)}"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@pytest.mark.parametrize("hook_path", _HOOK_FILES, ids=_HOOK_IDS)
|
|
139
|
+
def test_tool_events_use_toolTypes_not_patterns(hook_path: Path):
|
|
140
|
+
"""FIX B: preToolUse/postToolUse filter on `toolTypes` (array), never
|
|
141
|
+
`patterns`. `patterns` on a tool event is a silent schema error."""
|
|
142
|
+
data = _load(hook_path)
|
|
143
|
+
when = data["when"]
|
|
144
|
+
if when.get("type") not in TOOL_EVENTS:
|
|
145
|
+
pytest.skip("not a tool event")
|
|
146
|
+
assert "patterns" not in when, (
|
|
147
|
+
f"{hook_path.name}: tool event {when['type']!r} must NOT use "
|
|
148
|
+
f"`patterns` -- the tool filter key is `toolTypes`"
|
|
149
|
+
)
|
|
150
|
+
tool_types = when.get("toolTypes")
|
|
151
|
+
assert isinstance(tool_types, list) and tool_types, (
|
|
152
|
+
f"{hook_path.name}: tool event {when['type']!r} must declare a "
|
|
153
|
+
f"non-empty `toolTypes` array, got {tool_types!r}"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@pytest.mark.parametrize("hook_path", _HOOK_FILES, ids=_HOOK_IDS)
|
|
158
|
+
def test_file_events_use_patterns_not_toolTypes(hook_path: Path):
|
|
159
|
+
"""FIX C: file events filter on `patterns` (array of globs), not
|
|
160
|
+
`toolTypes`."""
|
|
161
|
+
data = _load(hook_path)
|
|
162
|
+
when = data["when"]
|
|
163
|
+
if when.get("type") not in FILE_EVENTS:
|
|
164
|
+
pytest.skip("not a file event")
|
|
165
|
+
assert "toolTypes" not in when, (
|
|
166
|
+
f"{hook_path.name}: file event {when['type']!r} must NOT use "
|
|
167
|
+
f"`toolTypes` -- the file filter key is `patterns`"
|
|
168
|
+
)
|
|
169
|
+
patterns = when.get("patterns")
|
|
170
|
+
assert isinstance(patterns, list) and patterns, (
|
|
171
|
+
f"{hook_path.name}: file event {when['type']!r} must declare a "
|
|
172
|
+
f"non-empty `patterns` array, got {patterns!r}"
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@pytest.mark.parametrize("hook_path", _HOOK_FILES, ids=_HOOK_IDS)
|
|
177
|
+
def test_timeout_is_small_integer_seconds(hook_path: Path):
|
|
178
|
+
"""FIX D: `then.timeout` is SECONDS (default 60, 0 = disabled). The 1.8.0
|
|
179
|
+
templates shipped 5000 (~83 minutes). Pin it to a small integer."""
|
|
180
|
+
then = _load(hook_path)["then"]
|
|
181
|
+
timeout = then.get("timeout")
|
|
182
|
+
assert isinstance(timeout, int) and not isinstance(timeout, bool), (
|
|
183
|
+
f"{hook_path.name}: then.timeout must be an integer (seconds), "
|
|
184
|
+
f"got {timeout!r}"
|
|
185
|
+
)
|
|
186
|
+
assert 0 <= timeout <= MAX_TIMEOUT_SECONDS, (
|
|
187
|
+
f"{hook_path.name}: then.timeout={timeout} is out of range for a "
|
|
188
|
+
f"seconds value (0..{MAX_TIMEOUT_SECONDS}). 5000 was the ms-as-seconds "
|
|
189
|
+
f"bug (~83 min)."
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@pytest.mark.parametrize("hook_path", _HOOK_FILES, ids=_HOOK_IDS)
|
|
194
|
+
def test_hook_runcommand_event_arg_is_recognized(hook_path: Path):
|
|
195
|
+
"""The `cz-kiro-adapter <event>` argument must be an event the shim
|
|
196
|
+
recognizes AND a valid Kiro event (so the IDE actually fires it)."""
|
|
197
|
+
then = _load(hook_path)["then"]
|
|
198
|
+
assert then.get("type") == "runCommand", (
|
|
199
|
+
f"{hook_path.name}: then.type must be 'runCommand', got {then.get('type')!r}"
|
|
200
|
+
)
|
|
201
|
+
command = then.get("command")
|
|
202
|
+
assert isinstance(command, str) and command, (
|
|
203
|
+
f"{hook_path.name}: then.command must be a non-empty string"
|
|
204
|
+
)
|
|
205
|
+
event_arg = _adapter_event_arg(command)
|
|
206
|
+
assert event_arg in ADAPTER_RECOGNIZED_EVENTS, (
|
|
207
|
+
f"{hook_path.name}: cz-kiro-adapter event {event_arg!r} is not "
|
|
208
|
+
f"recognized by the shim. Recognized: {sorted(ADAPTER_RECOGNIZED_EVENTS)}"
|
|
209
|
+
)
|
|
210
|
+
# The command arg is the event the IDE fires the hook for, so it must also
|
|
211
|
+
# be a valid Kiro IDE event (rules out the CLI-only userPromptSubmit alias
|
|
212
|
+
# leaking into an IDE template's command).
|
|
213
|
+
assert event_arg in KIRO_IDE_EVENTS, (
|
|
214
|
+
f"{hook_path.name}: cz-kiro-adapter event {event_arg!r} is not a valid "
|
|
215
|
+
f"Kiro IDE event. Allowed: {sorted(KIRO_IDE_EVENTS)}"
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# --- per-hook end-to-end behavior pins -------------------------------------
|
|
220
|
+
|
|
221
|
+
def test_prompt_submit_hook_uses_promptSubmit_not_userPromptSubmit():
|
|
222
|
+
"""Pin the EXACT customer bug: the prompt-submit template must declare the
|
|
223
|
+
IDE `promptSubmit` event, never the CLI's `userPromptSubmit`, and the shim
|
|
224
|
+
must normalize it into a prompt payload (end-to-end, not just the string)."""
|
|
225
|
+
data = _load(_templates_dir() / "ide-prompt-submit.kiro.hook")
|
|
226
|
+
assert data["when"]["type"] == "promptSubmit", (
|
|
227
|
+
"ide-prompt-submit.kiro.hook when.type must be 'promptSubmit' "
|
|
228
|
+
"(was the invalid 'userPromptSubmit' in 1.8.0 -> 1.8.1)"
|
|
229
|
+
)
|
|
230
|
+
assert _adapter_event_arg(data["then"]["command"]) == "promptSubmit"
|
|
231
|
+
normalized = kiro_adapter._normalize("promptSubmit", "scan this prompt text")
|
|
232
|
+
assert normalized is not None
|
|
233
|
+
assert normalized["tool_name"] == "prompt"
|
|
234
|
+
assert normalized["tool_input"]["prompt"] == "scan this prompt text"
|
|
235
|
+
assert normalized["hook_event_name"] == "promptSubmit"
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def test_file_save_hook_observes_and_never_fails_closed():
|
|
239
|
+
"""file-save uses the valid `fileEdited` trigger + `patterns`, runs the
|
|
240
|
+
adapter as `fileEdited`, and the shim treats it as observe-only: it must
|
|
241
|
+
NEVER return None (which would fail-closed and block the save), even when
|
|
242
|
+
the IDE delivers no structured payload."""
|
|
243
|
+
data = _load(_templates_dir() / "ide-file-save.kiro.hook")
|
|
244
|
+
assert data["when"]["type"] == "fileEdited"
|
|
245
|
+
assert _adapter_event_arg(data["then"]["command"]) == "fileEdited"
|
|
246
|
+
|
|
247
|
+
# Empty payload (the realistic file-event case) -> still a usable tool call,
|
|
248
|
+
# never None -> the shim allows + audits rather than blocking the save.
|
|
249
|
+
for raw in ("", " ", "{}"):
|
|
250
|
+
normalized = kiro_adapter._normalize("fileEdited", raw)
|
|
251
|
+
assert normalized is not None, (
|
|
252
|
+
f"file event with payload {raw!r} must not fail-closed"
|
|
253
|
+
)
|
|
254
|
+
assert normalized["tool_name"] == "fileSave"
|
|
255
|
+
|
|
256
|
+
# A bare file path -> captured under file_path.
|
|
257
|
+
n_path = kiro_adapter._normalize("fileEdited", "/etc/secrets.env")
|
|
258
|
+
assert n_path["tool_input"]["file_path"] == "/etc/secrets.env"
|
|
259
|
+
|
|
260
|
+
# A JSON object payload (filePath/content if Kiro ever provides it) is kept.
|
|
261
|
+
n_json = kiro_adapter._normalize(
|
|
262
|
+
"fileEdited", json.dumps({"filePath": "a.py", "content": "x=1"})
|
|
263
|
+
)
|
|
264
|
+
assert n_json["tool_input"]["filePath"] == "a.py"
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def test_file_save_runCommand_never_blocks_the_editor(monkeypatch):
|
|
268
|
+
"""End-to-end (observe-only): a fileEdited hook must exit 0 (allow) even
|
|
269
|
+
when (a) USER_PROMPT is empty AND (b) hook-check itself returns a DENY
|
|
270
|
+
(exit 2). A file save is post-write -- blocking it is pointless and must
|
|
271
|
+
never happen. We still run hook-check (for the audit row + secret scan)
|
|
272
|
+
but never propagate its exit code on a file event."""
|
|
273
|
+
|
|
274
|
+
class _DenyProc:
|
|
275
|
+
# hook-check returns 2 (deny) -- the adapter MUST still allow the save.
|
|
276
|
+
returncode = 2
|
|
277
|
+
|
|
278
|
+
captured = {}
|
|
279
|
+
|
|
280
|
+
def _fake_run(cmd, input, text, env): # noqa: A002
|
|
281
|
+
captured["payload"] = json.loads(input)
|
|
282
|
+
return _DenyProc()
|
|
283
|
+
|
|
284
|
+
monkeypatch.setattr(kiro_adapter.subprocess, "run", _fake_run)
|
|
285
|
+
monkeypatch.delenv("CZ_KIRO_UNKNOWN_PAYLOAD", raising=False)
|
|
286
|
+
monkeypatch.setenv("USER_PROMPT", "")
|
|
287
|
+
rc = kiro_adapter.main(["cz-kiro-adapter", "fileEdited"])
|
|
288
|
+
assert rc == 0, (
|
|
289
|
+
"file save must never be blocked -- not by missing context AND not "
|
|
290
|
+
"even by a hook-check deny (post-write, observe-only)"
|
|
291
|
+
)
|
|
292
|
+
# The audit/scan call still ran with the synthesized fileSave payload.
|
|
293
|
+
assert captured["payload"]["tool_name"] == "fileSave"
|
|
294
|
+
assert captured["payload"]["hook_event_name"] == "fileEdited"
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def test_tool_event_still_propagates_block(monkeypatch):
|
|
298
|
+
"""Contrast guard: a real TOOL event (preToolUse) MUST still propagate a
|
|
299
|
+
hook-check deny (exit 2). The observe-only relaxation is file-events-only;
|
|
300
|
+
it must not have weakened tool-call gating."""
|
|
301
|
+
|
|
302
|
+
class _DenyProc:
|
|
303
|
+
returncode = 2
|
|
304
|
+
|
|
305
|
+
monkeypatch.setattr(
|
|
306
|
+
kiro_adapter.subprocess, "run", lambda *a, **k: _DenyProc()
|
|
307
|
+
)
|
|
308
|
+
monkeypatch.setenv(
|
|
309
|
+
"USER_PROMPT", json.dumps({"toolName": "execute_bash", "toolArgs": {}})
|
|
310
|
+
)
|
|
311
|
+
rc = kiro_adapter.main(["cz-kiro-adapter", "preToolUse"])
|
|
312
|
+
assert rc == 2, "tool-event deny must still block (only file events are observe-only)"
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def test_pre_tool_use_ships_disabled_with_toolTypes():
|
|
316
|
+
"""The IDE preToolUse hook ships disabled (upstream empty-payload bug) and,
|
|
317
|
+
when someone does enable it, must already be schema-correct: toolTypes not
|
|
318
|
+
patterns, timeout in seconds."""
|
|
319
|
+
data = _load(_templates_dir() / "ide-pre-tool-use.kiro.hook")
|
|
320
|
+
assert data["enabled"] is False
|
|
321
|
+
assert data["when"]["type"] == "preToolUse"
|
|
322
|
+
assert data["when"]["toolTypes"] == ["*"]
|
|
323
|
+
assert "patterns" not in data["when"]
|
|
324
|
+
assert data["then"]["timeout"] == 5
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{controlzero-1.8.1 → controlzero-1.8.2}/controlzero/_internal/credentials_data/built_in.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|