controlzero 1.13.3__tar.gz → 1.13.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 (304) hide show
  1. {controlzero-1.13.3 → controlzero-1.13.5}/.gitignore +4 -1
  2. {controlzero-1.13.3 → controlzero-1.13.5}/CHANGELOG.md +20 -1
  3. {controlzero-1.13.3 → controlzero-1.13.5}/PKG-INFO +1 -1
  4. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/__init__.py +1 -1
  5. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/action_validator.py +1 -0
  6. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/bundle.py +128 -17
  7. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/enforcer.py +31 -0
  8. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/tool_extractors.json +4 -1
  9. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/audit_remote.py +23 -0
  10. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/agent_config.py +1 -1
  11. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/hosts/codex_cli.py +15 -0
  12. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/kiro_adapter.py +11 -3
  13. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/main.py +444 -141
  14. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/gemini-cli.yaml +2 -1
  15. controlzero-1.13.5/controlzero/cli/templates/kiro/ide-pre-tool-use.kiro.hook +15 -0
  16. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/kiro/kiro.yaml +4 -3
  17. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/client.py +17 -2
  18. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/hitl/secret_leak_guard.py +5 -1
  19. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/hook_integrity.py +1 -1
  20. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/hosted_policy.py +91 -10
  21. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/policy_loader.py +19 -0
  22. {controlzero-1.13.3 → controlzero-1.13.5}/pyproject.toml +1 -1
  23. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_cli_extractor_integration.py +43 -0
  24. controlzero-1.13.5/tests/test_cross_adapter_shell_enforcement.py +335 -0
  25. controlzero-1.13.5/tests/test_deny_wins_default_catchall_1620.py +130 -0
  26. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_dlp_mask.py +1 -1
  27. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hitl_6a_request_approval.py +1 -1
  28. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hitl_6a_secret_leak_guard.py +8 -0
  29. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hitl_phase2b_protocol.py +54 -0
  30. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hitl_reason_codes.py +6 -3
  31. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hook_integrity.py +1 -1
  32. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hosts_adapter.py +43 -0
  33. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_kiro_cli_e2e.py +12 -4
  34. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_kiro_cli_v210_hooks.py +16 -6
  35. controlzero-1.13.5/tests/test_kiro_install_standardisation.py +218 -0
  36. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_synthetic_policy_id_t79.py +8 -6
  37. controlzero-1.13.5/tests/test_version_aware_fail_closed.py +211 -0
  38. controlzero-1.13.3/controlzero/cli/templates/kiro/ide-pre-tool-use.kiro.hook +0 -15
  39. {controlzero-1.13.3 → controlzero-1.13.5}/Dockerfile.test +0 -0
  40. {controlzero-1.13.3 → controlzero-1.13.5}/LICENSE +0 -0
  41. {controlzero-1.13.3 → controlzero-1.13.5}/README.md +0 -0
  42. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/__main__.py +0 -0
  43. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/__init__.py +0 -0
  44. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/_messages.py +0 -0
  45. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/action_aliases.py +0 -0
  46. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/condition_ops.py +0 -0
  47. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/credential_hook.py +0 -0
  48. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/credential_scanner.py +0 -0
  49. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/credentials_data/__init__.py +0 -0
  50. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/credentials_data/built_in.yaml +0 -0
  51. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/dlp_scanner.py +0 -0
  52. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/hook_extractors.py +0 -0
  53. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/io_capture_producer.py +0 -0
  54. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/source_labels.py +0 -0
  55. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/_internal/types.py +0 -0
  56. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/audit_local.py +0 -0
  57. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/canonical.py +0 -0
  58. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/__init__.py +0 -0
  59. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/__main__.py +0 -0
  60. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/_secrets.py +0 -0
  61. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/console.py +0 -0
  62. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/debug_bundle.py +0 -0
  63. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/doctor.py +0 -0
  64. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/enforcement.py +0 -0
  65. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/exec_cmd.py +0 -0
  66. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/hook_entry.py +0 -0
  67. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/hosts/__init__.py +0 -0
  68. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/hosts/antigravity.py +0 -0
  69. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/hosts/base.py +0 -0
  70. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/hosts/claude_code.py +0 -0
  71. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/hosts/cursor.py +0 -0
  72. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/hosts/cursor_cli.py +0 -0
  73. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/hosts/gemini_cli.py +0 -0
  74. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/hosts/kiro.py +0 -0
  75. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/hosts/unknown.py +0 -0
  76. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/migrate.py +0 -0
  77. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/output.py +0 -0
  78. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/spool_cmd.py +0 -0
  79. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/telemetry_consent.py +0 -0
  80. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/antigravity/hooks.json +0 -0
  81. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/antigravity.yaml +0 -0
  82. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/autogen.yaml +0 -0
  83. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/claude-code.yaml +0 -0
  84. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/codex-cli.yaml +0 -0
  85. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/cost-cap.yaml +0 -0
  86. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/crewai.yaml +0 -0
  87. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/cursor.yaml +0 -0
  88. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/generic.yaml +0 -0
  89. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/kiro/ide-file-save.kiro.hook +0 -0
  90. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/kiro/ide-prompt-submit.kiro.hook +0 -0
  91. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/langchain.yaml +0 -0
  92. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/mcp.yaml +0 -0
  93. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/templates/rag.yaml +0 -0
  94. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/cli/update_cmd.py +0 -0
  95. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/device.py +0 -0
  96. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/enrollment.py +0 -0
  97. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/error_codes.py +0 -0
  98. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/error_codes.yaml +0 -0
  99. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/errors.py +0 -0
  100. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/hitl/__init__.py +0 -0
  101. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/hitl/grant_protocol.py +0 -0
  102. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/hitl/mock.py +0 -0
  103. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/hitl/pending_approval.py +0 -0
  104. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/hitl/status.py +0 -0
  105. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/hooks/__init__.py +0 -0
  106. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/hooks/tool_output_handler.py +0 -0
  107. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/__init__.py +0 -0
  108. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/anthropic.py +0 -0
  109. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/autogen.py +0 -0
  110. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/braintrust.py +0 -0
  111. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/crewai/__init__.py +0 -0
  112. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/crewai/agent.py +0 -0
  113. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/crewai/crew.py +0 -0
  114. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/crewai/task.py +0 -0
  115. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/crewai/tool.py +0 -0
  116. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/google.py +0 -0
  117. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/google_adk/__init__.py +0 -0
  118. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/google_adk/agent.py +0 -0
  119. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/google_adk/tool.py +0 -0
  120. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/langchain/__init__.py +0 -0
  121. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/langchain/agent.py +0 -0
  122. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/langchain/callbacks.py +0 -0
  123. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/langchain/chain.py +0 -0
  124. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/langchain/graph.py +0 -0
  125. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/langchain/modern.py +0 -0
  126. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/langchain/tool.py +0 -0
  127. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/langfuse.py +0 -0
  128. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/litellm.py +0 -0
  129. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/openai.py +0 -0
  130. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/pydantic_ai.py +0 -0
  131. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/integrations/vercel_ai.py +0 -0
  132. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/layout_migration.py +0 -0
  133. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/secrets/__init__.py +0 -0
  134. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/secrets/reference.py +0 -0
  135. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/secrets/resolver.py +0 -0
  136. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/secrets/secretstr.py +0 -0
  137. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/spool/__init__.py +0 -0
  138. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/spool/_compress.py +0 -0
  139. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/spool/_constants.py +0 -0
  140. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/spool/_crc32c.py +0 -0
  141. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/spool/_crypto.py +0 -0
  142. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/spool/_frame.py +0 -0
  143. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/spool/_keyring.py +0 -0
  144. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/spool/_metrics.py +0 -0
  145. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/spool/_spool.py +0 -0
  146. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/spool/_state.py +0 -0
  147. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/spool/_uploader.py +0 -0
  148. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/spool/cz-audit-v1.dict +0 -0
  149. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/tamper.py +0 -0
  150. {controlzero-1.13.3 → controlzero-1.13.5}/controlzero/tracecontext.py +0 -0
  151. {controlzero-1.13.3 → controlzero-1.13.5}/examples/hello_world.py +0 -0
  152. {controlzero-1.13.3 → controlzero-1.13.5}/tests/_fixtures/antigravity-envelope-expected.json +0 -0
  153. {controlzero-1.13.3 → controlzero-1.13.5}/tests/_fixtures/jcs_args_hash_vectors.json +0 -0
  154. {controlzero-1.13.3 → controlzero-1.13.5}/tests/conftest.py +0 -0
  155. {controlzero-1.13.3 → controlzero-1.13.5}/tests/fixtures/adapter-contracts/antigravity.json +0 -0
  156. {controlzero-1.13.3 → controlzero-1.13.5}/tests/fixtures/adapter-contracts/claude_code.json +0 -0
  157. {controlzero-1.13.3 → controlzero-1.13.5}/tests/fixtures/adapter-contracts/codex_cli.json +0 -0
  158. {controlzero-1.13.3 → controlzero-1.13.5}/tests/fixtures/adapter-contracts/cursor.json +0 -0
  159. {controlzero-1.13.3 → controlzero-1.13.5}/tests/fixtures/adapter-contracts/gemini_cli.json +0 -0
  160. {controlzero-1.13.3 → controlzero-1.13.5}/tests/fixtures/adapter-contracts/kiro.json +0 -0
  161. {controlzero-1.13.3 → controlzero-1.13.5}/tests/integrations/__init__.py +0 -0
  162. {controlzero-1.13.3 → controlzero-1.13.5}/tests/integrations/test_google.py +0 -0
  163. {controlzero-1.13.3 → controlzero-1.13.5}/tests/parity/action_aliases.json +0 -0
  164. {controlzero-1.13.3 → controlzero-1.13.5}/tests/spool/__init__.py +0 -0
  165. {controlzero-1.13.3 → controlzero-1.13.5}/tests/spool/conftest.py +0 -0
  166. {controlzero-1.13.3 → controlzero-1.13.5}/tests/spool/test_spool_cli.py +0 -0
  167. {controlzero-1.13.3 → controlzero-1.13.5}/tests/spool/test_spool_concurrency.py +0 -0
  168. {controlzero-1.13.3 → controlzero-1.13.5}/tests/spool/test_spool_conformance.py +0 -0
  169. {controlzero-1.13.3 → controlzero-1.13.5}/tests/spool/test_spool_core.py +0 -0
  170. {controlzero-1.13.3 → controlzero-1.13.5}/tests/spool/test_spool_crash.py +0 -0
  171. {controlzero-1.13.3 → controlzero-1.13.5}/tests/spool/test_spool_diskfull.py +0 -0
  172. {controlzero-1.13.3 → controlzero-1.13.5}/tests/spool/test_spool_durable_default_tamper.py +0 -0
  173. {controlzero-1.13.3 → controlzero-1.13.5}/tests/spool/test_spool_keychain_dek.py +0 -0
  174. {controlzero-1.13.3 → controlzero-1.13.5}/tests/spool/test_spool_sink_wiring.py +0 -0
  175. {controlzero-1.13.3 → controlzero-1.13.5}/tests/spool/test_spool_transcript_localack.py +0 -0
  176. {controlzero-1.13.3 → controlzero-1.13.5}/tests/spool/test_spool_uploader.py +0 -0
  177. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_action_aliases.py +0 -0
  178. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_action_canonicalization.py +0 -0
  179. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_action_validator_t86.py +0 -0
  180. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_adapter_contracts.py +0 -0
  181. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_adapter_interface.py +0 -0
  182. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_agent_name_env.py +0 -0
  183. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_agy_docs_no_exit2_1373.py +0 -0
  184. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_antigravity_adapter.py +0 -0
  185. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_antigravity_envelope_parity.py +0 -0
  186. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_antigravity_ga_blockers_1248.py +0 -0
  187. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_antigravity_hook_check.py +0 -0
  188. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_antigravity_install.py +0 -0
  189. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_antigravity_posttooluse_observe_58.py +0 -0
  190. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_antigravity_tool_vocab_1303.py +0 -0
  191. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_api_key_mask.py +0 -0
  192. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_audit_project_id_attribution_pr2.py +0 -0
  193. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_audit_remote.py +0 -0
  194. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_audit_remote_io_capture.py +0 -0
  195. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_audit_remote_sdk_version.py +0 -0
  196. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_audit_sink_isolation.py +0 -0
  197. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_bundle_cache_freshness_1303.py +0 -0
  198. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_bundle_parser.py +0 -0
  199. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_bundle_translate.py +0 -0
  200. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_canonical_phase1a.py +0 -0
  201. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_capability_matrix_doc.py +0 -0
  202. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_cli_carve_out.py +0 -0
  203. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_cli_debug_bundle.py +0 -0
  204. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_cli_hook.py +0 -0
  205. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_cli_hook_entry.py +0 -0
  206. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_cli_hosted_refresh.py +0 -0
  207. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_cli_init.py +0 -0
  208. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_cli_init_templates.py +0 -0
  209. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_cli_tail.py +0 -0
  210. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_cli_test.py +0 -0
  211. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_cli_validate.py +0 -0
  212. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_coding_agent_hooks.py +0 -0
  213. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_condition_operators_1495.py +0 -0
  214. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_conditions.py +0 -0
  215. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_config_format_parity_1303.py +0 -0
  216. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_conformance.py +0 -0
  217. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_console.py +0 -0
  218. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_credential_hook.py +0 -0
  219. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_cursor_adapter.py +0 -0
  220. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_default_action.py +0 -0
  221. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_deny_rule_reconciliation_1011.py +0 -0
  222. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_device.py +0 -0
  223. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_dlp_scanner.py +0 -0
  224. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_doctor.py +0 -0
  225. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_engine_version_consistency.py +0 -0
  226. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_enrollment.py +0 -0
  227. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_env_dump_438.py +0 -0
  228. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_epic_1247_bryan_acceptance.py +0 -0
  229. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_error_codes.py +0 -0
  230. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_errors_e_codes.py +0 -0
  231. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_fail_closed_eval.py +0 -0
  232. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_failopen_1303.py +0 -0
  233. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_glob_matching.py +0 -0
  234. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hitl_5d_email_install.py +0 -0
  235. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hitl_6a_cli_flag.py +0 -0
  236. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hitl_6a_exceptions.py +0 -0
  237. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hitl_6a_get_secret_hitl.py +0 -0
  238. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hitl_6a_mock_backend.py +0 -0
  239. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hitl_6a_pending_approval.py +0 -0
  240. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hitl_6a_wait.py +0 -0
  241. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hitl_conformance.py +0 -0
  242. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hitl_validator_keys.py +0 -0
  243. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hook_extractors.py +0 -0
  244. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hook_mask_failclosed.py +0 -0
  245. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hosted_local_audit_1247.py +0 -0
  246. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hosted_local_shadow_warn_1265.py +0 -0
  247. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hosted_policy_e2e.py +0 -0
  248. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hybrid_mode_strict.py +0 -0
  249. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_hybrid_mode_warn.py +0 -0
  250. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_install_hook_command.py +0 -0
  251. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_install_hooks.py +0 -0
  252. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_io_capture_producer.py +0 -0
  253. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_kiro_adapter.py +0 -0
  254. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_kiro_cli_hook_pin_1265.py +0 -0
  255. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_kiro_hook_templates.py +0 -0
  256. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_kiro_install.py +0 -0
  257. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_layout_migration_t101.py +0 -0
  258. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_layout_parity_t102.py +0 -0
  259. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_local_mode_dict.py +0 -0
  260. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_local_mode_file_json.py +0 -0
  261. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_local_mode_file_yaml.py +0 -0
  262. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_localized_reason.py +0 -0
  263. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_log_fallback_stderr.py +0 -0
  264. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_log_options_ignored_hosted.py +0 -0
  265. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_log_rotation.py +0 -0
  266. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_migrate.py +0 -0
  267. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_min_sdk_version_gate.py +0 -0
  268. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_multi_client_per_project_175.py +0 -0
  269. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_no_policy_no_key.py +0 -0
  270. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_observe_mode_1247.py +0 -0
  271. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_package_rename_shim.py +0 -0
  272. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_part3_active_policy_count_1303.py +0 -0
  273. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_per_client_isolation_1011.py +0 -0
  274. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_policy_engine_version_phase1b.py +0 -0
  275. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_policy_freshness.py +0 -0
  276. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_policy_load_failclosed_f3.py +0 -0
  277. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_policy_settings.py +0 -0
  278. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_policy_source_audit.py +0 -0
  279. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_pr4_install_messaging.py +0 -0
  280. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_pr4_output.py +0 -0
  281. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_pr4_status.py +0 -0
  282. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_probe_audit_isolation.py +0 -0
  283. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_quarantine.py +0 -0
  284. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_reason_code.py +0 -0
  285. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_refresh.py +0 -0
  286. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_secrets.py +0 -0
  287. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_secrets_reference_resolver.py +0 -0
  288. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_shell_semantic_class.py +0 -0
  289. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_source_labels.py +0 -0
  290. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_sql_semantic_class.py +0 -0
  291. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_t103_precedence.py +0 -0
  292. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_t104_cache_gc.py +0 -0
  293. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_t108_local_override_audit.py +0 -0
  294. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_t96_single_audit_log.py +0 -0
  295. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_t99_install_prefetch_bundle.py +0 -0
  296. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_tamper.py +0 -0
  297. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_tamper_behavior.py +0 -0
  298. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_tamper_hook.py +0 -0
  299. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_telemetry_consent.py +0 -0
  300. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_tracecontext.py +0 -0
  301. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_unsafe_int_boundary.py +0 -0
  302. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_update_cmd.py +0 -0
  303. {controlzero-1.13.3 → controlzero-1.13.5}/tests/test_upgrade_nudge.py +0 -0
  304. {controlzero-1.13.3 → controlzero-1.13.5}/tools/cz-kiro-adapter +0 -0
@@ -70,7 +70,6 @@ firebase-service-account*.json
70
70
  firebase-adminsdk-*.json
71
71
  serviceAccountKey.json
72
72
  cz-service-account.json
73
- firebase-credentials.json/
74
73
  google-services.json
75
74
  GoogleService-Info.plist
76
75
  !*.pub
@@ -261,3 +260,7 @@ doc_local/
261
260
 
262
261
  # Antigravity (agy) CLI local workspace
263
262
  .antigravitycli/
263
+
264
+ # HITL canary local config (never commit secrets)
265
+ hitl-canary.env
266
+ *.canary.env
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.13.4 (2026-07-05): deny-wins fail-open fix (P0)
4
+
5
+ ### Fixed
6
+
7
+ - **Explicit deny now wins -- a default allow-* no longer shadows an attached
8
+ deny (#1623/#1620).** The bundle translator sorted policies by `priority`
9
+ ASCENDING, so a priority-0 catch-all `allow-*` was evaluated before a
10
+ higher-priority attached `deny`, and a denied action (e.g. `rm -rf`) fell
11
+ OPEN. The translator now sorts DESCENDING (higher priority first), matching
12
+ the server-side policy engine, so an explicit deny is honored. This is the
13
+ hard gate for the fail-open reported on 1.13.3 and below.
14
+
15
+ ### Notes
16
+
17
+ - The backend now stamps `min_sdk_version = 1.13.4` on any bundle containing a
18
+ deny rule, so an SDK below 1.13.4 fails CLOSED (`BundleRequiresNewerSDKError`,
19
+ E1712) rather than silently under-enforcing. Upgrade to 1.13.4 to keep deny
20
+ rules enforced.
21
+
3
22
  ## 1.13.3 (2026-06-29): `verify --prove` enforcement-robustness layer
4
23
 
5
24
  ### Added
@@ -181,7 +200,7 @@
181
200
  server-side before persist (#1433), and captured payloads are sealed under a
182
201
  per-record DEK.
183
202
 
184
- - **DLP `mask` action enforced in the SDK (#1278 kakaopay checklist #6).**
203
+ - **DLP `mask` action enforced in the SDK (#1278 acme checklist #6).**
185
204
  The `mask` action now redacts matched sensitive spans in the tool arguments
186
205
  (modify-and-proceed) instead of only being recorded as a finding. When a DLP
187
206
  rule matches with `action="mask"` on an `allow` decision, the enforcer
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: controlzero
3
- Version: 1.13.3
3
+ Version: 1.13.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
@@ -41,7 +41,7 @@ from controlzero.hitl.grant_protocol import (
41
41
  from controlzero.policy_loader import load_policy
42
42
  from controlzero.secrets import SecretRef, SecretStr, secret_ref
43
43
 
44
- __version__ = "1.13.3"
44
+ __version__ = "1.13.5"
45
45
 
46
46
  __all__ = [
47
47
  "Client",
@@ -39,6 +39,7 @@ _CANONICAL_TOOLS: set[str] = {
39
39
  # Bash aliases
40
40
  "bash", "shell", "ShellTool", "run_shell_command",
41
41
  "PowerShell", "powershell", "Shell",
42
+ "execute_bash", "local_shell", "run_terminal_cmd",
42
43
  # http aliases
43
44
  "fetch", "web_fetch", "WebFetch", "HTTPRequest", "request",
44
45
  # web_search aliases
@@ -430,37 +430,138 @@ def _parse_version_tuple(v: str) -> tuple:
430
430
  return tuple(nums) + (1, "")
431
431
 
432
432
 
433
- def check_min_sdk_version(payload: dict, sdk_version: str) -> None:
434
- """Raise :class:`BundleRequiresNewerSDKError` if the bundle
435
- declares a higher floor than ``sdk_version``.
436
-
437
- Bundles without ``metadata.min_sdk_version`` are accepted
438
- unconditionally (back-compat: every bundle minted before #602
439
- omits the field).
440
-
441
- No-op fast path covers 99% of bundles in the field, so the
442
- cost is one ``.get("metadata", {}).get("min_sdk_version")``
443
- indirection per load.
433
+ def sdk_below_floor(payload: dict, sdk_version: str) -> Optional[tuple[str, str]]:
434
+ """Return ``(required, actual)`` when ``sdk_version`` is BELOW the bundle's
435
+ ``metadata.min_sdk_version`` floor, else ``None``.
436
+
437
+ The floor is stamped by the backend as the MAX over every min-SDK floor the
438
+ bundle's rules require -- the #602 selector floor AND the capability floors
439
+ (any deny rule -> 1.13.4, any tagged {op,value} condition -> 1.13.0). An SDK
440
+ below the floor SILENTLY MIS-ENFORCES those rules (the confirmed #1011 rm-rf
441
+ fail-open on a stale 1.12.0), so the caller must fail loud/closed rather than
442
+ under-enforce.
443
+
444
+ Bundles without ``metadata.min_sdk_version`` return ``None`` (back-compat:
445
+ every bundle minted before #602 omits the field). No-op fast path covers
446
+ 99% of bundles in the field.
444
447
  """
445
448
  metadata = payload.get("metadata")
446
449
  if not isinstance(metadata, dict):
447
- return
450
+ return None
448
451
  required = metadata.get("min_sdk_version")
449
452
  if not isinstance(required, str) or not required:
453
+ return None
454
+ if _parse_version_tuple(sdk_version) >= _parse_version_tuple(required):
455
+ return None
456
+ return required, sdk_version
457
+
458
+
459
+ def check_min_sdk_version(payload: dict, sdk_version: str) -> None:
460
+ """Raise :class:`BundleRequiresNewerSDKError` if the bundle declares a
461
+ higher floor than ``sdk_version``.
462
+
463
+ This is the low-level HARD-refusal predicate, kept for direct callers and
464
+ back-compat. The hosted load path does NOT call this directly -- it uses
465
+ :func:`sdk_below_floor` + posture (see hosted_policy) so an allow-by-default
466
+ bundle degrades-with-a-loud-warning instead of hard-refusing, while a
467
+ deny-by-default bundle fails closed.
468
+ """
469
+ below = sdk_below_floor(payload, sdk_version)
470
+ if below is None:
450
471
  return
472
+ required, actual = below
451
473
  # Lazy import: errors -> _internal cycle is avoided because
452
474
  # this is invoked at runtime, after both modules finished loading.
453
475
  from controlzero.errors import BundleRequiresNewerSDKError
454
476
 
455
- if _parse_version_tuple(sdk_version) >= _parse_version_tuple(required):
456
- return
457
477
  raise BundleRequiresNewerSDKError(
458
478
  required=required,
459
- actual=sdk_version,
479
+ actual=actual,
460
480
  upgrade_command="pip install -U controlzero",
461
481
  )
462
482
 
463
483
 
484
+ # Canonical upgrade command surfaced in every below-floor message. Per-language
485
+ # (mirrors BundleRequiresNewerSDKError.upgrade_command); the reason_code and
486
+ # enforcement_status are the byte-identical cross-SDK fields.
487
+ SDK_UPGRADE_COMMAND = "pip install -U controlzero"
488
+
489
+
490
+ def below_floor_deny_reason(required: str, actual: str, upgrade_command: str) -> str:
491
+ """User-facing reason on a fail-closed deny caused by an under-floor SDK."""
492
+ return (
493
+ f"[Control Zero] Your SDK {actual} is below the minimum {required} this "
494
+ f"policy requires. Enforcement cannot be guaranteed -- blocking as a "
495
+ f"precaution (deny-by-default posture). Upgrade to restore enforcement: "
496
+ f"{upgrade_command}"
497
+ )
498
+
499
+
500
+ def below_floor_degraded_warning(required: str, actual: str, upgrade_command: str) -> str:
501
+ """Loud stderr banner when an under-floor SDK ALLOWS under an allow/observe
502
+ posture -- enforcement is degraded, never silent."""
503
+ return (
504
+ f"[Control Zero] ENFORCEMENT DEGRADED: your SDK {actual} is below the "
505
+ f"minimum {required} this policy requires (allow-by-default posture) -- "
506
+ f"this policy's deny rules may NOT be applied. Upgrade to restore "
507
+ f"enforcement: {upgrade_command}"
508
+ )
509
+
510
+
511
+ def make_below_floor_policy(
512
+ required: str,
513
+ actual: str,
514
+ upgrade_command: str = SDK_UPGRADE_COMMAND,
515
+ ) -> dict:
516
+ """Build the synthetic deny-all local-policy used when a deny-by-default
517
+ bundle is loaded by an SDK BELOW its min_sdk_version floor.
518
+
519
+ Same shape as :func:`translate_to_local_policy` so downstream
520
+ :func:`controlzero.policy_loader.load_policy` accepts it unchanged. Every
521
+ tool call is denied with reason_code ``SDK_BELOW_FLOOR`` and
522
+ ``settings.enforcement_status = "fail_closed"`` -- the drift is loud on
523
+ every audit row, never a silent under-enforce.
524
+ """
525
+ return {
526
+ "version": "1",
527
+ "rules": [
528
+ {
529
+ "effect": "deny",
530
+ "action": "*",
531
+ "id": "synthetic:SDK_BELOW_FLOOR",
532
+ "reason": below_floor_deny_reason(required, actual, upgrade_command),
533
+ "reason_code": "SDK_BELOW_FLOOR",
534
+ }
535
+ ],
536
+ "settings": {
537
+ "default_action": "deny",
538
+ "default_on_missing": "deny",
539
+ "default_on_tamper": "warn",
540
+ "enforcement_status": "fail_closed",
541
+ },
542
+ }
543
+
544
+
545
+ def bundle_is_deny_posture(payload: dict) -> bool:
546
+ """Whether the bundle's resolved default posture is deny-by-default.
547
+
548
+ Used ONLY to decide the below-floor behaviour: a deny-by-default bundle
549
+ fails CLOSED (deny-all) when the SDK is under the floor; an allow/observe
550
+ bundle degrades-with-a-loud-warning (the #1011 case: allow-by-default + stale
551
+ SDK must scream, not silently allow). Missing / unknown default_action
552
+ coerces to the canonical ``deny`` -- the safe direction.
553
+ """
554
+ from controlzero._internal.enforcer import (
555
+ DEFAULT_BUNDLE_ACTION,
556
+ VALID_DEFAULT_ACTIONS,
557
+ )
558
+
559
+ da = payload.get("default_action")
560
+ if da not in VALID_DEFAULT_ACTIONS:
561
+ da = DEFAULT_BUNDLE_ACTION
562
+ return da == "deny"
563
+
564
+
464
565
  # --- Schema translation ----------------------------------------------------
465
566
 
466
567
 
@@ -521,8 +622,14 @@ def translate_to_local_policy(
521
622
  either ``{"deny": "<tool>"}``, ``{"allow": "<tool>"}`` or
522
623
  ``{"effect": "<deny|allow|warn|audit>", "action": "<tool>"}``.
523
624
 
524
- Policies are sorted by ``priority`` ascending so SDKs in every
525
- language produce identical decisions from identical input.
625
+ Policies are sorted by ``priority`` DESCENDING (higher number = higher
626
+ precedence = evaluated first; first-match-wins), so SDKs in every language
627
+ -- and the policy engine, which already sorts descending -- produce
628
+ identical decisions from identical input (#1620 cross-engine parity). The
629
+ auto-attached onboarding "Default policy" catch-all is pinned to the lowest
630
+ priority (``math.MinInt32``) by the backend, so it sorts LAST and never
631
+ shadows a user-attached deny. (Before #1620 this sort was ASCENDING, which
632
+ put the priority-0 default catch-all FIRST -- the core-promise fail-open.)
526
633
 
527
634
  Schema 1.1 (#228 Phase 2) adds three top-level enforcement-default
528
635
  knobs -- ``default_action``, ``default_on_missing``, and
@@ -580,9 +687,13 @@ def translate_to_local_policy(
580
687
  # latter must fail CLOSED, never observe-allow.
581
688
  raw_policies = payload.get("policies")
582
689
  policies = raw_policies or []
690
+ # DESCENDING (#1620): higher priority evaluated first, matching the policy
691
+ # engine. `sorted` is stable, so equal-priority policies keep the backend's
692
+ # emit order (which already places the catch-all last within its tier).
583
693
  policies = sorted(
584
694
  [p for p in policies if isinstance(p, dict)],
585
695
  key=lambda p: int(p.get("priority", 100)),
696
+ reverse=True,
586
697
  )
587
698
 
588
699
  flat: list[dict] = []
@@ -107,6 +107,14 @@ REASON_CODE_HITL_IDENTITY_NOT_IN_ORG = "HITL_IDENTITY_NOT_IN_ORG"
107
107
  REASON_CODE_HITL_IDENTITY_REQUIRED = "HITL_IDENTITY_REQUIRED"
108
108
  REASON_CODE_HITL_IDENTITY_CLAIM_REJECTED = "HITL_IDENTITY_CLAIM_REJECTED"
109
109
  REASON_CODE_HITL_ARGS_HASH_MISMATCH = "HITL_ARGS_HASH_MISMATCH"
110
+ # Version-aware fail-closed enforcement (the #1011 stale-SDK fail-open). Stamped
111
+ # when the running SDK is BELOW the bundle's metadata.min_sdk_version -- the
112
+ # floor the backend derives from the rule capabilities the bundle uses (deny
113
+ # rules / condition operators). On a deny-by-default posture the SDK fails
114
+ # CLOSED with this code; on allow/observe posture it allows but flags every row
115
+ # with enforcement_status=degraded_below_floor. Either way the drift is VISIBLE,
116
+ # never a silent under-enforce.
117
+ REASON_CODE_SDK_BELOW_FLOOR = "SDK_BELOW_FLOOR"
110
118
 
111
119
  VALID_REASON_CODES = frozenset({
112
120
  REASON_CODE_RULE_MATCH,
@@ -128,6 +136,24 @@ VALID_REASON_CODES = frozenset({
128
136
  REASON_CODE_HITL_IDENTITY_REQUIRED,
129
137
  REASON_CODE_HITL_IDENTITY_CLAIM_REJECTED,
130
138
  REASON_CODE_HITL_ARGS_HASH_MISMATCH,
139
+ REASON_CODE_SDK_BELOW_FLOOR,
140
+ })
141
+
142
+ # Enforcement status (version-aware fail-closed telemetry). A per-decision
143
+ # machine-readable summary of WHETHER the SDK is actually enforcing, surfaced on
144
+ # the audit/telemetry wire so the dashboard can show, per connected machine,
145
+ # green (enforcing) vs a degraded/fail-closed banner. Absent on the wire ==
146
+ # "enforcing" (lean additive contract). Kept in lockstep across all three SDKs.
147
+ ENFORCEMENT_STATUS_ENFORCING = "enforcing"
148
+ ENFORCEMENT_STATUS_DEGRADED_BELOW_FLOOR = "degraded_below_floor"
149
+ ENFORCEMENT_STATUS_FAIL_CLOSED = "fail_closed"
150
+ ENFORCEMENT_STATUS_BUNDLE_MISSING = "bundle_missing"
151
+
152
+ VALID_ENFORCEMENT_STATUSES = frozenset({
153
+ ENFORCEMENT_STATUS_ENFORCING,
154
+ ENFORCEMENT_STATUS_DEGRADED_BELOW_FLOOR,
155
+ ENFORCEMENT_STATUS_FAIL_CLOSED,
156
+ ENFORCEMENT_STATUS_BUNDLE_MISSING,
131
157
  })
132
158
 
133
159
  # Synthetic policy_id sentinels (T79 / the deny-deny postmortem,
@@ -158,6 +184,10 @@ SYNTHETIC_ENGINE_UNAVAILABLE = "synthetic:ENGINE_UNAVAILABLE"
158
184
  # policy_id -- e.g. an old field SDK or a CLI fail-closed path. SDKs do not
159
185
  # emit it; it is listed here so downstream consumers recognize it.
160
186
  SYNTHETIC_UNATTRIBUTED_DENY = "synthetic:UNATTRIBUTED_DENY"
187
+ # Version-aware fail-closed: the deny-all synthetic policy_id stamped on every
188
+ # row when a deny-posture bundle is loaded by an SDK BELOW its min_sdk_version
189
+ # floor. Pairs with reason_code=SDK_BELOW_FLOOR.
190
+ SYNTHETIC_SDK_BELOW_FLOOR = "synthetic:SDK_BELOW_FLOOR"
161
191
 
162
192
  VALID_SYNTHETIC_POLICY_IDS = frozenset({
163
193
  SYNTHETIC_NO_RULE_MATCH,
@@ -168,6 +198,7 @@ VALID_SYNTHETIC_POLICY_IDS = frozenset({
168
198
  SYNTHETIC_QUARANTINE,
169
199
  SYNTHETIC_ENGINE_UNAVAILABLE,
170
200
  SYNTHETIC_UNATTRIBUTED_DENY,
201
+ SYNTHETIC_SDK_BELOW_FLOOR,
171
202
  })
172
203
 
173
204
  # Canonical bundle-level default values. These must stay in lockstep
@@ -20,7 +20,10 @@
20
20
  "run_command",
21
21
  "PowerShell",
22
22
  "powershell",
23
- "Shell"
23
+ "Shell",
24
+ "execute_bash",
25
+ "local_shell",
26
+ "run_terminal_cmd"
24
27
  ]
25
28
  },
26
29
  "http": {
@@ -454,6 +454,29 @@ def _build_wire_entry(
454
454
  if entry.get(key) is True:
455
455
  wire[key] = True
456
456
 
457
+ # Version-aware fail-closed telemetry (#1011). enforcement_status is the
458
+ # per-decision "is this machine actually enforcing" summary (fail_closed /
459
+ # degraded_below_floor / bundle_missing); client_version above already
460
+ # carries the running SDK version. bundle_age_s + last_sync_ts (bundle
461
+ # freshness) are forwarded when the caller supplies them. Each is emitted
462
+ # ONLY when present so the common enforcing row stays byte-identical
463
+ # (absent enforcement_status == "enforcing"); older backends ignore the
464
+ # additive keys. This is the exact wire shape the machines/agents dashboard
465
+ # consumes to show per-machine SDK version + bundle age + enforcement state.
466
+ _enf = entry.get("enforcement_status")
467
+ if _enf:
468
+ wire["enforcement_status"] = str(_enf)
469
+ for _fresh_key in ("bundle_age_s", "last_sync_ts"):
470
+ _fv = entry.get(_fresh_key)
471
+ if _fv is None:
472
+ continue
473
+ try:
474
+ _fn = float(_fv)
475
+ except (TypeError, ValueError, OverflowError):
476
+ continue
477
+ if math.isfinite(_fn) and _fn >= 0:
478
+ wire[_fresh_key] = int(_fn)
479
+
457
480
  # Final batch-poison backstop (#439 codex P1-b): every value that goes on
458
481
  # the wire -- including the raw top-level forwarded fields above (the
459
482
  # credential_* envelope copies entry[key] verbatim) and any future field
@@ -180,7 +180,7 @@ def per_agent_api_key(
180
180
  ONLY the per-agent ``integrations/<canonical_source>/config.yaml`` key, or
181
181
  None. hook-check uses it to make a surface's installed key AUTHORITATIVE
182
182
  over a stale ambient ``CONTROLZERO_API_KEY`` (#1011 follow-up): a surface
183
- wired by ``install <agent> --api-key`` / ``kiro init --api-key`` must
183
+ wired by ``install <agent> --api-key`` (e.g. ``install kiro --api-key``) must
184
184
  attribute its audit to the project it was installed for, never to whatever
185
185
  key happens to be exported in the shell. Never raises.
186
186
  """
@@ -35,6 +35,15 @@ from controlzero.cli.hosts.base import CoverageDecl, CZDecision, HostAdapter
35
35
  # fail-open backstop).
36
36
  _CODEX_CLI_CLIENTS = ("codex-cli", "codex_cli", "codex")
37
37
 
38
+ # Kiro CLI v3 is built on the Codex codebase and exports CODEX_HOME/CODEX_CLI.
39
+ # Its hooks send camelCase hook_event_name values (preToolUse, postToolUse,
40
+ # userPromptSubmit) -- byte-disjoint from Codex's PascalCase (PreToolUse).
41
+ # When we see a camelCase event, defer to KiroCLIAdapter rather than claiming
42
+ # the call based on CODEX_HOME/CODEX_CLI env hints alone (Bug #1655).
43
+ _KIRO_CLI_CAMEL_EVENTS = frozenset(
44
+ {"preToolUse", "postToolUse", "userPromptSubmit", "agentSpawn", "stop"}
45
+ )
46
+
38
47
 
39
48
  class CodexCLIAdapter(HostAdapter):
40
49
  name = "codex_cli"
@@ -66,6 +75,12 @@ class CodexCLIAdapter(HostAdapter):
66
75
  # pinned host's adapter (not this one) claims the call.
67
76
  if self._pinned_elsewhere(env, _CODEX_CLI_CLIENTS):
68
77
  return False
78
+ # Kiro CLI exports CODEX_HOME/CODEX_CLI (built on Codex codebase) but
79
+ # sends camelCase hook_event_name values (preToolUse vs Codex's
80
+ # PascalCase PreToolUse). Defer to KiroCLIAdapter when we see a
81
+ # camelCase Kiro event so audit logs show "kiro_cli" not "codex_cli".
82
+ if isinstance(payload, dict) and payload.get("hook_event_name") in _KIRO_CLI_CAMEL_EVENTS:
83
+ return False
69
84
  for hint in self._ENV_HINTS:
70
85
  if env.get(hint):
71
86
  return True
@@ -13,7 +13,7 @@ IDE surface, NOT the Kiro CLI's ``userPromptSubmit``), e.g.::
13
13
 
14
14
  cz-kiro-adapter preToolUse
15
15
 
16
- This module is the packaged entry point: ``controlzero kiro init`` wires the
16
+ This module is the packaged entry point: ``controlzero install kiro`` wires the
17
17
  IDE hooks to the ``cz-kiro-adapter`` console script declared in pyproject, so
18
18
  it resolves on PATH after ``pip install controlzero`` with no separate copy
19
19
  step. The repository ``tools/cz-kiro-adapter`` script delegates here so dev
@@ -134,9 +134,17 @@ def main(argv: list[str] | None = None) -> int:
134
134
  file=sys.stderr,
135
135
  )
136
136
  return 0
137
+ # Upstream Kiro IDE bug #7375/#7408/#7500: preToolUse hooks receive
138
+ # USER_PROMPT={} with no tool context. The 'Control Zero - Pre Tool Use'
139
+ # hook in Kiro IDE settings ships DISABLED to prevent this. If you are
140
+ # seeing this message, you have enabled that hook. Disable it in Kiro
141
+ # IDE Settings > Hooks to restore normal tool execution.
137
142
  print(
138
- "controlzero(kiro): empty/unparseable IDE payload; blocking "
139
- "(fail-closed; set CZ_KIRO_UNKNOWN_PAYLOAD=open to override)",
143
+ f"controlzero(kiro): {event} hook received empty payload "
144
+ "(upstream Kiro IDE bug #7375/#7408/#7500 -- preToolUse delivers "
145
+ "USER_PROMPT={}). Failing closed. FIX: disable the "
146
+ "'Control Zero - Pre Tool Use' hook in Kiro IDE Settings > Hooks. "
147
+ "Set CZ_KIRO_UNKNOWN_PAYLOAD=open in the hook env to allow instead.",
140
148
  file=sys.stderr,
141
149
  )
142
150
  return 2