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.
Files changed (237) hide show
  1. {controlzero-1.9.4 → controlzero-1.9.5}/CHANGELOG.md +35 -0
  2. {controlzero-1.9.4 → controlzero-1.9.5}/PKG-INFO +18 -8
  3. {controlzero-1.9.4 → controlzero-1.9.5}/README.md +17 -7
  4. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/__init__.py +1 -1
  5. controlzero-1.9.5/controlzero/cli/__main__.py +13 -0
  6. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/hosts/kiro.py +7 -4
  7. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/main.py +275 -7
  8. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/claude-code.yaml +15 -0
  9. {controlzero-1.9.4 → controlzero-1.9.5}/pyproject.toml +1 -1
  10. controlzero-1.9.5/tests/test_kiro_cli_e2e.py +297 -0
  11. {controlzero-1.9.4 → controlzero-1.9.5}/.gitignore +0 -0
  12. {controlzero-1.9.4 → controlzero-1.9.5}/Dockerfile.test +0 -0
  13. {controlzero-1.9.4 → controlzero-1.9.5}/LICENSE +0 -0
  14. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/_internal/__init__.py +0 -0
  15. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/_internal/action_aliases.py +0 -0
  16. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/_internal/action_validator.py +0 -0
  17. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/_internal/bundle.py +0 -0
  18. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/_internal/credential_hook.py +0 -0
  19. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/_internal/credential_scanner.py +0 -0
  20. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/_internal/credentials_data/__init__.py +0 -0
  21. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/_internal/credentials_data/built_in.yaml +0 -0
  22. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/_internal/dlp_scanner.py +0 -0
  23. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/_internal/enforcer.py +0 -0
  24. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/_internal/hook_extractors.py +0 -0
  25. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/_internal/tool_extractors.json +0 -0
  26. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/_internal/types.py +0 -0
  27. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/audit_local.py +0 -0
  28. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/audit_remote.py +0 -0
  29. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/canonical.py +0 -0
  30. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/__init__.py +0 -0
  31. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/_secrets.py +0 -0
  32. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/console.py +0 -0
  33. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/debug_bundle.py +0 -0
  34. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/doctor.py +0 -0
  35. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/hosts/__init__.py +0 -0
  36. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/hosts/antigravity.py +0 -0
  37. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/hosts/base.py +0 -0
  38. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/hosts/claude_code.py +0 -0
  39. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/hosts/codex_cli.py +0 -0
  40. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/hosts/gemini_cli.py +0 -0
  41. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/hosts/unknown.py +0 -0
  42. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/kiro_adapter.py +0 -0
  43. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/migrate.py +0 -0
  44. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/spool_cmd.py +0 -0
  45. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/telemetry_consent.py +0 -0
  46. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/antigravity/hooks.json +0 -0
  47. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/antigravity.yaml +0 -0
  48. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/autogen.yaml +0 -0
  49. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/codex-cli.yaml +0 -0
  50. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/cost-cap.yaml +0 -0
  51. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/crewai.yaml +0 -0
  52. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/cursor.yaml +0 -0
  53. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/gemini-cli.yaml +0 -0
  54. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/generic.yaml +0 -0
  55. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/kiro/ide-file-save.kiro.hook +0 -0
  56. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/kiro/ide-pre-tool-use.kiro.hook +0 -0
  57. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/kiro/ide-prompt-submit.kiro.hook +0 -0
  58. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/kiro/kiro.yaml +0 -0
  59. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/langchain.yaml +0 -0
  60. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/mcp.yaml +0 -0
  61. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/cli/templates/rag.yaml +0 -0
  62. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/client.py +0 -0
  63. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/device.py +0 -0
  64. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/enrollment.py +0 -0
  65. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/error_codes.py +0 -0
  66. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/error_codes.yaml +0 -0
  67. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/errors.py +0 -0
  68. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/hitl/__init__.py +0 -0
  69. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/hitl/grant_protocol.py +0 -0
  70. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/hitl/mock.py +0 -0
  71. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/hitl/pending_approval.py +0 -0
  72. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/hitl/secret_leak_guard.py +0 -0
  73. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/hitl/status.py +0 -0
  74. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/hooks/__init__.py +0 -0
  75. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/hooks/tool_output_handler.py +0 -0
  76. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/hosted_policy.py +0 -0
  77. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/__init__.py +0 -0
  78. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/anthropic.py +0 -0
  79. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/autogen.py +0 -0
  80. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/braintrust.py +0 -0
  81. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/crewai/__init__.py +0 -0
  82. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/crewai/agent.py +0 -0
  83. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/crewai/crew.py +0 -0
  84. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/crewai/task.py +0 -0
  85. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/crewai/tool.py +0 -0
  86. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/google.py +0 -0
  87. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/google_adk/__init__.py +0 -0
  88. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/google_adk/agent.py +0 -0
  89. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/google_adk/tool.py +0 -0
  90. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/langchain/__init__.py +0 -0
  91. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/langchain/agent.py +0 -0
  92. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/langchain/callbacks.py +0 -0
  93. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/langchain/chain.py +0 -0
  94. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/langchain/graph.py +0 -0
  95. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/langchain/modern.py +0 -0
  96. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/langchain/tool.py +0 -0
  97. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/langfuse.py +0 -0
  98. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/litellm.py +0 -0
  99. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/openai.py +0 -0
  100. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/pydantic_ai.py +0 -0
  101. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/integrations/vercel_ai.py +0 -0
  102. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/layout_migration.py +0 -0
  103. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/policy_loader.py +0 -0
  104. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/spool/__init__.py +0 -0
  105. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/spool/_compress.py +0 -0
  106. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/spool/_constants.py +0 -0
  107. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/spool/_crc32c.py +0 -0
  108. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/spool/_crypto.py +0 -0
  109. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/spool/_frame.py +0 -0
  110. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/spool/_keyring.py +0 -0
  111. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/spool/_metrics.py +0 -0
  112. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/spool/_spool.py +0 -0
  113. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/spool/_state.py +0 -0
  114. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/spool/_uploader.py +0 -0
  115. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/spool/cz-audit-v1.dict +0 -0
  116. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/tamper.py +0 -0
  117. {controlzero-1.9.4 → controlzero-1.9.5}/controlzero/tracecontext.py +0 -0
  118. {controlzero-1.9.4 → controlzero-1.9.5}/examples/hello_world.py +0 -0
  119. {controlzero-1.9.4 → controlzero-1.9.5}/tests/_fixtures/jcs_args_hash_vectors.json +0 -0
  120. {controlzero-1.9.4 → controlzero-1.9.5}/tests/conftest.py +0 -0
  121. {controlzero-1.9.4 → controlzero-1.9.5}/tests/integrations/__init__.py +0 -0
  122. {controlzero-1.9.4 → controlzero-1.9.5}/tests/integrations/test_google.py +0 -0
  123. {controlzero-1.9.4 → controlzero-1.9.5}/tests/parity/action_aliases.json +0 -0
  124. {controlzero-1.9.4 → controlzero-1.9.5}/tests/spool/__init__.py +0 -0
  125. {controlzero-1.9.4 → controlzero-1.9.5}/tests/spool/conftest.py +0 -0
  126. {controlzero-1.9.4 → controlzero-1.9.5}/tests/spool/test_spool_cli.py +0 -0
  127. {controlzero-1.9.4 → controlzero-1.9.5}/tests/spool/test_spool_concurrency.py +0 -0
  128. {controlzero-1.9.4 → controlzero-1.9.5}/tests/spool/test_spool_conformance.py +0 -0
  129. {controlzero-1.9.4 → controlzero-1.9.5}/tests/spool/test_spool_core.py +0 -0
  130. {controlzero-1.9.4 → controlzero-1.9.5}/tests/spool/test_spool_crash.py +0 -0
  131. {controlzero-1.9.4 → controlzero-1.9.5}/tests/spool/test_spool_diskfull.py +0 -0
  132. {controlzero-1.9.4 → controlzero-1.9.5}/tests/spool/test_spool_durable_default_tamper.py +0 -0
  133. {controlzero-1.9.4 → controlzero-1.9.5}/tests/spool/test_spool_keychain_dek.py +0 -0
  134. {controlzero-1.9.4 → controlzero-1.9.5}/tests/spool/test_spool_sink_wiring.py +0 -0
  135. {controlzero-1.9.4 → controlzero-1.9.5}/tests/spool/test_spool_transcript_localack.py +0 -0
  136. {controlzero-1.9.4 → controlzero-1.9.5}/tests/spool/test_spool_uploader.py +0 -0
  137. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_action_aliases.py +0 -0
  138. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_action_canonicalization.py +0 -0
  139. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_action_validator_t86.py +0 -0
  140. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_agent_name_env.py +0 -0
  141. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_antigravity_adapter.py +0 -0
  142. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_antigravity_hook_check.py +0 -0
  143. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_antigravity_install.py +0 -0
  144. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_api_key_mask.py +0 -0
  145. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_audit_remote.py +0 -0
  146. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_audit_remote_sdk_version.py +0 -0
  147. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_audit_sink_isolation.py +0 -0
  148. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_bundle_parser.py +0 -0
  149. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_bundle_translate.py +0 -0
  150. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_canonical_phase1a.py +0 -0
  151. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_cli_carve_out.py +0 -0
  152. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_cli_debug_bundle.py +0 -0
  153. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_cli_extractor_integration.py +0 -0
  154. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_cli_hook.py +0 -0
  155. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_cli_hosted_refresh.py +0 -0
  156. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_cli_init.py +0 -0
  157. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_cli_init_templates.py +0 -0
  158. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_cli_tail.py +0 -0
  159. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_cli_test.py +0 -0
  160. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_cli_validate.py +0 -0
  161. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_coding_agent_hooks.py +0 -0
  162. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_conditions.py +0 -0
  163. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_conformance.py +0 -0
  164. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_console.py +0 -0
  165. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_credential_hook.py +0 -0
  166. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_default_action.py +0 -0
  167. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_device.py +0 -0
  168. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_dlp_scanner.py +0 -0
  169. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_doctor.py +0 -0
  170. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_engine_version_consistency.py +0 -0
  171. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_enrollment.py +0 -0
  172. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_env_dump_438.py +0 -0
  173. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_epic_1247_bryan_acceptance.py +0 -0
  174. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_error_codes.py +0 -0
  175. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_errors_e_codes.py +0 -0
  176. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_fail_closed_eval.py +0 -0
  177. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_glob_matching.py +0 -0
  178. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hitl_5d_email_install.py +0 -0
  179. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hitl_6a_cli_flag.py +0 -0
  180. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hitl_6a_exceptions.py +0 -0
  181. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hitl_6a_get_secret_hitl.py +0 -0
  182. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hitl_6a_mock_backend.py +0 -0
  183. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hitl_6a_pending_approval.py +0 -0
  184. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hitl_6a_request_approval.py +0 -0
  185. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hitl_6a_secret_leak_guard.py +0 -0
  186. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hitl_6a_wait.py +0 -0
  187. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hitl_conformance.py +0 -0
  188. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hitl_phase2b_protocol.py +0 -0
  189. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hitl_reason_codes.py +0 -0
  190. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hitl_validator_keys.py +0 -0
  191. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hook_extractors.py +0 -0
  192. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hosted_local_audit_1247.py +0 -0
  193. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hosted_policy_e2e.py +0 -0
  194. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hosts_adapter.py +0 -0
  195. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hybrid_mode_strict.py +0 -0
  196. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_hybrid_mode_warn.py +0 -0
  197. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_install_hook_command.py +0 -0
  198. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_install_hooks.py +0 -0
  199. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_kiro_adapter.py +0 -0
  200. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_kiro_hook_templates.py +0 -0
  201. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_kiro_install.py +0 -0
  202. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_layout_migration_t101.py +0 -0
  203. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_layout_parity_t102.py +0 -0
  204. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_local_mode_dict.py +0 -0
  205. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_local_mode_file_json.py +0 -0
  206. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_local_mode_file_yaml.py +0 -0
  207. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_log_fallback_stderr.py +0 -0
  208. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_log_options_ignored_hosted.py +0 -0
  209. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_log_rotation.py +0 -0
  210. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_migrate.py +0 -0
  211. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_min_sdk_version_gate.py +0 -0
  212. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_multi_client_per_project_175.py +0 -0
  213. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_no_policy_no_key.py +0 -0
  214. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_observe_mode_1247.py +0 -0
  215. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_package_rename_shim.py +0 -0
  216. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_policy_engine_version_phase1b.py +0 -0
  217. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_policy_freshness.py +0 -0
  218. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_policy_settings.py +0 -0
  219. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_policy_source_audit.py +0 -0
  220. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_quarantine.py +0 -0
  221. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_reason_code.py +0 -0
  222. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_refresh.py +0 -0
  223. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_secrets.py +0 -0
  224. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_sql_semantic_class.py +0 -0
  225. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_synthetic_policy_id_t79.py +0 -0
  226. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_t103_precedence.py +0 -0
  227. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_t104_cache_gc.py +0 -0
  228. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_t108_local_override_audit.py +0 -0
  229. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_t96_single_audit_log.py +0 -0
  230. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_t99_install_prefetch_bundle.py +0 -0
  231. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_tamper.py +0 -0
  232. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_tamper_behavior.py +0 -0
  233. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_tamper_hook.py +0 -0
  234. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_telemetry_consent.py +0 -0
  235. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_tracecontext.py +0 -0
  236. {controlzero-1.9.4 → controlzero-1.9.5}/tests/test_unsafe_int_boundary.py +0 -0
  237. {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.4
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 IDE) hook starter (Beta)
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
- When running without an API key, every decision is written to `./controlzero.log`
229
- with daily rotation and 30-day retention. Tail it:
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
- Configure rotation via the client:
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 IDE) hook starter (Beta)
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
- When running without an API key, every decision is written to `./controlzero.log`
175
- with daily rotation and 30-day retention. Tail it:
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
- Configure rotation via the client:
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
@@ -40,7 +40,7 @@ from controlzero.hitl.grant_protocol import (
40
40
  )
41
41
  from controlzero.policy_loader import load_policy
42
42
 
43
- __version__ = "1.9.4"
43
+ __version__ = "1.9.5"
44
44
 
45
45
  __all__ = [
46
46
  "Client",
@@ -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`` currently
26
- delivers ``USER_PROMPT={}`` with no tool context, so IDE argument-level
27
- pre-tool policy is bounded; prompt-submit and post-tool-use carry data. See
28
- docs/integrations/kiro-hook-payloads.md. Pin the validated Kiro version.
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: pass through
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: log to stderr and pass through. Never brick the agent.
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 break the agent)
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(" Default policy: ALLOW ALL (every tool call is permitted and logged)")
2071
- click.echo(" Add deny rules to block specific tools or patterns.")
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
- _KIRO_CLI_HOOK_COMMAND = "controlzero hook-check"
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.4"
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"}