controlzero 1.13.8__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 (324) hide show
  1. {controlzero-1.13.8 → controlzero-1.13.9}/CHANGELOG.md +46 -2
  2. {controlzero-1.13.8 → controlzero-1.13.9}/PKG-INFO +1 -1
  3. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/__init__.py +1 -1
  4. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/_messages.py +45 -0
  5. controlzero-1.13.9/controlzero/_internal/dlp_remediation.py +245 -0
  6. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/enforcer.py +25 -1
  7. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/enforcement.py +24 -1
  8. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/hosts/claude_code.py +21 -0
  9. controlzero-1.13.9/controlzero/cli/hosts/kiro.py +364 -0
  10. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/kiro_adapter.py +26 -1
  11. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/main.py +273 -8
  12. {controlzero-1.13.8 → controlzero-1.13.9}/pyproject.toml +1 -1
  13. {controlzero-1.13.8 → controlzero-1.13.9}/tests/fixtures/adapter-contracts/kiro.json +4 -2
  14. controlzero-1.13.9/tests/test_cli_coverage.py +62 -0
  15. controlzero-1.13.9/tests/test_dlp_agent_remediation_1938.py +366 -0
  16. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_kiro_adapter.py +196 -1
  17. controlzero-1.13.9/tests/test_kiro_agent_harness_routing.py +196 -0
  18. controlzero-1.13.9/tests/test_kiro_ide_v2_hooks_1944.py +480 -0
  19. controlzero-1.13.9/tests/test_kiro_version_schemes.py +123 -0
  20. controlzero-1.13.8/controlzero/cli/hosts/kiro.py +0 -181
  21. {controlzero-1.13.8 → controlzero-1.13.9}/.gitignore +0 -0
  22. {controlzero-1.13.8 → controlzero-1.13.9}/Dockerfile.test +0 -0
  23. {controlzero-1.13.8 → controlzero-1.13.9}/LICENSE +0 -0
  24. {controlzero-1.13.8 → controlzero-1.13.9}/README.md +0 -0
  25. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/__main__.py +0 -0
  26. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/__init__.py +0 -0
  27. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/action_aliases.py +0 -0
  28. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/action_validator.py +0 -0
  29. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/bundle.py +0 -0
  30. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/condition_ops.py +0 -0
  31. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/credential_hook.py +0 -0
  32. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/credential_scanner.py +0 -0
  33. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/credentials_data/__init__.py +0 -0
  34. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/credentials_data/built_in.yaml +0 -0
  35. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/dlp_scanner.py +0 -0
  36. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/hook_extractors.py +0 -0
  37. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/io_capture_producer.py +0 -0
  38. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/source_labels.py +0 -0
  39. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/tool_extractors.json +0 -0
  40. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/types.py +0 -0
  41. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/audit_local.py +0 -0
  42. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/audit_remote.py +0 -0
  43. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/canonical.py +0 -0
  44. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/__init__.py +0 -0
  45. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/__main__.py +0 -0
  46. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/_secrets.py +0 -0
  47. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/agent_config.py +0 -0
  48. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/console.py +0 -0
  49. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/debug_bundle.py +0 -0
  50. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/doctor.py +0 -0
  51. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/exec_cmd.py +0 -0
  52. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/hook_entry.py +0 -0
  53. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/hosts/__init__.py +0 -0
  54. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/hosts/antigravity.py +0 -0
  55. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/hosts/base.py +0 -0
  56. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/hosts/codex_cli.py +0 -0
  57. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/hosts/cursor.py +0 -0
  58. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/hosts/cursor_cli.py +0 -0
  59. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/hosts/gemini_cli.py +0 -0
  60. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/hosts/unknown.py +0 -0
  61. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/migrate.py +0 -0
  62. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/output.py +0 -0
  63. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/setup_wizard.py +0 -0
  64. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/spool_cmd.py +0 -0
  65. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/telemetry_consent.py +0 -0
  66. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/antigravity/hooks.json +0 -0
  67. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/antigravity.yaml +0 -0
  68. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/autogen.yaml +0 -0
  69. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/claude-code.yaml +0 -0
  70. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/codex-cli.yaml +0 -0
  71. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/cost-cap.yaml +0 -0
  72. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/crewai.yaml +0 -0
  73. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/cursor.yaml +0 -0
  74. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/gemini-cli.yaml +0 -0
  75. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/generic.yaml +0 -0
  76. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/kiro/ide-file-save.kiro.hook +0 -0
  77. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/kiro/ide-pre-tool-use.kiro.hook +0 -0
  78. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/kiro/ide-prompt-submit.kiro.hook +0 -0
  79. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/kiro/kiro.yaml +0 -0
  80. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/langchain.yaml +0 -0
  81. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/mcp.yaml +0 -0
  82. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/rag.yaml +0 -0
  83. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/update_cmd.py +0 -0
  84. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/client.py +0 -0
  85. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/device.py +0 -0
  86. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/enrollment.py +0 -0
  87. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/error_codes.py +0 -0
  88. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/error_codes.yaml +0 -0
  89. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/errors.py +0 -0
  90. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/hitl/__init__.py +0 -0
  91. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/hitl/grant_protocol.py +0 -0
  92. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/hitl/mock.py +0 -0
  93. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/hitl/pending_approval.py +0 -0
  94. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/hitl/secret_leak_guard.py +0 -0
  95. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/hitl/status.py +0 -0
  96. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/hook_integrity.py +0 -0
  97. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/hooks/__init__.py +0 -0
  98. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/hooks/tool_output_handler.py +0 -0
  99. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/hosted_policy.py +0 -0
  100. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/__init__.py +0 -0
  101. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/anthropic.py +0 -0
  102. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/autogen.py +0 -0
  103. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/braintrust.py +0 -0
  104. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/crewai/__init__.py +0 -0
  105. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/crewai/agent.py +0 -0
  106. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/crewai/crew.py +0 -0
  107. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/crewai/task.py +0 -0
  108. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/crewai/tool.py +0 -0
  109. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/google.py +0 -0
  110. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/google_adk/__init__.py +0 -0
  111. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/google_adk/agent.py +0 -0
  112. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/google_adk/tool.py +0 -0
  113. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/langchain/__init__.py +0 -0
  114. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/langchain/agent.py +0 -0
  115. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/langchain/callbacks.py +0 -0
  116. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/langchain/chain.py +0 -0
  117. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/langchain/graph.py +0 -0
  118. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/langchain/modern.py +0 -0
  119. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/langchain/tool.py +0 -0
  120. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/langfuse.py +0 -0
  121. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/litellm.py +0 -0
  122. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/openai.py +0 -0
  123. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/pydantic_ai.py +0 -0
  124. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/vercel_ai.py +0 -0
  125. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/layout_migration.py +0 -0
  126. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/policy_loader.py +0 -0
  127. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/secrets/__init__.py +0 -0
  128. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/secrets/reference.py +0 -0
  129. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/secrets/resolver.py +0 -0
  130. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/secrets/secretstr.py +0 -0
  131. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/spool/__init__.py +0 -0
  132. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/spool/_compress.py +0 -0
  133. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/spool/_constants.py +0 -0
  134. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/spool/_crc32c.py +0 -0
  135. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/spool/_crypto.py +0 -0
  136. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/spool/_frame.py +0 -0
  137. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/spool/_keyring.py +0 -0
  138. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/spool/_metrics.py +0 -0
  139. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/spool/_spool.py +0 -0
  140. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/spool/_state.py +0 -0
  141. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/spool/_uploader.py +0 -0
  142. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/spool/cz-audit-v1.dict +0 -0
  143. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/tamper.py +0 -0
  144. {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/tracecontext.py +0 -0
  145. {controlzero-1.13.8 → controlzero-1.13.9}/examples/hello_world.py +0 -0
  146. {controlzero-1.13.8 → controlzero-1.13.9}/ruff.toml +0 -0
  147. {controlzero-1.13.8 → controlzero-1.13.9}/tests/_fixtures/antigravity-envelope-expected.json +0 -0
  148. {controlzero-1.13.8 → controlzero-1.13.9}/tests/_fixtures/jcs_args_hash_vectors.json +0 -0
  149. {controlzero-1.13.8 → controlzero-1.13.9}/tests/conftest.py +0 -0
  150. {controlzero-1.13.8 → controlzero-1.13.9}/tests/fixtures/adapter-contracts/antigravity.json +0 -0
  151. {controlzero-1.13.8 → controlzero-1.13.9}/tests/fixtures/adapter-contracts/claude_code.json +0 -0
  152. {controlzero-1.13.8 → controlzero-1.13.9}/tests/fixtures/adapter-contracts/codex_cli.json +0 -0
  153. {controlzero-1.13.8 → controlzero-1.13.9}/tests/fixtures/adapter-contracts/cursor.json +0 -0
  154. {controlzero-1.13.8 → controlzero-1.13.9}/tests/fixtures/adapter-contracts/gemini_cli.json +0 -0
  155. {controlzero-1.13.8 → controlzero-1.13.9}/tests/integrations/__init__.py +0 -0
  156. {controlzero-1.13.8 → controlzero-1.13.9}/tests/integrations/test_google.py +0 -0
  157. {controlzero-1.13.8 → controlzero-1.13.9}/tests/parity/action_aliases.json +0 -0
  158. {controlzero-1.13.8 → controlzero-1.13.9}/tests/spool/__init__.py +0 -0
  159. {controlzero-1.13.8 → controlzero-1.13.9}/tests/spool/conftest.py +0 -0
  160. {controlzero-1.13.8 → controlzero-1.13.9}/tests/spool/test_spool_cli.py +0 -0
  161. {controlzero-1.13.8 → controlzero-1.13.9}/tests/spool/test_spool_concurrency.py +0 -0
  162. {controlzero-1.13.8 → controlzero-1.13.9}/tests/spool/test_spool_conformance.py +0 -0
  163. {controlzero-1.13.8 → controlzero-1.13.9}/tests/spool/test_spool_core.py +0 -0
  164. {controlzero-1.13.8 → controlzero-1.13.9}/tests/spool/test_spool_crash.py +0 -0
  165. {controlzero-1.13.8 → controlzero-1.13.9}/tests/spool/test_spool_diskfull.py +0 -0
  166. {controlzero-1.13.8 → controlzero-1.13.9}/tests/spool/test_spool_durable_default_tamper.py +0 -0
  167. {controlzero-1.13.8 → controlzero-1.13.9}/tests/spool/test_spool_keychain_dek.py +0 -0
  168. {controlzero-1.13.8 → controlzero-1.13.9}/tests/spool/test_spool_sink_wiring.py +0 -0
  169. {controlzero-1.13.8 → controlzero-1.13.9}/tests/spool/test_spool_transcript_localack.py +0 -0
  170. {controlzero-1.13.8 → controlzero-1.13.9}/tests/spool/test_spool_uploader.py +0 -0
  171. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_action_aliases.py +0 -0
  172. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_action_canonicalization.py +0 -0
  173. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_action_validator_t86.py +0 -0
  174. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_adapter_contracts.py +0 -0
  175. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_adapter_interface.py +0 -0
  176. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_agent_name_env.py +0 -0
  177. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_agy_docs_no_exit2_1373.py +0 -0
  178. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_antigravity_adapter.py +0 -0
  179. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_antigravity_envelope_parity.py +0 -0
  180. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_antigravity_ga_blockers_1248.py +0 -0
  181. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_antigravity_hook_check.py +0 -0
  182. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_antigravity_install.py +0 -0
  183. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_antigravity_posttooluse_observe_58.py +0 -0
  184. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_antigravity_tool_vocab_1303.py +0 -0
  185. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_api_key_mask.py +0 -0
  186. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_api_url_config.py +0 -0
  187. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_audit_project_id_attribution_pr2.py +0 -0
  188. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_audit_remote.py +0 -0
  189. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_audit_remote_io_capture.py +0 -0
  190. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_audit_remote_sdk_version.py +0 -0
  191. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_audit_sink_isolation.py +0 -0
  192. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_bundle_cache_freshness_1303.py +0 -0
  193. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_bundle_parser.py +0 -0
  194. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_bundle_translate.py +0 -0
  195. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_canonical_phase1a.py +0 -0
  196. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_capability_matrix_doc.py +0 -0
  197. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_cli_carve_out.py +0 -0
  198. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_cli_debug_bundle.py +0 -0
  199. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_cli_extractor_integration.py +0 -0
  200. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_cli_hook.py +0 -0
  201. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_cli_hook_entry.py +0 -0
  202. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_cli_hosted_refresh.py +0 -0
  203. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_cli_init.py +0 -0
  204. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_cli_init_templates.py +0 -0
  205. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_cli_tail.py +0 -0
  206. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_cli_test.py +0 -0
  207. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_cli_validate.py +0 -0
  208. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_coding_agent_hooks.py +0 -0
  209. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_condition_operators_1495.py +0 -0
  210. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_conditions.py +0 -0
  211. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_config_format_parity_1303.py +0 -0
  212. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_conformance.py +0 -0
  213. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_console.py +0 -0
  214. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_credential_hook.py +0 -0
  215. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_cross_adapter_shell_enforcement.py +0 -0
  216. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_cursor_adapter.py +0 -0
  217. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_dashboard_shell_template_1904.py +0 -0
  218. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_default_action.py +0 -0
  219. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_deny_rule_reconciliation_1011.py +0 -0
  220. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_deny_wins_default_catchall_1620.py +0 -0
  221. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_device.py +0 -0
  222. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_dlp_mask.py +0 -0
  223. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_dlp_mask_hosts_1938.py +0 -0
  224. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_dlp_origin_contract.py +0 -0
  225. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_dlp_scan_coverage_1927_1931.py +0 -0
  226. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_dlp_scanner.py +0 -0
  227. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_doctor.py +0 -0
  228. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_engine_version_consistency.py +0 -0
  229. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_enrollment.py +0 -0
  230. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_env_dump_438.py +0 -0
  231. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_epic_1247_bryan_acceptance.py +0 -0
  232. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_error_codes.py +0 -0
  233. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_errors_e_codes.py +0 -0
  234. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_fail_closed_eval.py +0 -0
  235. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_failopen_1303.py +0 -0
  236. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_git_target_1734.py +0 -0
  237. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_glob_matching.py +0 -0
  238. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hitl_5d_email_install.py +0 -0
  239. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hitl_6a_cli_flag.py +0 -0
  240. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hitl_6a_exceptions.py +0 -0
  241. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hitl_6a_get_secret_hitl.py +0 -0
  242. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hitl_6a_mock_backend.py +0 -0
  243. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hitl_6a_pending_approval.py +0 -0
  244. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hitl_6a_request_approval.py +0 -0
  245. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hitl_6a_secret_leak_guard.py +0 -0
  246. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hitl_6a_wait.py +0 -0
  247. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hitl_conformance.py +0 -0
  248. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hitl_phase2b_protocol.py +0 -0
  249. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hitl_reason_codes.py +0 -0
  250. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hitl_validator_keys.py +0 -0
  251. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hook_command_pinning_1705.py +0 -0
  252. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hook_extractors.py +0 -0
  253. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hook_integrity.py +0 -0
  254. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hook_mask_failclosed.py +0 -0
  255. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hook_warn_surfaces_reason_1903.py +0 -0
  256. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hosted_local_audit_1247.py +0 -0
  257. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hosted_local_shadow_warn_1265.py +0 -0
  258. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hosted_policy_e2e.py +0 -0
  259. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hosts_adapter.py +0 -0
  260. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hybrid_mode_strict.py +0 -0
  261. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hybrid_mode_warn.py +0 -0
  262. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_install_api_url.py +0 -0
  263. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_install_hook_command.py +0 -0
  264. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_install_hooks.py +0 -0
  265. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_io_capture_producer.py +0 -0
  266. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_kiro_cli_e2e.py +0 -0
  267. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_kiro_cli_hook_pin_1265.py +0 -0
  268. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_kiro_cli_v210_hooks.py +0 -0
  269. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_kiro_hook_templates.py +0 -0
  270. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_kiro_install.py +0 -0
  271. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_kiro_install_standardisation.py +0 -0
  272. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_layout_migration_t101.py +0 -0
  273. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_layout_parity_t102.py +0 -0
  274. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_local_mode_dict.py +0 -0
  275. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_local_mode_file_json.py +0 -0
  276. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_local_mode_file_yaml.py +0 -0
  277. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_localized_reason.py +0 -0
  278. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_log_fallback_stderr.py +0 -0
  279. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_log_options_ignored_hosted.py +0 -0
  280. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_log_rotation.py +0 -0
  281. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_migrate.py +0 -0
  282. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_min_sdk_version_gate.py +0 -0
  283. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_multi_client_per_project_175.py +0 -0
  284. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_no_policy_no_key.py +0 -0
  285. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_observe_mode_1247.py +0 -0
  286. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_package_rename_shim.py +0 -0
  287. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_part3_active_policy_count_1303.py +0 -0
  288. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_per_client_isolation_1011.py +0 -0
  289. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_policy_engine_version_phase1b.py +0 -0
  290. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_policy_freshness.py +0 -0
  291. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_policy_load_failclosed_f3.py +0 -0
  292. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_policy_settings.py +0 -0
  293. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_policy_source_audit.py +0 -0
  294. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_pr4_install_messaging.py +0 -0
  295. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_pr4_output.py +0 -0
  296. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_pr4_status.py +0 -0
  297. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_probe_audit_isolation.py +0 -0
  298. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_quarantine.py +0 -0
  299. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_reason_code.py +0 -0
  300. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_reason_code_hitl_grant_1596.py +0 -0
  301. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_refresh.py +0 -0
  302. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_secrets.py +0 -0
  303. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_secrets_reference_resolver.py +0 -0
  304. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_setup_wizard.py +0 -0
  305. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_shell_semantic_class.py +0 -0
  306. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_source_labels.py +0 -0
  307. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_sql_semantic_class.py +0 -0
  308. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_synthetic_policy_id_t79.py +0 -0
  309. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_t103_precedence.py +0 -0
  310. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_t104_cache_gc.py +0 -0
  311. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_t108_local_override_audit.py +0 -0
  312. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_t96_single_audit_log.py +0 -0
  313. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_t99_install_prefetch_bundle.py +0 -0
  314. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_tamper.py +0 -0
  315. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_tamper_behavior.py +0 -0
  316. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_tamper_hook.py +0 -0
  317. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_telemetry_consent.py +0 -0
  318. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_tls_ca_bundle.py +0 -0
  319. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_tracecontext.py +0 -0
  320. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_unsafe_int_boundary.py +0 -0
  321. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_update_cmd.py +0 -0
  322. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_upgrade_nudge.py +0 -0
  323. {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_version_aware_fail_closed.py +0 -0
  324. {controlzero-1.13.8 → controlzero-1.13.9}/tools/cz-kiro-adapter +0 -0
@@ -1,6 +1,50 @@
1
1
  # Changelog
2
2
 
3
- ## 1.13.8 (2026-07-27): warn decisions reach the agent + Kiro IDE adapter unblocked (P0)
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)
4
48
 
5
49
  ### Added
6
50
 
@@ -214,7 +258,7 @@
214
258
  "cannot return rewritten tool arguments". **That was wrong**, and correcting
215
259
  it is why this paragraph exists (#202 no-false-claims):
216
260
 
217
- | Host | Can rewrite tool input | Masking behaviour in 1.13.8 |
261
+ | Host | Can rewrite tool input | Masking behaviour in that release |
218
262
  | --- | --- | --- |
219
263
  | Claude Code | Yes -- `hookSpecificOutput.updatedInput` | **Masks and proceeds** |
220
264
  | Gemini CLI | Yes -- `hookSpecificOutput.tool_input` (BeforeTool) | **Masks and proceeds** |
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: controlzero
3
- Version: 1.13.8
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.8"
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": (
@@ -0,0 +1,245 @@
1
+ """Agent-actionable denial text for a DLP finding: TYPE + LOCATION, never VALUE.
2
+
3
+ Why this module exists
4
+ ----------------------
5
+ DLP can MASK a secret out of tool arguments, but only on hosts whose hook
6
+ protocol accepts REWRITTEN tool input. Today that is Claude Code and Gemini CLI
7
+ only (``CoverageDecl.can_rewrite_tool_input``); every other host -- Codex CLI,
8
+ Cursor IDE, Cursor CLI, Kiro CLI, Kiro IDE, Antigravity, and the unknown-host
9
+ fallback -- escalates a mask-rule match to a hard DENY.
10
+
11
+ That deny used to say, in full:
12
+
13
+ DLP mask matched but this host cannot rewrite arguments
14
+
15
+ which is true, actionable for nobody, and addressed to the wrong audience. The
16
+ model that issued the call reads it, cannot tell WHICH argument tripped or WHAT
17
+ it tripped on, and typically retries the identical call -- so the user's task
18
+ dead-ends instead of completing.
19
+
20
+ An **agent-actionable denial** closes that gap with no vendor change and no new
21
+ config: the deny reason itself becomes an instruction precise enough that the
22
+ model re-issues the SAME call with the sensitive value removed. One extra
23
+ round-trip instead of a dead end. It works on every non-rewriting host because
24
+ every one of them already routes ``decision.reason`` to a MODEL-visible field:
25
+
26
+ =============== ==========================================================
27
+ host model-visible field carrying ``decision.reason``
28
+ =============== ==========================================================
29
+ codex_cli ``hookSpecificOutput.permissionDecisionReason`` (+ stderr)
30
+ cursor_ide ``agent_message``
31
+ cursor_cli ``agent_message``
32
+ kiro_cli envelope ``reason`` + stderr (exit 2 sends stderr to the model)
33
+ kiro_ide envelope ``reason`` (v1 wraps hook output in ``<HOOK_INSTRUCTION>``)
34
+ antigravity envelope ``reason``
35
+ unknown envelope ``reason``
36
+ =============== ==========================================================
37
+
38
+ So NO adapter needed rewiring for this. The only thing that was missing was
39
+ text worth putting in that channel.
40
+
41
+ THE HARD CONSTRAINT
42
+ -------------------
43
+ **The message must NEVER contain the detected value.** Writing "found AWS key
44
+ AKIA... at offset 34" would echo the secret straight into the model's context
45
+ window -- which is the exact disclosure DLP exists to prevent, and a breach of
46
+ the #1881 "PII never in cleartext" mandate. A deny that leaks the secret to the
47
+ model is strictly worse than the uninformative deny it replaced.
48
+
49
+ Findings are therefore described by TYPE (the rule's name + category, which are
50
+ properties of the RULE definition, never of the matched bytes) and by LOCATION
51
+ (the argument path). Nothing quotes, prefixes, suffixes, hashes-with-a-
52
+ recoverable-prefix, or partially redacts the value; the value never enters this
53
+ module's output by CONSTRUCTION, not by filtering -- there is no code path here
54
+ that reads ``DLPMatch.matched_text``.
55
+
56
+ The one value-derived input is an argument PATH SEGMENT: a dict KEY is attacker/
57
+ model-controlled and could itself BE the secret (``{"AKIA...": "v"}``). That is
58
+ the single echo route, and :func:`_safe_segment` closes it by scanning every
59
+ segment and replacing any that matches a DLP rule with a fixed placeholder. It
60
+ errs toward redacting (an all-caps key like ``ACCESSKEYID`` trips the SWIFT/BIC
61
+ builtin and is redacted); losing a little location precision is the cheap side
62
+ of that trade.
63
+ """
64
+
65
+ from __future__ import annotations
66
+
67
+ from dataclasses import dataclass
68
+ from typing import Any, Optional
69
+
70
+ from controlzero._internal._messages import system_message
71
+
72
+ #: Placeholder for an argument-path segment that is ITSELF sensitive. A dict key
73
+ #: is model-controlled, so ``{"AKIA...": "v"}`` would otherwise route the secret
74
+ #: into the location clause. Value-free and fixed, exactly like the mask
75
+ #: placeholder, so it can never carry a fragment of the original bytes.
76
+ REDACTED_SEGMENT = "<redacted-key>"
77
+
78
+ #: Path segment for a value that is not reachable by a name (a bare scalar
79
+ #: passed as the whole argument object, or a leaf whose parents are all lists).
80
+ ROOT_SEGMENT = "<tool arguments>"
81
+
82
+ #: Cap on how many findings are enumerated. A pathological call can match
83
+ #: hundreds of spans; an unbounded list would bury the instruction it exists to
84
+ #: deliver and burn the model's context. The remainder is reported as a count.
85
+ MAX_LISTED_FINDINGS = 5
86
+
87
+ #: Cap on a rendered argument path. Deeply nested payloads produce long paths
88
+ #: that add no actionable precision past a point.
89
+ MAX_PATH_CHARS = 120
90
+
91
+
92
+ @dataclass(frozen=True)
93
+ class ArgFinding:
94
+ """One sensitive span, described by WHERE it is and WHAT KIND it is.
95
+
96
+ Deliberately carries no field that could hold the matched bytes. Adding one
97
+ would defeat the whole point of this module, so the dataclass shape is the
98
+ enforcement: there is nowhere for a value to live.
99
+ """
100
+
101
+ arg_path: str
102
+ rule_name: str
103
+ category: str
104
+
105
+
106
+ def _safe_segment(scanner: Any, segment: str) -> str:
107
+ """Return ``segment`` unless the segment TEXT is itself sensitive.
108
+
109
+ A dict key comes from the model's own tool call, so it can be the secret
110
+ (``{"ghp_...": "note"}``). Scanning the segment and substituting a fixed
111
+ placeholder is what stops the location clause from becoming the leak.
112
+ """
113
+ if not segment:
114
+ return ROOT_SEGMENT
115
+ try:
116
+ if scanner.scan(segment):
117
+ return REDACTED_SEGMENT
118
+ except Exception: # pragma: no cover - a scanner fault must not leak
119
+ # Fail toward redaction: if we cannot prove the segment is clean, do not
120
+ # print it. This branch mirrors the module-wide asymmetry -- an
121
+ # imprecise location costs a little usefulness, an echoed secret costs
122
+ # everything.
123
+ return REDACTED_SEGMENT
124
+ return segment
125
+
126
+
127
+ def _join(parent: str, child: str) -> str:
128
+ return f"{parent}.{child}" if parent else child
129
+
130
+
131
+ def _walk(
132
+ scanner: Any,
133
+ obj: Any,
134
+ path: str,
135
+ actions: frozenset,
136
+ out: list,
137
+ seen: set,
138
+ ) -> None:
139
+ """Collect ``ArgFinding`` per LEAF so each finding has a real location.
140
+
141
+ Per-leaf, not over the flattened blob ``extract_text_from_args`` produces:
142
+ the flat text's offsets do not map back to an argument, and "somewhere in
143
+ your arguments" is not a location the model can act on.
144
+ """
145
+ if isinstance(obj, dict):
146
+ for key, value in obj.items():
147
+ _walk(scanner, value, _join(path, _safe_segment(scanner, str(key))), actions, out, seen)
148
+ return
149
+ if isinstance(obj, (list, tuple)):
150
+ for index, item in enumerate(obj):
151
+ _walk(scanner, item, f"{path}[{index}]" if path else f"[{index}]", actions, out, seen)
152
+ return
153
+ if obj is None:
154
+ return
155
+ if isinstance(obj, str):
156
+ text = obj
157
+ elif isinstance(obj, (int, float, bool)):
158
+ # Scanned as text for the same reason the masker stringifies scalars:
159
+ # a Korean card / RRN / account number frequently arrives as a JSON
160
+ # NUMBER, and skipping it here would report no location for a finding
161
+ # the engine already decided to block on.
162
+ text = str(obj)
163
+ else:
164
+ return
165
+
166
+ location = path or ROOT_SEGMENT
167
+ if len(location) > MAX_PATH_CHARS:
168
+ location = location[:MAX_PATH_CHARS] + "..."
169
+
170
+ for match in scanner.scan(text):
171
+ if match.action not in actions:
172
+ continue
173
+ # One line per (location, rule) pair. Ten hits of the same rule in one
174
+ # argument is one instruction, not ten.
175
+ key = (location, match.rule_id)
176
+ if key in seen:
177
+ continue
178
+ seen.add(key)
179
+ out.append(
180
+ ArgFinding(
181
+ arg_path=location,
182
+ rule_name=match.rule_name,
183
+ category=match.category,
184
+ )
185
+ )
186
+
187
+
188
+ def locate_findings(
189
+ scanner: Any,
190
+ args: Any,
191
+ *,
192
+ actions: frozenset = frozenset({"mask"}),
193
+ ) -> list:
194
+ """Describe every ``actions`` match in ``args`` by location + type.
195
+
196
+ ``actions`` is a NAMED POSITIVE SET, not a negation of something else, per
197
+ the CLAUDE.md controls mandate: a future DLP action lands OUTSIDE it and is
198
+ simply not described, rather than being silently swept in by an ``!=``.
199
+ """
200
+ out: list = []
201
+ seen: set = set()
202
+ _walk(scanner, args, "", actions, out, seen)
203
+ return out
204
+
205
+
206
+ def _describe(findings: list) -> str:
207
+ """Render the findings clause. TYPE + LOCATION only -- never the value."""
208
+ clauses = [
209
+ f'argument "{f.arg_path}" contains a value matching the '
210
+ f'"{f.rule_name}" {f.category} detector'
211
+ for f in findings[:MAX_LISTED_FINDINGS]
212
+ ]
213
+ overflow = len(findings) - len(clauses)
214
+ if overflow > 0:
215
+ clauses.append(f"and {overflow} further match(es) in other arguments")
216
+ return "; ".join(clauses)
217
+
218
+
219
+ def agent_remediation_message(
220
+ scanner: Any,
221
+ args: Any,
222
+ *,
223
+ actions: frozenset = frozenset({"mask"}),
224
+ locale: Optional[str] = None,
225
+ ) -> str:
226
+ """The deny reason handed to the MODEL on a non-rewriting host.
227
+
228
+ THE single helper this module exists to provide. Returns text that names
229
+ what was found and where, and tells the agent what to do next -- and that,
230
+ by construction, cannot contain the detected value.
231
+
232
+ Falls back to the location-free base message when nothing could be located
233
+ (e.g. a match that only exists across the boundary between two concatenated
234
+ arguments, so no single leaf carries it). Less useful, still never wrong,
235
+ and never a crash on the deny path.
236
+ """
237
+ findings = locate_findings(scanner, args, actions=actions)
238
+ key = "DLP_MASK_UNSUPPORTED:agent" if findings else "DLP_MASK_UNSUPPORTED:base"
239
+ message = system_message(key, locale, findings=_describe(findings))
240
+ # system_message returns None only for an unknown key. Both keys above are
241
+ # in the pack and the pack is pinned by tests, but the deny path must not
242
+ # raise, so a missing entry degrades to the legacy text rather than a
243
+ # traceback that would fail the hook open on hosts that read a crash as a
244
+ # pass.
245
+ return message or "DLP mask matched but this host cannot rewrite arguments"
@@ -27,6 +27,7 @@ from controlzero._internal._messages import (
27
27
  resolve_locale,
28
28
  system_message,
29
29
  )
30
+ from controlzero._internal.dlp_remediation import agent_remediation_message
30
31
  from controlzero._internal.dlp_scanner import (
31
32
  DLPScanner,
32
33
  extract_text_from_args,
@@ -1386,7 +1387,30 @@ class PolicyEvaluator:
1386
1387
  "host_cannot_rewrite_arguments",
1387
1388
  ),
1388
1389
  policy_id=decision.policy_id,
1389
- reason="DLP mask matched but this host cannot rewrite arguments",
1390
+ # AGENT-ACTIONABLE DENIAL. This deny's reason is routed to a
1391
+ # MODEL-visible field by every non-rewriting host (Codex's
1392
+ # permissionDecisionReason, Cursor's agent_message, the
1393
+ # Kiro / Antigravity / unknown envelope `reason`, plus
1394
+ # stderr), so it is the one channel that can get the user's
1395
+ # task unblocked without a vendor change: name the argument
1396
+ # and the detector precisely enough that the model re-issues
1397
+ # the SAME call with the secret removed.
1398
+ #
1399
+ # The legacy text ("DLP mask matched but this host cannot
1400
+ # rewrite arguments") was true and useless -- the model
1401
+ # could not tell WHICH argument tripped or WHAT on, so it
1402
+ # retried the identical call and the task dead-ended.
1403
+ #
1404
+ # TYPE + LOCATION only. The message can never carry the
1405
+ # detected value: echoing it here would put the secret in
1406
+ # the model's context window, which is the precise
1407
+ # disclosure this control exists to prevent. Enforced by
1408
+ # construction in dlp_remediation (nothing there reads
1409
+ # matched_text) and pinned by
1410
+ # tests/test_dlp_agent_remediation_1938.py.
1411
+ reason=agent_remediation_message(
1412
+ self._dlp_scanner, args, locale=self._locale
1413
+ ),
1390
1414
  reason_code=REASON_CODE_DLP_BLOCKED,
1391
1415
  evaluated_rules=decision.evaluated_rules,
1392
1416
  dlp_findings=self._dlp_scanner.get_findings_for_audit(mask_matches),
@@ -149,7 +149,30 @@ ADAPTER_CONTRACTS: dict[str, AdapterContract] = {
149
149
  match_all_tokens=("*",),
150
150
  # preToolUse matchers are canonical tool names that MUST self-match.
151
151
  installer_matchers=("execute_bash", "fs_write", "fs_read", "use_aws"),
152
- tested_agent_versions=">=1.23.0",
152
+ # This contract describes the **Kiro CLI** surface only (note
153
+ # ``version_command`` below is ``kiro-cli --version``); KiroIDEAdapter
154
+ # is versioned on a different scheme entirely and is NOT reconciled
155
+ # here -- see hosts/kiro.py.
156
+ #
157
+ # Was ">=1.23.0", a stale Amazon-Q-era claim: kiro-cli reported 1.x
158
+ # when this fixture was written (the fixture's own ``version_detect``
159
+ # note still said "kiro-cli is 1.x (rebranded Amazon Q), NOT 2.8.x").
160
+ # kiro-cli has since renumbered to 2.x -- the binary installed for
161
+ # verification reports ``kiro-cli 2.14.2``. The old range only kept
162
+ # producing a "tested" label at line ~483 by semver accident
163
+ # (2.14.2 >= 1.23.0), not because 1.23.0 means anything today.
164
+ #
165
+ # Floor 2.1.0: first build where preToolUse hooks fire at all
166
+ # (kirodotdev/Kiro#7697). Ceiling <3.0.0: bounds the 2.x binary major.
167
+ #
168
+ # CAVEAT, stated because the version range CANNOT enforce it: Kiro
169
+ # "CLI v3" is not a 3.x release -- it ships INSIDE the 2.x binary
170
+ # behind ``kiro-cli --v3`` (kiro.dev/docs/cli/v3.md: "V3 runs
171
+ # alongside your existing 2.x install"), and it changes the hook
172
+ # contract to PascalCase standalone files. So a --v3 run still reports
173
+ # 2.14.2 and still lands inside this range. Surface separation is done
174
+ # by payload signature in hosts/kiro.py, never by this string.
175
+ tested_agent_versions=">=2.1.0,<3.0.0",
153
176
  sdk_min_core_version="1.9.0",
154
177
  block_mechanism="exit_code",
155
178
  version_command=("kiro-cli", "--version"),
@@ -45,6 +45,7 @@ from __future__ import annotations
45
45
  from collections.abc import Mapping
46
46
 
47
47
  from controlzero.cli.hosts.base import CoverageDecl, CZDecision, HostAdapter
48
+ from controlzero.cli.hosts.kiro import is_kiro_agent_payload
48
49
 
49
50
  # CONTROLZERO_CLIENT values that pin THIS adapter. A non-empty pin that is NOT
50
51
  # one of these means a different host was pinned -> this adapter must DEFER
@@ -131,6 +132,26 @@ class ClaudeCodeAdapter(HostAdapter):
131
132
  if env.get(hint):
132
133
  return True
133
134
 
135
+ # Disambiguation #2: Kiro IDE 1.0+ / Kiro CLI v3 run the shared Kiro
136
+ # Agent Server harness, which emits PascalCase ``hook_event_name``
137
+ # ("PreToolUse") on stdin -- the same names Claude Code uses. Claude
138
+ # sits FIRST in the REGISTRY, so without this defer a Kiro
139
+ # agent-harness call is claimed here. The block would still land (both
140
+ # hosts exit 2), which is what makes it dangerous: the surface looks
141
+ # healthy while the audit SOURCE says claude_code and the per-agent
142
+ # config resolves under ~/.controlzero/integrations/claude_code/ --
143
+ # the WRONG project + DLP profile, silently.
144
+ #
145
+ # This runs AFTER the Anthropic env-hint check above, deliberately: an
146
+ # ambient CLAUDECODE is a positive Claude signal and must keep winning
147
+ # (Windows detection leans on it). ``is_kiro_agent_payload`` only
148
+ # claims a shared PreToolUse/PostToolUse name when ``transcript_path``
149
+ # is absent, and Claude Code always sends it -- so a genuine Claude
150
+ # payload is never deferred. See kiro.py for the verified payload
151
+ # shape and its vendor-source citation.
152
+ if is_kiro_agent_payload(payload):
153
+ return False
154
+
134
155
  # Stdin payload signature: PreToolUse always carries
135
156
  # ``hook_event_name`` (post-2024-10 schema) OR a
136
157
  # ``session_id`` + ``transcript_path`` + ``tool_input``