controlzero 1.13.7__tar.gz → 1.13.9__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 (328) hide show
  1. {controlzero-1.13.7 → controlzero-1.13.9}/CHANGELOG.md +396 -0
  2. {controlzero-1.13.7 → controlzero-1.13.9}/PKG-INFO +1 -1
  3. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/__init__.py +1 -1
  4. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/_internal/_messages.py +45 -0
  5. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/_internal/action_aliases.py +1 -1
  6. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/_internal/action_validator.py +23 -2
  7. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/_internal/bundle.py +12 -4
  8. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/_internal/credential_hook.py +0 -1
  9. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/_internal/credential_scanner.py +0 -1
  10. controlzero-1.13.9/controlzero/_internal/dlp_remediation.py +245 -0
  11. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/_internal/dlp_scanner.py +0 -1
  12. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/_internal/enforcer.py +580 -22
  13. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/_internal/hook_extractors.py +187 -5
  14. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/_internal/io_capture_producer.py +4 -3
  15. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/_internal/types.py +1 -0
  16. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/audit_remote.py +2 -1
  17. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/canonical.py +2 -1
  18. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/_secrets.py +6 -5
  19. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/console.py +0 -1
  20. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/doctor.py +25 -16
  21. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/enforcement.py +24 -1
  22. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/hosts/__init__.py +0 -1
  23. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/hosts/antigravity.py +16 -1
  24. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/hosts/base.py +82 -1
  25. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/hosts/claude_code.py +88 -1
  26. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/hosts/codex_cli.py +51 -1
  27. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/hosts/cursor.py +49 -1
  28. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/hosts/cursor_cli.py +18 -1
  29. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/hosts/gemini_cli.py +36 -1
  30. controlzero-1.13.9/controlzero/cli/hosts/kiro.py +364 -0
  31. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/hosts/unknown.py +4 -1
  32. controlzero-1.13.9/controlzero/cli/kiro_adapter.py +926 -0
  33. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/main.py +572 -49
  34. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/migrate.py +5 -6
  35. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/spool_cmd.py +2 -1
  36. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/telemetry_consent.py +4 -5
  37. controlzero-1.13.9/controlzero/cli/templates/kiro/ide-pre-tool-use.kiro.hook +15 -0
  38. controlzero-1.13.9/controlzero/cli/templates/kiro/ide-prompt-submit.kiro.hook +14 -0
  39. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/templates/kiro/kiro.yaml +9 -6
  40. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/client.py +111 -22
  41. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/enrollment.py +3 -2
  42. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/error_codes.py +5 -5
  43. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/error_codes.yaml +9 -6
  44. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/errors.py +3 -2
  45. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/hitl/grant_protocol.py +2 -1
  46. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/hitl/mock.py +7 -7
  47. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/hitl/pending_approval.py +30 -3
  48. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/hook_integrity.py +2 -1
  49. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/integrations/anthropic.py +5 -1
  50. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/integrations/autogen.py +2 -1
  51. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/integrations/crewai/agent.py +8 -6
  52. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/integrations/crewai/crew.py +18 -14
  53. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/integrations/crewai/task.py +11 -4
  54. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/integrations/crewai/tool.py +6 -4
  55. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/integrations/google.py +11 -3
  56. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/integrations/google_adk/agent.py +9 -7
  57. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/integrations/google_adk/tool.py +8 -5
  58. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/integrations/langchain/agent.py +14 -14
  59. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/integrations/langchain/callbacks.py +27 -23
  60. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/integrations/langchain/chain.py +10 -10
  61. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/integrations/langchain/graph.py +25 -25
  62. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/integrations/langchain/modern.py +2 -1
  63. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/integrations/langchain/tool.py +10 -7
  64. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/integrations/litellm.py +7 -2
  65. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/integrations/openai.py +6 -2
  66. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/integrations/pydantic_ai.py +6 -2
  67. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/integrations/vercel_ai.py +6 -2
  68. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/policy_loader.py +7 -2
  69. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/secrets/reference.py +2 -2
  70. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/secrets/resolver.py +9 -8
  71. {controlzero-1.13.7 → controlzero-1.13.9}/pyproject.toml +1 -1
  72. controlzero-1.13.9/ruff.toml +40 -0
  73. {controlzero-1.13.7 → controlzero-1.13.9}/tests/fixtures/adapter-contracts/kiro.json +4 -2
  74. {controlzero-1.13.7 → controlzero-1.13.9}/tests/spool/test_spool_conformance.py +1 -1
  75. {controlzero-1.13.7 → controlzero-1.13.9}/tests/spool/test_spool_crash.py +1 -1
  76. {controlzero-1.13.7 → controlzero-1.13.9}/tests/spool/test_spool_durable_default_tamper.py +1 -2
  77. {controlzero-1.13.7 → controlzero-1.13.9}/tests/spool/test_spool_uploader.py +1 -1
  78. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_action_validator_t86.py +17 -0
  79. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_adapter_contracts.py +0 -1
  80. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_adapter_interface.py +16 -4
  81. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_antigravity_adapter.py +14 -2
  82. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_antigravity_envelope_parity.py +13 -1
  83. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_antigravity_ga_blockers_1248.py +16 -5
  84. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_antigravity_tool_vocab_1303.py +0 -1
  85. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_audit_project_id_attribution_pr2.py +0 -1
  86. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_audit_remote.py +0 -1
  87. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_audit_remote_io_capture.py +0 -1
  88. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_audit_remote_sdk_version.py +1 -2
  89. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_bundle_cache_freshness_1303.py +1 -2
  90. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_bundle_parser.py +0 -1
  91. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_bundle_translate.py +0 -2
  92. controlzero-1.13.9/tests/test_cli_coverage.py +62 -0
  93. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_cli_debug_bundle.py +0 -1
  94. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_cli_extractor_integration.py +0 -1
  95. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_cli_hook.py +5 -0
  96. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_cli_init_templates.py +1 -1
  97. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_condition_operators_1495.py +0 -1
  98. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_conditions.py +0 -1
  99. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_credential_hook.py +0 -1
  100. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_cross_adapter_shell_enforcement.py +0 -1
  101. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_cursor_adapter.py +14 -3
  102. controlzero-1.13.9/tests/test_dashboard_shell_template_1904.py +746 -0
  103. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_default_action.py +0 -1
  104. controlzero-1.13.9/tests/test_dlp_agent_remediation_1938.py +366 -0
  105. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_dlp_mask.py +0 -1
  106. controlzero-1.13.9/tests/test_dlp_mask_hosts_1938.py +164 -0
  107. controlzero-1.13.9/tests/test_dlp_origin_contract.py +620 -0
  108. controlzero-1.13.9/tests/test_dlp_scan_coverage_1927_1931.py +634 -0
  109. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_dlp_scanner.py +7 -7
  110. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_enrollment.py +10 -8
  111. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_epic_1247_bryan_acceptance.py +6 -4
  112. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_errors_e_codes.py +0 -1
  113. controlzero-1.13.9/tests/test_git_target_1734.py +242 -0
  114. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_hitl_5d_email_install.py +2 -2
  115. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_hitl_6a_cli_flag.py +0 -1
  116. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_hitl_6a_get_secret_hitl.py +26 -26
  117. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_hitl_6a_mock_backend.py +0 -1
  118. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_hitl_6a_request_approval.py +0 -1
  119. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_hitl_6a_secret_leak_guard.py +0 -1
  120. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_hitl_6a_wait.py +16 -7
  121. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_hitl_conformance.py +10 -11
  122. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_hitl_phase2b_protocol.py +29 -8
  123. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_hitl_reason_codes.py +5 -3
  124. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_hook_command_pinning_1705.py +0 -1
  125. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_hook_extractors.py +0 -1
  126. controlzero-1.13.9/tests/test_hook_warn_surfaces_reason_1903.py +479 -0
  127. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_hosted_local_audit_1247.py +2 -0
  128. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_hosted_policy_e2e.py +4 -3
  129. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_hosts_adapter.py +29 -18
  130. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_install_hook_command.py +0 -1
  131. controlzero-1.13.9/tests/test_kiro_adapter.py +1422 -0
  132. controlzero-1.13.9/tests/test_kiro_agent_harness_routing.py +196 -0
  133. controlzero-1.13.9/tests/test_kiro_ide_v2_hooks_1944.py +480 -0
  134. controlzero-1.13.9/tests/test_kiro_version_schemes.py +123 -0
  135. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_layout_migration_t101.py +0 -1
  136. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_localized_reason.py +1 -0
  137. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_multi_client_per_project_175.py +8 -5
  138. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_per_client_isolation_1011.py +1 -2
  139. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_policy_freshness.py +0 -1
  140. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_policy_load_failclosed_f3.py +3 -4
  141. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_pr4_install_messaging.py +0 -1
  142. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_quarantine.py +0 -1
  143. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_reason_code.py +8 -3
  144. controlzero-1.13.9/tests/test_reason_code_hitl_grant_1596.py +34 -0
  145. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_secrets.py +0 -2
  146. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_secrets_reference_resolver.py +1 -2
  147. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_shell_semantic_class.py +0 -1
  148. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_sql_semantic_class.py +0 -1
  149. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_synthetic_policy_id_t79.py +10 -5
  150. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_t103_precedence.py +0 -1
  151. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_t108_local_override_audit.py +0 -1
  152. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_t96_single_audit_log.py +0 -1
  153. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_t99_install_prefetch_bundle.py +0 -1
  154. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_tamper.py +0 -1
  155. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_tamper_behavior.py +1 -1
  156. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_unsafe_int_boundary.py +0 -1
  157. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_update_cmd.py +2 -1
  158. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_version_aware_fail_closed.py +1 -0
  159. controlzero-1.13.7/controlzero/cli/hosts/kiro.py +0 -163
  160. controlzero-1.13.7/controlzero/cli/kiro_adapter.py +0 -223
  161. controlzero-1.13.7/controlzero/cli/templates/kiro/ide-pre-tool-use.kiro.hook +0 -15
  162. controlzero-1.13.7/controlzero/cli/templates/kiro/ide-prompt-submit.kiro.hook +0 -14
  163. controlzero-1.13.7/tests/test_kiro_adapter.py +0 -257
  164. {controlzero-1.13.7 → controlzero-1.13.9}/.gitignore +0 -0
  165. {controlzero-1.13.7 → controlzero-1.13.9}/Dockerfile.test +0 -0
  166. {controlzero-1.13.7 → controlzero-1.13.9}/LICENSE +0 -0
  167. {controlzero-1.13.7 → controlzero-1.13.9}/README.md +0 -0
  168. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/__main__.py +0 -0
  169. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/_internal/__init__.py +0 -0
  170. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/_internal/condition_ops.py +0 -0
  171. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/_internal/credentials_data/__init__.py +0 -0
  172. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/_internal/credentials_data/built_in.yaml +0 -0
  173. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/_internal/source_labels.py +0 -0
  174. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/_internal/tool_extractors.json +0 -0
  175. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/audit_local.py +0 -0
  176. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/__init__.py +0 -0
  177. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/__main__.py +0 -0
  178. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/agent_config.py +0 -0
  179. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/debug_bundle.py +0 -0
  180. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/exec_cmd.py +0 -0
  181. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/hook_entry.py +0 -0
  182. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/output.py +0 -0
  183. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/setup_wizard.py +0 -0
  184. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/templates/antigravity/hooks.json +0 -0
  185. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/templates/antigravity.yaml +0 -0
  186. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/templates/autogen.yaml +0 -0
  187. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/templates/claude-code.yaml +0 -0
  188. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/templates/codex-cli.yaml +0 -0
  189. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/templates/cost-cap.yaml +0 -0
  190. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/templates/crewai.yaml +0 -0
  191. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/templates/cursor.yaml +0 -0
  192. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/templates/gemini-cli.yaml +0 -0
  193. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/templates/generic.yaml +0 -0
  194. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/templates/kiro/ide-file-save.kiro.hook +0 -0
  195. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/templates/langchain.yaml +0 -0
  196. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/templates/mcp.yaml +0 -0
  197. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/templates/rag.yaml +0 -0
  198. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/cli/update_cmd.py +0 -0
  199. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/device.py +0 -0
  200. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/hitl/__init__.py +0 -0
  201. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/hitl/secret_leak_guard.py +0 -0
  202. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/hitl/status.py +0 -0
  203. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/hooks/__init__.py +0 -0
  204. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/hooks/tool_output_handler.py +0 -0
  205. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/hosted_policy.py +1 -1
  206. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/integrations/__init__.py +0 -0
  207. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/integrations/braintrust.py +0 -0
  208. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/integrations/crewai/__init__.py +1 -1
  209. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/integrations/google_adk/__init__.py +0 -0
  210. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/integrations/langchain/__init__.py +3 -3
  211. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/integrations/langfuse.py +0 -0
  212. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/layout_migration.py +0 -0
  213. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/secrets/__init__.py +0 -0
  214. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/secrets/secretstr.py +0 -0
  215. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/spool/__init__.py +0 -0
  216. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/spool/_compress.py +0 -0
  217. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/spool/_constants.py +0 -0
  218. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/spool/_crc32c.py +0 -0
  219. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/spool/_crypto.py +0 -0
  220. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/spool/_frame.py +0 -0
  221. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/spool/_keyring.py +0 -0
  222. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/spool/_metrics.py +0 -0
  223. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/spool/_spool.py +0 -0
  224. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/spool/_state.py +0 -0
  225. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/spool/_uploader.py +0 -0
  226. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/spool/cz-audit-v1.dict +0 -0
  227. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/tamper.py +2 -2
  228. {controlzero-1.13.7 → controlzero-1.13.9}/controlzero/tracecontext.py +0 -0
  229. {controlzero-1.13.7 → controlzero-1.13.9}/examples/hello_world.py +0 -0
  230. {controlzero-1.13.7 → controlzero-1.13.9}/tests/_fixtures/antigravity-envelope-expected.json +0 -0
  231. {controlzero-1.13.7 → controlzero-1.13.9}/tests/_fixtures/jcs_args_hash_vectors.json +0 -0
  232. {controlzero-1.13.7 → controlzero-1.13.9}/tests/conftest.py +0 -0
  233. {controlzero-1.13.7 → controlzero-1.13.9}/tests/fixtures/adapter-contracts/antigravity.json +0 -0
  234. {controlzero-1.13.7 → controlzero-1.13.9}/tests/fixtures/adapter-contracts/claude_code.json +0 -0
  235. {controlzero-1.13.7 → controlzero-1.13.9}/tests/fixtures/adapter-contracts/codex_cli.json +0 -0
  236. {controlzero-1.13.7 → controlzero-1.13.9}/tests/fixtures/adapter-contracts/cursor.json +0 -0
  237. {controlzero-1.13.7 → controlzero-1.13.9}/tests/fixtures/adapter-contracts/gemini_cli.json +0 -0
  238. {controlzero-1.13.7 → controlzero-1.13.9}/tests/integrations/__init__.py +0 -0
  239. {controlzero-1.13.7 → controlzero-1.13.9}/tests/integrations/test_google.py +0 -0
  240. {controlzero-1.13.7 → controlzero-1.13.9}/tests/parity/action_aliases.json +0 -0
  241. {controlzero-1.13.7 → controlzero-1.13.9}/tests/spool/__init__.py +0 -0
  242. {controlzero-1.13.7 → controlzero-1.13.9}/tests/spool/conftest.py +0 -0
  243. {controlzero-1.13.7 → controlzero-1.13.9}/tests/spool/test_spool_cli.py +0 -0
  244. {controlzero-1.13.7 → controlzero-1.13.9}/tests/spool/test_spool_concurrency.py +0 -0
  245. {controlzero-1.13.7 → controlzero-1.13.9}/tests/spool/test_spool_core.py +0 -0
  246. {controlzero-1.13.7 → controlzero-1.13.9}/tests/spool/test_spool_diskfull.py +0 -0
  247. {controlzero-1.13.7 → controlzero-1.13.9}/tests/spool/test_spool_keychain_dek.py +0 -0
  248. {controlzero-1.13.7 → controlzero-1.13.9}/tests/spool/test_spool_sink_wiring.py +0 -0
  249. {controlzero-1.13.7 → controlzero-1.13.9}/tests/spool/test_spool_transcript_localack.py +0 -0
  250. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_action_aliases.py +0 -0
  251. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_action_canonicalization.py +0 -0
  252. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_agent_name_env.py +0 -0
  253. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_agy_docs_no_exit2_1373.py +0 -0
  254. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_antigravity_hook_check.py +0 -0
  255. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_antigravity_install.py +0 -0
  256. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_antigravity_posttooluse_observe_58.py +0 -0
  257. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_api_key_mask.py +0 -0
  258. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_api_url_config.py +0 -0
  259. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_audit_sink_isolation.py +0 -0
  260. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_canonical_phase1a.py +0 -0
  261. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_capability_matrix_doc.py +0 -0
  262. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_cli_carve_out.py +0 -0
  263. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_cli_hook_entry.py +0 -0
  264. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_cli_hosted_refresh.py +0 -0
  265. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_cli_init.py +0 -0
  266. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_cli_tail.py +0 -0
  267. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_cli_test.py +0 -0
  268. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_cli_validate.py +0 -0
  269. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_coding_agent_hooks.py +0 -0
  270. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_config_format_parity_1303.py +0 -0
  271. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_conformance.py +0 -0
  272. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_console.py +0 -0
  273. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_deny_rule_reconciliation_1011.py +0 -0
  274. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_deny_wins_default_catchall_1620.py +0 -0
  275. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_device.py +0 -0
  276. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_doctor.py +0 -0
  277. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_engine_version_consistency.py +0 -0
  278. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_env_dump_438.py +0 -0
  279. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_error_codes.py +0 -0
  280. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_fail_closed_eval.py +0 -0
  281. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_failopen_1303.py +1 -1
  282. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_glob_matching.py +0 -0
  283. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_hitl_6a_exceptions.py +0 -0
  284. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_hitl_6a_pending_approval.py +0 -0
  285. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_hitl_validator_keys.py +0 -0
  286. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_hook_integrity.py +0 -0
  287. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_hook_mask_failclosed.py +0 -0
  288. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_hosted_local_shadow_warn_1265.py +0 -0
  289. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_hybrid_mode_strict.py +0 -0
  290. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_hybrid_mode_warn.py +0 -0
  291. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_install_api_url.py +0 -0
  292. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_install_hooks.py +4 -4
  293. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_io_capture_producer.py +0 -0
  294. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_kiro_cli_e2e.py +0 -0
  295. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_kiro_cli_hook_pin_1265.py +0 -0
  296. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_kiro_cli_v210_hooks.py +0 -0
  297. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_kiro_hook_templates.py +0 -0
  298. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_kiro_install.py +0 -0
  299. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_kiro_install_standardisation.py +0 -0
  300. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_layout_parity_t102.py +0 -0
  301. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_local_mode_dict.py +0 -0
  302. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_local_mode_file_json.py +0 -0
  303. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_local_mode_file_yaml.py +0 -0
  304. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_log_fallback_stderr.py +0 -0
  305. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_log_options_ignored_hosted.py +0 -0
  306. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_log_rotation.py +0 -0
  307. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_migrate.py +0 -0
  308. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_min_sdk_version_gate.py +0 -0
  309. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_no_policy_no_key.py +0 -0
  310. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_observe_mode_1247.py +0 -0
  311. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_package_rename_shim.py +0 -0
  312. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_part3_active_policy_count_1303.py +1 -1
  313. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_policy_engine_version_phase1b.py +0 -0
  314. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_policy_settings.py +0 -0
  315. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_policy_source_audit.py +0 -0
  316. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_pr4_output.py +0 -0
  317. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_pr4_status.py +0 -0
  318. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_probe_audit_isolation.py +0 -0
  319. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_refresh.py +1 -1
  320. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_setup_wizard.py +1 -1
  321. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_source_labels.py +0 -0
  322. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_t104_cache_gc.py +0 -0
  323. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_tamper_hook.py +0 -0
  324. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_telemetry_consent.py +0 -0
  325. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_tls_ca_bundle.py +0 -0
  326. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_tracecontext.py +0 -0
  327. {controlzero-1.13.7 → controlzero-1.13.9}/tests/test_upgrade_nudge.py +0 -0
  328. {controlzero-1.13.7 → controlzero-1.13.9}/tools/cz-kiro-adapter +0 -0
@@ -1,5 +1,401 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.13.9 (2026-07-28): Kiro IDE hooks execute on 1.0.x and stay in the correct project
4
+
5
+ ### Fixed
6
+
7
+ - **Kiro IDE hooks no longer appear enabled while silently executing on
8
+ nothing on Kiro 1.0.x (#1952).** The installer now emits `preToolUse` and
9
+ `promptSubmit` hooks in the `.json` format read by Kiro's 1.0 hook loader,
10
+ while retaining the legacy hook files needed by older builds. It does not
11
+ dual-emit `fileEdited`, because both loaders can execute that event and
12
+ doing so would duplicate scans and audit rows. `promptSubmit` now actually
13
+ runs on current Kiro; `preToolUse` is installed in the working format but
14
+ remains disabled by its existing opt-in flag.
15
+ - **Kiro payloads can no longer be claimed by the Claude Code adapter and
16
+ evaluated under the wrong project and DLP profile (#1945).** Kiro IDE 1.0+
17
+ and Kiro CLI v3 use PascalCase agent-harness events that overlap with Claude
18
+ Code. Kiro-specific payload signals now win routing, so attribution, API-key
19
+ selection, policy, and DLP configuration stay with the Kiro integration
20
+ instead of silently crossing tenants.
21
+ - **An inherited `CONTROLZERO_CLIENT` can no longer redirect a Kiro IDE call
22
+ into another host's configuration (#1951).** The Kiro shim now pins its
23
+ client identity rather than preserving a value inherited from the launching
24
+ environment, preventing the same silent wrong-project and wrong-DLP-profile
25
+ outcome.
26
+ - **A DLP mask match on a host that cannot rewrite tool input now tells the
27
+ agent what to fix (#1946).** The denial identifies the affected argument and
28
+ detector type without echoing the sensitive value, so the agent can retry
29
+ with the secret removed instead of dead-ending on a generic unsupported-mask
30
+ message.
31
+
32
+ ### Added
33
+
34
+ - **`controlzero coverage` is generated from the adapters' declared
35
+ capabilities (#1947).** The CLI now exposes each host's enforcement tier,
36
+ deciding and observation events, argument delivery, rewrite support, and
37
+ known gaps from the same data used by adapter contracts.
38
+
39
+ ### Kiro IDE enforcement status
40
+
41
+ Kiro IDE remains `tier=observe`; this release does **not** claim deterministic
42
+ enforcement. It makes the enabled `promptSubmit` hook execute on Kiro 1.0.x and
43
+ puts `preToolUse` one enable flag away from execution. Until that flag is
44
+ enabled and the deterministic block path is verified end to end, the SDK's
45
+ coverage declaration intentionally remains observe-only.
46
+
47
+ ## 1.13.8 (2026-07-27): warn decisions reach the agent + Kiro IDE adapter unblocked (P0)
48
+
49
+ ### Added
50
+
51
+ - **Branch-aware `git` actions and cloud-CLI subcommand actions (#1734, #1751).**
52
+ The #437 semantic-class layer classified a shell command's danger class but
53
+ never exposed its operand, so `git push origin main` and
54
+ `git push origin feature/x` both collapsed to `Bash:git` -- a policy could not
55
+ allow feature-branch pushes while denying a push to a protected branch. A
56
+ `Bash` call now additionally promotes `git <sub> ... <ref>` into a matchable
57
+ `git_<sub>:<target>` action (no ref named on the line -> `git_<sub>:*`) and
58
+ `aws` / `gcloud` / `kubectl` into `<cli>:<sub>`, so a rule can
59
+ `deny git_push:main`, `deny git_push:release/*` or `deny aws:s3` and let
60
+ everything else fall through to `default_action`. Refspecs reduce to their
61
+ destination (`src:dst` -> `dst`, a leading `+` stripped), `checkout -b` /
62
+ `switch -c NAME` resolve to the new branch name, and `git -C <dir>` /
63
+ `-c <name=value>` global options are skipped. The per-command `Bash:git`
64
+ action is still emitted, so existing `Bash:*` rules are unaffected.
65
+ Byte-identical to the Node and Go SDKs (`git_command_cases` and
66
+ `cloud_subcommand_cases` in `parity-cases.json`).
67
+ **Upgrade impact:** these are NEW candidate actions evaluated on every `Bash`
68
+ call. A policy that already carries a rule in the `git_*`, `aws:`, `gcloud:`
69
+ or `kubectl:` namespace will now match shell commands it never matched
70
+ before -- audit those rules before upgrading.
71
+ **Authoring note:** use explicit branch names or a namespace glob
72
+ (`release/*`). A bare `git_push:*` as an "any nested branch" matcher is not
73
+ portable: Python `fnmatch`, Node `minimatch` and Go `path.Match` disagree on
74
+ whether `*` crosses `/`.
75
+
76
+ ### Fixed
77
+
78
+ - **DLP never scanned a `warn` decision, so a `warn` catch-all silently
79
+ disabled data-leak scanning for an org's whole shell surface (#1927, P0).**
80
+ The scan gate in `_internal/enforcer.py` read
81
+ `if decision.allowed and args and self._dlp_scanner is not None:`.
82
+ `.allowed` is `effect == "allow"`; a `warn` is neither `.allowed` nor
83
+ `.denied`, so a warn-matched call proceeded to the tool with its arguments
84
+ NEVER scanned. Our own shipped "Shell Command Restrictions" template carried
85
+ an unconditional `warn bash:*` catch-all, so every shell call in an org using
86
+ it was exempt -- reported by a customer as "DLP isn't working". The
87
+ half-canonical `audit` policy effect is now rejected explicitly instead of
88
+ falling through differently across SDK and backend consumers. The gate is now a positive
89
+ membership test against `DLP_SCANNED_EFFECTS`, derived as
90
+ `ALL_EFFECTS - {"deny"}`, so every effect whose call PROCEEDS is scanned and
91
+ any effect added in future is scanned by default rather than silently
92
+ exempted.
93
+ - **The no-match default was structurally unreachable from the DLP scan
94
+ (#1931, P0).** That same gate sat INSIDE the per-rule loop, two lines before
95
+ its `return decision`. `evaluate()`'s no-match fallthrough returns from
96
+ outside the loop, so `default_action: allow` and `default_action: warn` --
97
+ the steady state for every allow-by-default org, on every call that matches
98
+ no rule -- were never scanned at all. Both decision origins now route
99
+ through a single chokepoint, `PolicyEvaluator.finalize_decision`, which is
100
+ the only caller of the scan.
101
+ - **An empty findings list meant both "scanned, clean" and "never scanned"
102
+ (#1927).** That ambiguity is why the bypass went unnoticed in the audit
103
+ trail for as long as it did. Every `PolicyDecision` and every row written to
104
+ the local audit log (`~/.controlzero/audit.log`) now carries required,
105
+ structured `dlp_outcome`, always with a non-empty reason. A `PolicyDecision`
106
+ cannot be constructed without it. The compatibility `dlp_status` projection
107
+ carries the same closed status and never defaults a missing value to clean.
108
+
109
+ The vocabulary is **one member, one meaning**:
110
+
111
+ | Status | Means |
112
+ | --- | --- |
113
+ | `clean` / `findings` | The scanner ran and returned a terminal verdict |
114
+ | `skipped` | Policy denied (nothing executes) or the data is explicitly ineligible |
115
+ | `unavailable` | The scanner could not be used. **Never** means clean |
116
+ | `mask_unsupported_on_host` | A mask matched on a host that cannot rewrite arguments |
117
+ | `suppressed` | **An admin deliberately turned enforcement off. Nothing else.** |
118
+ | `not_finalized` | Internal pre-finalization sentinel |
119
+ | `not_applicable` | The tool already ran (post-execution observation) |
120
+ | `unreachable` | An origin that structurally cannot reach the chokepoint |
121
+
122
+ An earlier draft of this fix routed six unrelated plumbing states through
123
+ `suppressed`. That would have rebuilt the very defect being fixed one field
124
+ over: `#1927` was an empty findings list meaning both "clean" and "never
125
+ looked", and a single `suppressed` would have meant both "an admin chose this"
126
+ and "the code did not get there" -- so an operator asking *show me everything
127
+ DLP did not block* could not separate deliberate policy from plumbing.
128
+ Disambiguating by also reading the free-text `reason` was rejected: `reason`
129
+ has no completeness guard and cannot carry a safety-critical distinction.
130
+
131
+ `suppressed` therefore has **no producer in this release** -- the explicit
132
+ admin observe posture is not implemented yet (#1939) and the default is
133
+ ENFORCE. The member is reserved so no plumbing state can claim the name
134
+ later. A critical telemetry alarm fires if the `not_finalized` sentinel
135
+ reaches an execution boundary, and both the idempotence guard and the
136
+ outcome-stamping check key on the status MEMBER, never on the reason string.
137
+ - **`guard_with_approval(retry_guard=False)` returned an allow that was
138
+ neither DLP-scanned nor audited (#1927).** On that path a human-approved
139
+ call proceeded with its ORIGINAL arguments and left no audit row. It now
140
+ routes through the same chokepoint and writes an audit row. A human
141
+ approving a tool call does not approve exfiltrating a secret in its
142
+ arguments.
143
+ - **The Node SDK is at parity, not tracking behind (#1937).**
144
+ `@controlzero/sdk` carried BOTH of the same bypasses -- `enforcer.ts` gated
145
+ the scan on `decision.allowed` inside the matched-rule branch -- so a `warn`
146
+ catch-all disabled DLP there exactly as it did in Python. Node now has the
147
+ same single chokepoint, the same subtraction-derived scanned-effect set, and
148
+ the same closed outcome vocabulary. Both suites assert against ONE shared
149
+ contract fixture (`tests/parity/dlp_control_outcomes.json`), so adding a
150
+ status or an effect to one SDK alone turns the other SDK's suite red. Nothing
151
+ structural previously stopped one from being fixed while the other was left
152
+ behind, which is how this shipped in two SDKs at once. **The Go SDK is NOT
153
+ ported and still carries the bypass** -- #1937 stays open for it.
154
+ - **A human-approved HITL request stays an `allow` (#1936).** An in-flight
155
+ revision of this work flipped the synthetic approve decision to a `deny` so an
156
+ unscanned allow could not escape. That produced the incoherent pair
157
+ `status == "approved"` with `decision.denied == True`, breaking every caller
158
+ reading `approval.decision.allowed`. The DLP concern belongs in the outcome,
159
+ not the effect: no scanner or arguments exist at that point, so the decision
160
+ records `unreachable(hitl_approval_requires_reguard)` -- which can never read
161
+ as "scanned, clean" -- and the `Client` re-guards the original request through
162
+ the chokepoint before anything executes.
163
+
164
+ - **The Kiro IDE preToolUse hook blocked every tool call because of OUR
165
+ adapter, not an upstream bug (#1885).** Verified against the shipped build on
166
+ the reporting machine -- Kiro IDE **0.12.333** (commit `c7e35289`, built
167
+ 2026-06-10, read from `Kiro.app/Contents/Resources/app/product.json`): the IDE
168
+ does NOT deliver `USER_PROMPT={}`. Its v1 hook provider sends
169
+ `USER_PROMPT=JSON.stringify(toolArgs)` -- the tool ARGUMENTS with **no tool
170
+ name** (postToolUse sends the full `{toolName, toolArgs, toolResult,
171
+ toolSuccess}`). `kiro_adapter._normalize` REQUIRED `toolName`, returned
172
+ `None` for that shape, and every real IDE tool call landed on the
173
+ unknown-payload fail-closed path. An args-only payload is now evaluated on
174
+ the arguments that did arrive, under the explicit placeholder tool
175
+ `kiro_ide_unknown_tool`, and MARKED (`cz_payload_degraded`) plus a loud
176
+ stderr line so a tool-identity-unknown decision can never be read as a normal
177
+ argument-level governed decision. A genuinely empty payload (`{}`, non-JSON,
178
+ a JSON non-object) still fails closed.
179
+ - **Adapter exit codes are now safe across the Kiro 1.0 boundary (#1885).**
180
+ Kiro 1.0 redefines a non-zero exit OTHER than 2 as a warning that lets the
181
+ tool PROCEED. The adapter now emits ONLY 0 or 2: an uncaught exception is
182
+ caught at the top level and collapsed to the block code instead of escaping
183
+ as Python's exit 1 (which blocks on 0.12.x and would silently allow on
184
+ 1.0.x). `controlzero install kiro` and `controlzero kiro verify` print the
185
+ version boundary, the detected Kiro build, and the residual risk.
186
+ - **Forward-compatible with the v2 hook provider (#1885).** The same 0.12.333
187
+ bundle carries a second provider (session setting `hooks.v2 === true`) that
188
+ spawns hooks with an EMPTY env and delivers a Claude-Code-shaped JSON payload
189
+ on STDIN. The adapter now falls back to a timeout-bounded stdin read when
190
+ `USER_PROMPT` is empty, so a flip to v2 yields full-fidelity governance
191
+ instead of fail-closed-on-everything. The v1 path never touches stdin.
192
+
193
+ - **A `warn` decision is no longer byte-identical to an allow on the hook
194
+ surface (#1903, P0).** `controlzero hook-check` had no warn branch: on the
195
+ enforcer's `PolicyDecision` a `warn` effect satisfies neither `.denied` nor
196
+ `.allowed`, and the CLI's only decision branch was `if decision.denied:`. A
197
+ warn therefore fell through to the allow tail and was re-emitted as
198
+ `{"decision":"allow","reason":"[Control Zero] Allowed: <tool>"}` -- the
199
+ operator's warn message was discarded on every host. In the reported
200
+ incident a `find <dir> -mindepth 1 -delete` was classified CORRECTLY as
201
+ semantic class `file_delete` with a matching `warn` rule, and the agent was
202
+ handed an unqualified allow; the warn was visible in the dashboard only.
203
+ A warn still PROCEEDS (unchanged advisory contract, exit 0, no host block or
204
+ prompt token on any surface), but the operator's reason now reaches the
205
+ agent and the human, branded `[Control Zero] WARN: <reason>` on stdout and
206
+ stderr. Claude Code and Codex CLI additionally use the documented
207
+ non-deciding channels `systemMessage` (human) and
208
+ `hookSpecificOutput.additionalContext` (model) -- never `permissionDecision`,
209
+ which would auto-approve and bypass the user's own permission prompts.
210
+ Antigravity keeps its documented `decision:"allow"` token and carries the
211
+ warn text as the reason. Cursor IDE/CLI, which drop `reason` entirely on the
212
+ proceed path, now carry it in `user_message` / `agent_message`.
213
+
214
+ - **E1404 (TLS verification failed) pointed at a knob the SDK never reads
215
+ (#1824).** The fix hint told users to set `REQUESTS_CA_BUNDLE`, which appears
216
+ nowhere in the SDK's TLS path, so anyone hitting a TLS failure against an
217
+ air-gapped or self-hosted backend followed it and stayed broken. The bundled
218
+ `error_codes.yaml` now names the real custom-CA surface --
219
+ `CONTROLZERO_TLS_CA_BUNDLE`, `tls_ca_bundle` in `~/.controlzero/config.yaml`,
220
+ or `controlzero setup --tls-ca-bundle` -- and leads with the private-CA /
221
+ self-signed certificate case (the common cause), keeping clock skew as the
222
+ fallback. This text is spliced into `str(exc)` for every E1404, so it is what
223
+ the user sees on CLI stderr and in logs.
224
+
225
+ ### Changed
226
+
227
+ - **A DLP `block` match now DENIES a `warn` decision, and a DLP
228
+ `mask` match now applies to it (#1927) -- READ THIS BEFORE UPGRADING.**
229
+ Now that proceeding decisions are actually scanned, the scan's verdict
230
+ applies to them. A `warn` rule promises that THE RULE will not block; it
231
+ does not exempt the call from a mandatory org-level data control, and the
232
+ alternative -- letting any `warn` rule silently downgrade the org's DLP
233
+ posture -- is precisely the bypass being fixed.
234
+ **Upgrade impact, stated plainly:** an organisation running a `warn`
235
+ catch-all (including the `warn bash:*` in our own shipped "Shell Command
236
+ Restrictions" template, and any observation-only deployment) will start
237
+ seeing DENIES it has never seen before. Those calls were always violating a
238
+ DLP `block` rule; the SDK was not looking. The same is true for
239
+ `default_action: allow` / `warn` on calls that match no rule. On the
240
+ coding-agent hook surface the change is wider still: `hook-check` escalates
241
+ ANY decision carrying `masked_args` to a block (no host hook protocol can
242
+ hand rewritten tool input back), so a `warn` or a no-match-default call with
243
+ a MASK hit -- which previously could not happen -- will now block on Claude
244
+ Code, Gemini CLI, Codex CLI, Kiro and Antigravity. Before upgrading, review
245
+ your DLP rules with `action: block` and `action: mask` against the traffic
246
+ your `warn` rules cover.
247
+ - **DLP masking is negotiated before the verdict, never corrected after audit
248
+ (#1927, #1938).** Masking rewrites the
249
+ arguments the agent receives. With warn/no-match-default now in scope,
250
+ it can do so on a path where the operator only asked to be WARNED, so a
251
+ masked decision carries an explicit `dlp_masked` flag and the audit row
252
+ records `dlp_masked: true`.
253
+
254
+ Mask capability is now **per host, declared as data** on each adapter's
255
+ coverage record (`can_rewrite_tool_input`, `rewrite_field`,
256
+ `rewrite_events`), verified against each vendor's own documentation. A
257
+ previous draft of this entry stated that the hook protocol of *every* host
258
+ "cannot return rewritten tool arguments". **That was wrong**, and correcting
259
+ it is why this paragraph exists (#202 no-false-claims):
260
+
261
+ | Host | Can rewrite tool input | Masking behaviour in that release |
262
+ | --- | --- | --- |
263
+ | Claude Code | Yes -- `hookSpecificOutput.updatedInput` | **Masks and proceeds** |
264
+ | Gemini CLI | Yes -- `hookSpecificOutput.tool_input` (BeforeTool) | **Masks and proceeds** |
265
+ | Codex CLI | Yes, but only alongside `permissionDecision:"allow"` | Denies (see below) |
266
+ | Cursor IDE | Yes, on `preToolUse` only -- `updated_input` | Denies (see below) |
267
+ | Cursor CLI | No (only `beforeShellExecution` is wired) | Denies |
268
+ | Kiro CLI / IDE | No (exit-code-only contract) | Denies |
269
+ | Antigravity | No (`decision`/`reason`/`permissionOverrides` only) | Denies |
270
+ | Unknown host | Unknown, so treated as No | Denies |
271
+
272
+ **What changed for you:** on Claude Code and Gemini CLI a DLP `mask` match no
273
+ longer blocks the call -- the redacted arguments are sent to the host and the
274
+ call proceeds. Previously it was a hard deny on every host.
275
+
276
+ **Two capable hosts are deliberately NOT enabled**, and this is a named gap
277
+ rather than an oversight (#1938). Codex CLI's documented rewrite is only valid
278
+ with `permissionDecision:"allow"`, which would auto-approve the call and
279
+ bypass Codex's own confirmation -- trading a fail-closed deny for a fail-open
280
+ auto-approve is a separate security decision, not a side effect of a
281
+ scan-coverage fix. Cursor IDE's rewrite has not been observed taking effect on
282
+ a live host, and Cursor staff report `allow`/`ask` are currently ignored
283
+ there; enabling a mask we have not seen applied is the one DLP failure mode
284
+ with no recovery (we report a mask, the raw secret runs). Both stay at
285
+ today's behaviour: the mask escalates to a deny recorded as
286
+ `mask_unsupported_on_host(host_cannot_rewrite_arguments)`, decided BEFORE
287
+ audit and host rendering, so the audit row and the wire verdict agree.
288
+
289
+ Antigravity has an undocumented `overwrite` field in its Go runtime. It is
290
+ **not used and must not be**: it is reported broken (the permission review
291
+ fires on the ORIGINAL call and the overwrite is silently discarded), which
292
+ would mean believing we masked while the raw secret executed.
293
+ - `controlzero kiro verify` now reports **per-workspace Kiro IDE hook
294
+ coverage** and names the workspace path it inspected. Verify previously
295
+ reported PASS while the user's actual IDE workspace was ungoverned, because
296
+ IDE hooks are per-workspace and had been installed elsewhere. The CLI
297
+ PASS/FAIL verdict is unchanged and now says explicitly that it covers the
298
+ Kiro CLI surface only. New `--workspace` option (defaults to the current
299
+ directory).
300
+ - The IDE Pre Tool Use hook template still ships **disabled** (unchanged), but
301
+ its description no longer repeats the false "empty payload" claim. It now
302
+ states the verified build, the real payload shape, and the two real limits:
303
+ tool-NAME rules cannot match a v1 preToolUse call, and on the v1 provider the
304
+ block is LLM-mediated (any hook output intercepts the call and the model is
305
+ told the tool did not run, then decides) rather than a deterministic stop.
306
+
307
+ - **The Kiro IDE adapter now BLOCKS a deciding event whose DLP scan coverage
308
+ cannot be established (#1892) -- new behaviour on a hook that ships ENABLED.**
309
+ Where the adapter cannot prove the scanner will see the whole payload, it
310
+ exits 2 instead of forwarding an unvouched-for scan dressed up as a governed
311
+ decision. Triggers are policy-independent: a raw payload over 1,000,000
312
+ characters, decodable JSON still nested past depth 3, or any exception raised
313
+ while decoding. This reaches `preToolUse` AND `promptSubmit`, and
314
+ `ide-prompt-submit.kiro.hook` ships `"enabled": true` -- in 1.13.7 the
315
+ promptSubmit branch had no coverage concept and could not block at all. File
316
+ events (`fileSave`) are post-write and observe-only, so they proceed loudly
317
+ rather than pretending they were scanned. There is currently NO opt-out for
318
+ this path: `CZ_KIRO_UNKNOWN_PAYLOAD=open` is consulted only on the earlier
319
+ empty/unparseable-payload branch and does not cover it (tracked in #1925). If
320
+ you hit this on a legitimate payload, capture its SIZE and SHAPE (never its
321
+ content) and report against #1892.
322
+ - **The Kiro IDE adapter's DLP scan surface is wider, so previously-allowed
323
+ input can now DENY (#1892).** Every normalised payload now carries the full
324
+ received payload on `tool_input` -- every JSON key, duplicate members,
325
+ resolved `\uXXXX` escapes and one further decoded JSON layer, or the raw bytes
326
+ when it is not JSON. `hook-check` scans the recursively extracted string
327
+ values of `tool_input`, so a prompt or tool argument containing a JSON blob
328
+ whose KEY names or escaped content match a DLP block rule now denies where
329
+ 1.13.7 allowed. Known side effect: DLP findings for tool events can appear
330
+ twice in a single audit row, which changes finding counts for existing
331
+ customers.
332
+
333
+ ### Known gap
334
+
335
+ - The **Node** SDK adapter (`sdks/node/controlzero/src/cli/kiroAdapter.ts`)
336
+ still returns `null` when `toolName` is absent and therefore still blocks
337
+ every call if its IDE preToolUse hook is enabled. Its template description
338
+ now says so. The port of this fix is tracked in #1885.
339
+
340
+ - The **Node** SDK has no `warn` branch at all:
341
+ `sdks/node/controlzero/src/cli/main.ts` still branches only on
342
+ `decision.denied`, and no Node host adapter carries warn text on a proceed
343
+ path. The #1903 fix above is **Python-SDK only** -- for a `@controlzero/sdk`
344
+ user a `warn` is still discarded and re-emitted as a bare allow on every host
345
+ named there. The port is tracked in #1923.
346
+ - **DLP residuals from this release, named rather than left silent.** None of
347
+ these is a regression; each is a boundary this fix did not reach:
348
+ - **Three decision origins still proceed without reaching the DLP chokepoint
349
+ (#1936):** the CLI's pre-`Client` bundle-missing allow, the HITL approver
350
+ approval, and the no-policy pass-through allow. All three now RECORD
351
+ `unreachable` with a specific reason instead of producing a row that reads
352
+ like a clean scan, so they are visible in the audit trail -- but the scan
353
+ genuinely does not run there.
354
+ - **`dlp_findings` and `dlp_status` never leave the machine (#1935).**
355
+ `audit_remote._build_wire_entry` carries neither field, so the remote sink
356
+ and the dashboard do not see this signal. Everything this release says about
357
+ the audit trail is true of the LOCAL log (`~/.controlzero/audit.log`) and the
358
+ decision object; it is NOT yet true of the wire. Closing it needs matching
359
+ backend `AuditIngestEntry` columns.
360
+ - **The explicit admin observe posture is not implemented (#1939).** `suppressed`
361
+ is reserved and producerless; the only posture available today is ENFORCE.
362
+ An org that genuinely wants detect-only cannot yet elect it.
363
+ - **Codex CLI and Cursor IDE can rewrite tool input but are not enabled
364
+ (#1938)** -- see the masking entry above for why each was held back.
365
+ - **The Go SDK still carries both bypasses (#1937).** Only Python and Node are
366
+ fixed in this release.
367
+ - **Three origins write NO audit row at all (#1941)** -- broader than the DLP
368
+ gap above, and worth stating plainly: the no-policy pass-through allow, the
369
+ synthetic HITL grant deny (7 call sites, whose docstring claims it records
370
+ to the audit store while the body only logs a warning), and the CLI's
371
+ unenrolled first-run allow, which exits before a `Client` exists. Making
372
+ every decision carry a truthful `dlp_outcome` is worth nothing on a path
373
+ that never persists one.
374
+
375
+ ### Fixed (audit fidelity)
376
+
377
+ - **The observing posture survives a DLP override.** `observe` was set only on
378
+ the matched-rule construction, so every DERIVED decision -- the DLP block
379
+ override, the mask-boundary fail-closed deny, the size-cap deny and the
380
+ scanner-unavailable deny -- silently dropped it. A DLP-block deny made under
381
+ an observe posture was indistinguishable from an enforcing deny in the audit
382
+ row. Fixed in both SDKs.
383
+ - `controlzero validate` still warns on CUSTOM-VOCABULARY actions the backend
384
+ accepts -- `trade.execute`, `patient.read`, `data.read`, `mcp.tool.call`,
385
+ `production:*` -- so four shipped dashboard templates print `rule action
386
+ '<x>' is not a known canonical action or alias` and then parse and enforce
387
+ correctly. The warning is cosmetic (no decision depends on it) and is tracked
388
+ in #1918. The semantic-class namespaces are NO LONGER affected: #1913
389
+ registered `file_delete`, `network`, `git_<sub>` and `aws`/`gcloud`/`kubectl`
390
+ in all three SDK validators, so `warn: file_delete:*` -- the rule shape this
391
+ release makes useful -- now validates cleanly.
392
+ - Semantic-class coverage for destructive shell commands is partial (#1905).
393
+ `_SHELL_CLASS_MAP` deliberately excludes `dd`, `rsync`, `git`, `tar`, `awk`,
394
+ `openssl`, `sed` and every interpreter (`python`, `node`, `bash`, ...), so
395
+ `dd of=...`, `git clean -xfd`, `rsync --delete` and
396
+ `python -c "shutil.rmtree(...)"` classify as empty and match NO semantic-class
397
+ rule. A `file_delete` rule does not see destructive shell deletion in general.
398
+
3
399
  ## 1.13.6 (2026-07-12): interpreter-pinned hook command (P0)
4
400
 
5
401
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: controlzero
3
- Version: 1.13.7
3
+ Version: 1.13.9
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.7"
44
+ __version__ = "1.13.9"
45
45
 
46
46
  __all__ = [
47
47
  "Client",
@@ -131,6 +131,51 @@ SYSTEM_MESSAGES: dict[str, dict[str, str]] = {
131
131
  "마스킹되지 않은 값을 전달하는 대신 차단합니다(기본 차단)."
132
132
  ),
133
133
  },
134
+ # DLP mask on a host whose hook protocol cannot accept rewritten tool input
135
+ # (every host except Claude Code + Gemini CLI). The mask escalates to a
136
+ # deny, and THIS is the text the model reads on that deny -- so it is
137
+ # written as an INSTRUCTION to the agent, not as a status line for a human.
138
+ # Goal: the agent re-issues the same call with the sensitive value removed,
139
+ # so the user's task completes in one extra round-trip instead of dead-
140
+ # ending. See controlzero/_internal/dlp_remediation.py.
141
+ #
142
+ # {findings} is TYPE + LOCATION only. It NEVER contains the detected value:
143
+ # echoing the secret into the model's context is the exact disclosure DLP
144
+ # exists to prevent (#1881, "PII never in cleartext").
145
+ #
146
+ # NO ``ko`` ENTRY, deliberately. The Korean pack is a professional draft
147
+ # pending customer review (see the module docstring), and an unreviewed
148
+ # machine translation of a SECURITY INSTRUCTION is worse than the English
149
+ # fallback -- a mistranslated "do not re-send the value" is a leak. Korean
150
+ # locales fall back to ``en``, which is exactly what they get today from the
151
+ # hard-coded English this replaces, so there is no regression to fix later.
152
+ "DLP_MASK_UNSUPPORTED:agent": {
153
+ "en": (
154
+ "Blocked: the tool arguments contain sensitive data, and this "
155
+ "agent host cannot accept redacted arguments, so the call was "
156
+ "blocked instead of masked. Findings: {findings}. To proceed, "
157
+ "retry the SAME call with the sensitive value removed -- refer to "
158
+ "it indirectly (an environment variable the tool expands, a "
159
+ "secret-manager reference, or a file path the tool reads) instead "
160
+ "of inlining it. Do not print, echo, restate, or re-send the value "
161
+ "in any form, including in a summary or an explanation."
162
+ ),
163
+ },
164
+ # Location-free fallback: a mask matched on the flattened argument text but
165
+ # no single argument leaf carries it (a pattern spanning the boundary
166
+ # between two concatenated values). Still actionable, just less precise.
167
+ "DLP_MASK_UNSUPPORTED:base": {
168
+ "en": (
169
+ "Blocked: the tool arguments contain sensitive data, and this "
170
+ "agent host cannot accept redacted arguments, so the call was "
171
+ "blocked instead of masked. To proceed, retry the SAME call with "
172
+ "the sensitive value removed -- refer to it indirectly (an "
173
+ "environment variable the tool expands, a secret-manager "
174
+ "reference, or a file path the tool reads) instead of inlining it. "
175
+ "Do not print, echo, restate, or re-send the value in any form, "
176
+ "including in a summary or an explanation."
177
+ ),
178
+ },
134
179
  # Tamper / quarantine (constructed in client.py + cli/main.py).
135
180
  "BUNDLE_TAMPERED": {
136
181
  "en": (
@@ -39,7 +39,7 @@ modern intent is silently broken.
39
39
  from __future__ import annotations
40
40
 
41
41
  import json
42
- from typing import Iterable
42
+ from collections.abc import Iterable
43
43
 
44
44
  # Tool covered by this alias table. Currently only "database" carries
45
45
  # the legacy <-> canonical split; other tools were added post-#350 and
@@ -23,10 +23,10 @@ pass.
23
23
  """
24
24
  from __future__ import annotations
25
25
 
26
- from typing import Iterable
26
+ from collections.abc import Iterable
27
27
 
28
- from controlzero._internal.action_aliases import TOOL as _ALIAS_TOOL
29
28
  from controlzero._internal.action_aliases import _AMBIGUOUS, _CLASSES
29
+ from controlzero._internal.action_aliases import TOOL as _ALIAS_TOOL
30
30
 
31
31
  # Mirror of the canonical tool set + host-aliases the extractors
32
32
  # accept. Source of truth is tool_extractors.json; this list is
@@ -34,6 +34,16 @@ from controlzero._internal.action_aliases import _AMBIGUOUS, _CLASSES
34
34
  _CANONICAL_TOOLS: set[str] = {
35
35
  "Bash", "database", "http", "web_search", "browser",
36
36
  "file_read", "file_write", "file_search", "task",
37
+ # Shell semantic-class NAMESPACES (#437). The evaluator promotes a shell
38
+ # command's danger class into its own namespace as `<class>:<class>`
39
+ # (enforcer.py `evaluate`), so `file_delete:*` and `network:*` are
40
+ # first-class rule actions exactly like the `file_read:*` / `file_write:*`
41
+ # siblings already listed above. #437 registered two of the four classes
42
+ # and missed these two, so the shipped "Shell Command Restrictions"
43
+ # dashboard template warned "not a known canonical action" on rules that
44
+ # were in fact firing correctly (#1904). Warn-only path; no decision
45
+ # depends on this set.
46
+ "file_delete", "network",
37
47
  # database aliases
38
48
  "sql", "Database", "PostgreSQL", "MySQL", "postgres", "sqlite",
39
49
  # Bash aliases
@@ -61,6 +71,14 @@ _DATABASE_TOOL_ALIASES = {
61
71
  "database", "sql", "Database", "PostgreSQL", "MySQL", "postgres", "sqlite",
62
72
  }
63
73
 
74
+ # Context-sensitive namespaces the evaluator promotes for a shell call
75
+ # (#1734): `git <sub> ... <ref>` becomes `git_<sub>:<target>` and
76
+ # `aws|gcloud|kubectl <sub>` becomes `<cli>:<sub>`. The git subcommand set is
77
+ # open (any `git_*` tool token is a promoted action), so it is matched by
78
+ # prefix rather than enumerated.
79
+ _GIT_ACTION_TOOL_PREFIX = "git_"
80
+ _CLOUD_CLI_TOOLS = {"aws", "gcloud", "kubectl"}
81
+
64
82
 
65
83
  def _build_known_database_methods() -> set[str]:
66
84
  out: set[str] = {"*"}
@@ -87,6 +105,9 @@ def is_known_action(action: str) -> bool:
87
105
  tool, _, method = action.partition(":")
88
106
  if tool == "*":
89
107
  return True
108
+ # #1734 promoted namespaces: git_<sub>:<target>, <cloud_cli>:<sub>.
109
+ if tool.startswith(_GIT_ACTION_TOOL_PREFIX) or tool in _CLOUD_CLI_TOOLS:
110
+ return True
90
111
  if tool not in _CANONICAL_TOOLS:
91
112
  return False
92
113
  if method == "*" or method == "":
@@ -322,6 +322,7 @@ def _zstd_decompress(data: bytes) -> bytes:
322
322
  # Preferred: `zstandard` is the de-facto PyPI package.
323
323
  try:
324
324
  import io
325
+
325
326
  import zstandard as zstd
326
327
 
327
328
  dctx = zstd.ZstdDecompressor()
@@ -620,7 +621,7 @@ def translate_to_local_policy(
620
621
  :func:`controlzero.policy_loader.load_policy`: a dict with
621
622
  ``version: "1"`` and a flat ``rules`` list where each entry is
622
623
  either ``{"deny": "<tool>"}``, ``{"allow": "<tool>"}`` or
623
- ``{"effect": "<deny|allow|warn|audit>", "action": "<tool>"}``.
624
+ ``{"effect": "<deny|allow|warn>", "action": "<tool>"}``.
624
625
 
625
626
  Policies are sorted by ``priority`` DESCENDING (higher number = higher
626
627
  precedence = evaluated first; first-match-wins), so SDKs in every language
@@ -961,15 +962,22 @@ def _translate_rule(rule: dict, policy_id: str) -> Optional[dict]:
961
962
  # behavior) turned an unknown rule into allow-*-for-its-pattern -- a
962
963
  # fail-open for a security gate (#1303). A deny here over-denies that one
963
964
  # pattern at worst, which is the safe direction.
965
+ #
966
+ # ALL_EFFECTS is the single source of truth for that vocabulary (#1927);
967
+ # DLP_SCANNED_EFFECTS is derived from it by subtraction, so any effect that
968
+ # can reach a decision from here is DLP-scanned by default. Lazy import:
969
+ # enforcer imports from this file.
970
+ from controlzero._internal.enforcer import ALL_EFFECTS
971
+
964
972
  effect_raw = rule.get("effect")
965
973
  if not effect_raw:
966
974
  # Only fall back to rule["action"] for effect if it looks like
967
975
  # one of the canonical effects. Otherwise "action" is a tool
968
976
  # name and we should not use it here.
969
977
  fallback = rule.get("action")
970
- if fallback in ("allow", "deny", "warn", "audit"):
978
+ if fallback in ALL_EFFECTS:
971
979
  effect_raw = fallback
972
- effect = effect_raw if effect_raw in ("allow", "deny", "warn", "audit") else "deny"
980
+ effect = effect_raw if effect_raw in ALL_EFFECTS else "deny"
973
981
 
974
982
  # Tool pattern resolution order:
975
983
  # 1. plural ``actions`` (list, as emitted by the backend)
@@ -992,7 +1000,7 @@ def _translate_rule(rule: dict, policy_id: str) -> Optional[dict]:
992
1000
  # it is NOT a canonical effect name (effect already captured
993
1001
  # above).
994
1002
  candidate = rule.get("action")
995
- if candidate and candidate not in ("allow", "deny", "warn", "audit"):
1003
+ if candidate and candidate not in ALL_EFFECTS:
996
1004
  patterns = [str(candidate)]
997
1005
 
998
1006
  if not patterns:
@@ -39,7 +39,6 @@ from typing import Any, Literal
39
39
  from controlzero._internal.credential_scanner import scan_for_credentials
40
40
  from controlzero.errors import CredentialLeakBlocked
41
41
 
42
-
43
42
  Action = Literal["warn", "redact", "block"]
44
43
  Source = Literal["tool_output", "tool_stderr", "file_read", "grep_match"]
45
44
 
@@ -34,7 +34,6 @@ from functools import lru_cache
34
34
  from importlib import resources
35
35
  from typing import Any, Optional
36
36
 
37
-
38
37
  # Default per-occurrence byte cap for marker-block patterns that omit
39
38
  # `max_block_bytes`. Matches the Rust scanner's
40
39
  # `DEFAULT_MAX_BLOCK_BYTES` constant; 8192 bytes covers every PEM