plugin-scanner 2.0.61__tar.gz → 2.0.63__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 (317) hide show
  1. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/PKG-INFO +1 -1
  2. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/pyproject.toml +1 -1
  3. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/pyproject.toml.bak +1 -1
  4. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/adapters/claude_code.py +45 -4
  5. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/cli/commands.py +264 -37
  6. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/version.py +1 -1
  7. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_guard_claude_adapter.py +65 -3
  8. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_guard_runtime.py +496 -42
  9. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_guard_surface_server.py +9 -15
  10. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/uv.lock +22 -22
  11. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/.clusterfuzzlite/Dockerfile +0 -0
  12. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/.clusterfuzzlite/build.sh +0 -0
  13. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/.clusterfuzzlite/project.yaml +0 -0
  14. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/.clusterfuzzlite/requirements-atheris.txt +0 -0
  15. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/.dockerignore +0 -0
  16. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/.github/CODEOWNERS +0 -0
  17. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/.github/ISSUE_TEMPLATE/bug-report.yml +0 -0
  18. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  19. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
  20. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/.github/dependabot.yml +0 -0
  21. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/.github/workflows/ci.yml +0 -0
  22. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/.github/workflows/codeql.yml +0 -0
  23. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/.github/workflows/dependabot-uv-lock.yml +0 -0
  24. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/.github/workflows/fuzz.yml +0 -0
  25. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/.github/workflows/harness-smoke.yml +0 -0
  26. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/.github/workflows/publish.yml +0 -0
  27. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/.github/workflows/scorecard.yml +0 -0
  28. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/.gitignore +0 -0
  29. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/.pre-commit-hooks.yaml +0 -0
  30. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/CONTRIBUTING.md +0 -0
  31. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/Dockerfile +0 -0
  32. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/LICENSE +0 -0
  33. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/README.md +0 -0
  34. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/SECURITY.md +0 -0
  35. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/dashboard/index.html +0 -0
  36. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/dashboard/package.json +0 -0
  37. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/dashboard/pnpm-lock.yaml +0 -0
  38. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/dashboard/public/brand/Logo_Whole.png +0 -0
  39. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/dashboard/src/app.tsx +0 -0
  40. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/dashboard/src/approval-center-layout.tsx +0 -0
  41. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/dashboard/src/approval-center-primitives.tsx +0 -0
  42. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/dashboard/src/approval-center-utils.ts +0 -0
  43. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/dashboard/src/fleet-workspace.tsx +0 -0
  44. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/dashboard/src/guard-api.ts +0 -0
  45. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/dashboard/src/guard-demo.ts +0 -0
  46. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/dashboard/src/guard-types.ts +0 -0
  47. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/dashboard/src/main.tsx +0 -0
  48. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/dashboard/src/receipts-workspace.tsx +0 -0
  49. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/dashboard/src/runtime-overview.tsx +0 -0
  50. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/dashboard/src/styles.css +0 -0
  51. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/dashboard/src/vite-env.d.ts +0 -0
  52. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/dashboard/tsconfig.json +0 -0
  53. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/dashboard/vite.config.ts +0 -0
  54. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/docker-requirements.txt +0 -0
  55. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/docs/guard/approval-audit.md +0 -0
  56. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/docs/guard/architecture.md +0 -0
  57. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/docs/guard/get-started.md +0 -0
  58. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/docs/guard/harness-support.md +0 -0
  59. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/docs/guard/local-vs-cloud.md +0 -0
  60. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/docs/guard/testing-matrix.md +0 -0
  61. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/docs/trust/mcp-trust-draft.md +0 -0
  62. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/docs/trust/plugin-trust-draft.md +0 -0
  63. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/docs/trust/skill-trust-local.md +0 -0
  64. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/fuzzers/manifest_fuzzer.py +0 -0
  65. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/requirements.txt +0 -0
  66. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/schemas/plugin-quality.v1.json +0 -0
  67. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/schemas/scan-result.v1.json +0 -0
  68. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/schemas/verify-result.v1.json +0 -0
  69. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/__init__.py +0 -0
  70. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/action_runner.py +0 -0
  71. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/argparse_utils.py +0 -0
  72. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/checks/__init__.py +0 -0
  73. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/checks/best_practices.py +0 -0
  74. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/checks/claude.py +0 -0
  75. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/checks/code_quality.py +0 -0
  76. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/checks/ecosystem_common.py +0 -0
  77. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/checks/gemini.py +0 -0
  78. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/checks/manifest.py +0 -0
  79. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/checks/manifest_support.py +0 -0
  80. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/checks/marketplace.py +0 -0
  81. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/checks/mcp_security.py +0 -0
  82. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/checks/opencode.py +0 -0
  83. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/checks/operational_security.py +0 -0
  84. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/checks/security.py +0 -0
  85. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/checks/skill_security.py +0 -0
  86. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/cli.py +0 -0
  87. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/cli_ui.py +0 -0
  88. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/config.py +0 -0
  89. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/ecosystems/__init__.py +0 -0
  90. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/ecosystems/base.py +0 -0
  91. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/ecosystems/claude.py +0 -0
  92. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/ecosystems/codex.py +0 -0
  93. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/ecosystems/detect.py +0 -0
  94. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/ecosystems/gemini.py +0 -0
  95. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/ecosystems/opencode.py +0 -0
  96. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/ecosystems/registry.py +0 -0
  97. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/ecosystems/types.py +0 -0
  98. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/github_reporting.py +0 -0
  99. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/__init__.py +0 -0
  100. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/adapters/__init__.py +0 -0
  101. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/adapters/antigravity.py +0 -0
  102. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/adapters/base.py +0 -0
  103. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/adapters/codex.py +0 -0
  104. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/adapters/copilot.py +0 -0
  105. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/adapters/cursor.py +0 -0
  106. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/adapters/gemini.py +0 -0
  107. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/adapters/hermes.py +0 -0
  108. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/adapters/mcp_servers.py +0 -0
  109. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/adapters/opencode.py +0 -0
  110. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/adapters/opencode_artifacts.py +0 -0
  111. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/approvals.py +0 -0
  112. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/bridge/__init__.py +0 -0
  113. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/capabilities.py +0 -0
  114. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/cli/__init__.py +0 -0
  115. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/cli/approval_commands.py +0 -0
  116. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/cli/bootstrap.py +0 -0
  117. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/cli/connect_flow.py +0 -0
  118. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/cli/install_commands.py +0 -0
  119. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/cli/product.py +0 -0
  120. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/cli/prompt.py +0 -0
  121. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/cli/render.py +0 -0
  122. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/cli/update_commands.py +0 -0
  123. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/codex_config.py +0 -0
  124. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/config.py +0 -0
  125. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/consumer/__init__.py +0 -0
  126. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/consumer/service.py +0 -0
  127. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/daemon/__init__.py +0 -0
  128. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/daemon/client.py +0 -0
  129. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/daemon/manager.py +0 -0
  130. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/daemon/server.py +0 -0
  131. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/daemon/static/assets/guard-dashboard.js +0 -0
  132. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/daemon/static/assets/index.css +0 -0
  133. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Whole.png +0 -0
  134. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/daemon/static/index.html +0 -0
  135. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/incident.py +0 -0
  136. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/launcher.py +0 -0
  137. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/mcp_tool_calls.py +0 -0
  138. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/models.py +0 -0
  139. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/policy/__init__.py +0 -0
  140. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/policy/engine.py +0 -0
  141. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/protect.py +0 -0
  142. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/proxy/__init__.py +0 -0
  143. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/proxy/remote.py +0 -0
  144. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/proxy/runtime_mcp.py +0 -0
  145. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/proxy/stdio.py +0 -0
  146. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/receipts/__init__.py +0 -0
  147. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/receipts/manager.py +0 -0
  148. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/risk.py +0 -0
  149. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/runtime/__init__.py +0 -0
  150. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/runtime/runner.py +0 -0
  151. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/runtime/secret_file_requests.py +0 -0
  152. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/runtime/surface_server.py +0 -0
  153. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/schemas/__init__.py +0 -0
  154. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/schemas/consumer_mode.py +0 -0
  155. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/schemas/surface_server.py +0 -0
  156. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/shims.py +0 -0
  157. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/store.py +0 -0
  158. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/store_approvals.py +0 -0
  159. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/store_connect.py +0 -0
  160. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/guard/types.py +0 -0
  161. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/integrations/__init__.py +0 -0
  162. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/integrations/cisco_mcp_scanner.py +0 -0
  163. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/integrations/cisco_skill_scanner.py +0 -0
  164. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/lint_fixes.py +0 -0
  165. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/marketplace_support.py +0 -0
  166. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/models.py +0 -0
  167. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/path_support.py +0 -0
  168. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/policy.py +0 -0
  169. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/quality_artifact.py +0 -0
  170. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/repo_detect.py +0 -0
  171. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/reporting.py +0 -0
  172. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/rules/__init__.py +0 -0
  173. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/rules/registry.py +0 -0
  174. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/rules/specs.py +0 -0
  175. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/scanner.py +0 -0
  176. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/submission.py +0 -0
  177. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/suppressions.py +0 -0
  178. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/trust_domain_scoring.py +0 -0
  179. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/trust_helpers.py +0 -0
  180. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/trust_mcp_scoring.py +0 -0
  181. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/trust_models.py +0 -0
  182. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/trust_plugin_scoring.py +0 -0
  183. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/trust_scoring.py +0 -0
  184. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/trust_skill_scoring.py +0 -0
  185. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/trust_specs.py +0 -0
  186. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/src/codex_plugin_scanner/verification.py +0 -0
  187. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/__init__.py +0 -0
  188. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/conftest.py +0 -0
  189. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/__init__.py +0 -0
  190. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/bad-plugin/.codex-plugin/plugin.json +0 -0
  191. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/bad-plugin/.mcp.json +0 -0
  192. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/bad-plugin/secrets.js +0 -0
  193. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/claude-plugin-good/.claude-plugin/plugin.json +0 -0
  194. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/claude-plugin-good/LICENSE +0 -0
  195. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/claude-plugin-good/README.md +0 -0
  196. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/claude-plugin-good/SECURITY.md +0 -0
  197. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/claude-plugin-good/hooks/hooks.json +0 -0
  198. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/claude-plugin-good/skills/example/SKILL.md +0 -0
  199. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/code-quality-bad/evil.js +0 -0
  200. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/code-quality-bad/inject.js +0 -0
  201. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/gemini-extension-good/GEMINI.md +0 -0
  202. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/gemini-extension-good/LICENSE +0 -0
  203. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/gemini-extension-good/README.md +0 -0
  204. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/gemini-extension-good/SECURITY.md +0 -0
  205. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/gemini-extension-good/commands/hello.toml +0 -0
  206. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/gemini-extension-good/gemini-extension.json +0 -0
  207. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/good-plugin/.codex-plugin/plugin.json +0 -0
  208. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/good-plugin/.codexignore +0 -0
  209. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/good-plugin/LICENSE +0 -0
  210. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/good-plugin/README.md +0 -0
  211. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/good-plugin/SECURITY.md +0 -0
  212. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/good-plugin/assets/icon.svg +0 -0
  213. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/good-plugin/assets/logo.svg +0 -0
  214. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/good-plugin/assets/screenshot.svg +0 -0
  215. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/good-plugin/skills/example/SKILL.md +0 -0
  216. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/guard-codex-malicious-mcp/.codex/config.toml +0 -0
  217. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/hermes-plugin-evil/config.yaml +0 -0
  218. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/hermes-plugin-evil/mcp_servers.json +0 -0
  219. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/hermes-plugin-evil/skills/security/malicious/SKILL.md +0 -0
  220. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/SKILL.md +0 -0
  221. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/references/api-setup.md +0 -0
  222. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/scripts/deploy.sh +0 -0
  223. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/hermes-plugin-evil/skills/utils/benign/SKILL.md +0 -0
  224. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/malformed-json/.codex-plugin/plugin.json +0 -0
  225. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/malicious-skill-plugin/.codex-plugin/plugin.json +0 -0
  226. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/malicious-skill-plugin/.codexignore +0 -0
  227. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/malicious-skill-plugin/LICENSE +0 -0
  228. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/malicious-skill-plugin/README.md +0 -0
  229. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/malicious-skill-plugin/SECURITY.md +0 -0
  230. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/malicious-skill-plugin/skills/leaky-skill/SKILL.md +0 -0
  231. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/mcp-canary-server.py +0 -0
  232. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/minimal-plugin/.codex-plugin/plugin.json +0 -0
  233. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/missing-fields/.codex-plugin/plugin.json +0 -0
  234. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/mit-license/LICENSE +0 -0
  235. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/multi-ecosystem-repo/codex-plugin/.codex-plugin/plugin.json +0 -0
  236. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/multi-ecosystem-repo/codex-plugin/LICENSE +0 -0
  237. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/multi-ecosystem-repo/codex-plugin/README.md +0 -0
  238. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/multi-ecosystem-repo/codex-plugin/SECURITY.md +0 -0
  239. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/multi-ecosystem-repo/gemini-ext/README.md +0 -0
  240. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/multi-ecosystem-repo/gemini-ext/gemini-extension.json +0 -0
  241. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/multi-plugin-repo/.agents/plugins/marketplace.json +0 -0
  242. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codex-plugin/plugin.json +0 -0
  243. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codexignore +0 -0
  244. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/LICENSE +0 -0
  245. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/README.md +0 -0
  246. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/SECURITY.md +0 -0
  247. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/skills/example/SKILL.md +0 -0
  248. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/.codex-plugin/plugin.json +0 -0
  249. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/skills/example/SKILL.md +0 -0
  250. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/no-version/.codex-plugin/plugin.json +0 -0
  251. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/opencode-good/.opencode/commands/hello.md +0 -0
  252. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/opencode-good/.opencode/plugins/example.ts +0 -0
  253. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/opencode-good/LICENSE +0 -0
  254. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/opencode-good/README.md +0 -0
  255. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/opencode-good/SECURITY.md +0 -0
  256. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/opencode-good/opencode.jsonc +0 -0
  257. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/skills-missing-dir/.codex-plugin/plugin.json +0 -0
  258. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/skills-no-frontmatter/.codex-plugin/plugin.json +0 -0
  259. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/skills-no-frontmatter/skills/bad-skill/SKILL.md +0 -0
  260. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/with-marketplace/.codex-plugin/plugin.json +0 -0
  261. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/with-marketplace/marketplace-broken.json +0 -0
  262. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/fixtures/with-marketplace/marketplace.json +0 -0
  263. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test-trust-scoring.py +0 -0
  264. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test-trust-specs.py +0 -0
  265. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_action_runner.py +0 -0
  266. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_best_practices.py +0 -0
  267. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_cisco_install_surfaces.py +0 -0
  268. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_cli.py +0 -0
  269. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_code_quality.py +0 -0
  270. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_config.py +0 -0
  271. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_coverage_remaining.py +0 -0
  272. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_ecosystems.py +0 -0
  273. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_edge_cases.py +0 -0
  274. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_final_coverage.py +0 -0
  275. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_guard_approvals.py +0 -0
  276. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_guard_bootstrap.py +0 -0
  277. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_guard_capabilities.py +0 -0
  278. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_guard_cli.py +0 -0
  279. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_guard_codex_e2e.py +0 -0
  280. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_guard_codex_install.py +0 -0
  281. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_guard_codex_proxy.py +0 -0
  282. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_guard_config_paths.py +0 -0
  283. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_guard_connect_flow.py +0 -0
  284. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_guard_consumer_mode.py +0 -0
  285. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_guard_copilot_adapter.py +0 -0
  286. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_guard_copilot_proxy.py +0 -0
  287. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_guard_daemon_manager.py +0 -0
  288. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_guard_events.py +0 -0
  289. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_guard_launch_env.py +0 -0
  290. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_guard_opencode_proxy.py +0 -0
  291. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_guard_product_flow.py +0 -0
  292. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_guard_protect.py +0 -0
  293. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_guard_render.py +0 -0
  294. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_guard_risk.py +0 -0
  295. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_guard_store_migrations.py +0 -0
  296. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_guard_verdicts.py +0 -0
  297. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_hermes_adapter.py +0 -0
  298. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_integration.py +0 -0
  299. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_lint_fixes.py +0 -0
  300. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_live_cisco_smoke.py +0 -0
  301. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_manifest.py +0 -0
  302. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_marketplace.py +0 -0
  303. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_mcp_security.py +0 -0
  304. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_operational_security.py +0 -0
  305. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_policy.py +0 -0
  306. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_quality_artifact.py +0 -0
  307. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_rule_registry.py +0 -0
  308. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_scanner.py +0 -0
  309. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_schema_contracts.py +0 -0
  310. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_security.py +0 -0
  311. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_security_ops.py +0 -0
  312. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_skill_security.py +0 -0
  313. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_submission.py +0 -0
  314. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_trust_scoring.py +0 -0
  315. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_trust_specs.py +0 -0
  316. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_verification.py +0 -0
  317. {plugin_scanner-2.0.61 → plugin_scanner-2.0.63}/tests/test_versioning.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plugin-scanner
3
- Version: 2.0.61
3
+ Version: 2.0.63
4
4
  Summary: Lint, verify, and gate plugin ecosystems for maintainers, CI, and publish workflows.
5
5
  Project-URL: Homepage, https://github.com/hashgraph-online/ai-plugin-scanner
6
6
  Project-URL: Repository, https://github.com/hashgraph-online/ai-plugin-scanner
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "plugin-scanner"
7
- version = "2.0.61"
7
+ version = "2.0.63"
8
8
  description = "Lint, verify, and gate plugin ecosystems for maintainers, CI, and publish workflows."
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "hol-guard"
7
- version = "2.0.61"
7
+ version = "2.0.63"
8
8
  description = "Protect local AI harnesses with HOL Guard and run scanner checks for Codex, Claude, Cursor, Gemini, and OpenCode."
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -19,6 +19,7 @@ from ..shims import install_guard_shim, remove_guard_shim
19
19
  from .base import HarnessAdapter, HarnessContext, _ensure_path_within_root, _json_payload, _run_command_probe
20
20
 
21
21
  CLAUDE_GUARD_TOOL_MATCHER = "Bash|Read|Write|Edit|MultiEdit|WebFetch|WebSearch|mcp__.*"
22
+ CLAUDE_GUARD_POST_TOOL_MATCHER = f"{CLAUDE_GUARD_TOOL_MATCHER}|AskUserQuestion"
22
23
  CLAUDE_GUARD_NOTIFICATION_MATCHER = "permission_prompt"
23
24
  CLAUDE_GUARD_SESSION_START_MATCHERS = ("startup", "resume", "clear", "compact")
24
25
  CLAUDE_GUARD_TOOL_TIMEOUT_SECONDS = 30
@@ -44,7 +45,8 @@ def _shell_command(command: tuple[str, ...], *, windows: bool | None = None) ->
44
45
  def _sync_runtime_hook_groups(hooks: dict[str, object], hook_command: str) -> None:
45
46
  for key, matcher, timeout in (
46
47
  ("PreToolUse", CLAUDE_GUARD_TOOL_MATCHER, CLAUDE_GUARD_TOOL_TIMEOUT_SECONDS),
47
- ("PostToolUse", CLAUDE_GUARD_TOOL_MATCHER, CLAUDE_GUARD_TOOL_TIMEOUT_SECONDS),
48
+ ("PermissionRequest", CLAUDE_GUARD_TOOL_MATCHER, CLAUDE_GUARD_NOTIFICATION_TIMEOUT_SECONDS),
49
+ ("PostToolUse", CLAUDE_GUARD_POST_TOOL_MATCHER, CLAUDE_GUARD_TOOL_TIMEOUT_SECONDS),
48
50
  ("UserPromptSubmit", None, CLAUDE_GUARD_PROMPT_TIMEOUT_SECONDS),
49
51
  ("Notification", CLAUDE_GUARD_NOTIFICATION_MATCHER, CLAUDE_GUARD_NOTIFICATION_TIMEOUT_SECONDS),
50
52
  ("Stop", None, CLAUDE_GUARD_STOP_TIMEOUT_SECONDS),
@@ -486,6 +488,7 @@ class ClaudeCodeHarnessAdapter(HarnessAdapter):
486
488
  def _daemon_hook_command_parts(context: HarnessContext) -> tuple[str, ...]:
487
489
  fallback_daemon_url = load_guard_daemon_url(context.guard_home) or guard_daemon_url_for_home(context.guard_home)
488
490
  state_path = context.guard_home / "daemon-state.json"
491
+ fallback_command = ClaudeCodeHarnessAdapter._hook_command_parts(context)
489
492
  query: dict[str, str] = {"guard-home": str(context.guard_home)}
490
493
  if context.home_dir.resolve() != Path.home().resolve():
491
494
  query["home"] = str(context.home_dir)
@@ -496,9 +499,11 @@ class ClaudeCodeHarnessAdapter(HarnessAdapter):
496
499
  "void MARKER;"
497
500
  "const fs=require('fs');"
498
501
  "const http=require('http');"
502
+ "const cp=require('child_process');"
499
503
  "const {URL}=require('url');"
500
504
  f"const statePath={str(state_path)!r};"
501
505
  f"const fallbackUrl={fallback_daemon_url!r};"
506
+ f"const fallbackCommand={list(fallback_command)!r};"
502
507
  f"const query={urlencode(query)!r};"
503
508
  "function daemonUrl(){"
504
509
  "try{"
@@ -507,9 +512,42 @@ class ClaudeCodeHarnessAdapter(HarnessAdapter):
507
512
  "}catch(_error){}"
508
513
  "return fallbackUrl;"
509
514
  "}"
515
+ "function eventName(data){"
516
+ "try{const payload=JSON.parse(data||'{}');"
517
+ "return String(payload.hook_event_name||payload.event||'PreToolUse');}"
518
+ "catch(_error){return 'PreToolUse';}"
519
+ "}"
520
+ "function degraded(reason,data){"
521
+ "const event=eventName(data);"
522
+ "const message=`HOL Guard could not reach the local daemon (${reason}), so it is using Claude's native "
523
+ "approval prompt as a temporary safety fallback.`;"
524
+ "if(event==='UserPromptSubmit'){return '';}"
525
+ "if(event==='PreToolUse'){return JSON.stringify({systemMessage:'HOL Guard could not reach the local "
526
+ "daemon, so it cannot render the full HOL Guard approval flow.',hookSpecificOutput:{"
527
+ "hookEventName:'PreToolUse',permissionDecision:'ask',permissionDecisionReason:`${message} Keep this "
528
+ "action blocked unless you intentionally trust it. Restart Guard to restore the branded Allow once / "
529
+ "Allow during this session / Keep blocked flow.`}});}"
530
+ "return '{}';"
531
+ "}"
532
+ "function shouldSuppressOutput(data,responseBody){"
533
+ "if(eventName(data)!=='UserPromptSubmit')return false;"
534
+ "const trimmed=(responseBody||'').trim();"
535
+ "return trimmed===''||trimmed==='{}';"
536
+ "}"
537
+ "function runLocalFallback(reason,data){"
538
+ "try{"
539
+ "const result=cp.spawnSync(fallbackCommand[0],fallbackCommand.slice(1),{input:data,encoding:'utf8',"
540
+ "timeout:30000,env:process.env});"
541
+ "if(result.error)return degraded(`${reason}; fallback failed: ${result.error.message}`,data);"
542
+ "if(result.status===0){"
543
+ "if(shouldSuppressOutput(data,result.stdout)){process.exit(0);}"
544
+ "process.stdout.write(result.stdout&&result.stdout.trim()?result.stdout:'{}');"
545
+ "process.exit(0);}"
546
+ "return degraded(`${reason}; fallback exited ${result.status}`,data);"
547
+ "}catch(error){return degraded(`${reason}; fallback crashed: ${error.message}`,data);}"
548
+ "}"
510
549
  "function fail(reason){"
511
- "const message=`HOL Guard could not evaluate this action: ${reason}`;"
512
- "process.stdout.write(JSON.stringify({decision:'block',reason:message}));"
550
+ "process.stdout.write(runLocalFallback(reason,body.trim()?body:'{}'));"
513
551
  "process.exit(0);"
514
552
  "}"
515
553
  "let body='';"
@@ -526,7 +564,9 @@ class ClaudeCodeHarnessAdapter(HarnessAdapter):
526
564
  "response.setEncoding('utf8');"
527
565
  "response.on('data',chunk=>{responseBody+=chunk;});"
528
566
  "response.on('end',()=>{"
529
- "if(response.statusCode>=200&&response.statusCode<300){process.stdout.write(responseBody);process.exit(0);}"
567
+ "if(response.statusCode>=200&&response.statusCode<300){"
568
+ "if(shouldSuppressOutput(data,responseBody)){process.exit(0);}"
569
+ "process.stdout.write(responseBody);process.exit(0);}"
530
570
  "fail(`daemon returned HTTP ${response.statusCode||0}`);"
531
571
  "});"
532
572
  "});"
@@ -672,6 +712,7 @@ class ClaudeCodeHarnessAdapter(HarnessAdapter):
672
712
  for key in (
673
713
  "SessionStart",
674
714
  "PreToolUse",
715
+ "PermissionRequest",
675
716
  "PostToolUse",
676
717
  "UserPromptSubmit",
677
718
  "Notification",
@@ -1187,8 +1187,25 @@ def run_guard_command(
1187
1187
  guard_home=context.guard_home,
1188
1188
  workspace=runtime_workspace,
1189
1189
  )
1190
+ if _is_claude_permission_request(args, payload):
1191
+ notice = _peek_claude_permission_notice(store, payload)
1192
+ if notice is None:
1193
+ _emit_claude_permission_request_passthrough(output_stream=output_stream)
1194
+ return 0
1195
+ _mark_claude_pending_permission_prompt_seen(store=store, payload=payload, notice=notice)
1196
+ _emit_native_hook_response(
1197
+ harness=args.harness,
1198
+ policy_action="block",
1199
+ event_name="PermissionRequest",
1200
+ reason="HOL Guard is routing this approval through AskUserQuestion.",
1201
+ system_message=_claude_permission_prompt_system_message(payload=payload, notice=notice),
1202
+ additional_context=_claude_guard_approval_question_message(notice),
1203
+ output_stream=output_stream,
1204
+ )
1205
+ return 0
1190
1206
  if _is_claude_permission_prompt_notification(args, payload):
1191
1207
  notice = _load_claude_permission_notice(store, payload)
1208
+ _mark_claude_pending_permission_prompt_seen(store=store, payload=payload, notice=notice)
1192
1209
  store.add_event(
1193
1210
  "claude/permission_prompt",
1194
1211
  {
@@ -1205,12 +1222,11 @@ def run_guard_command(
1205
1222
  _emit_native_hook_notification_stderr(
1206
1223
  _claude_permission_prompt_terminal_notice(payload=payload, notice=notice)
1207
1224
  )
1208
- return 2
1209
1225
  _emit_native_hook_response(
1210
1226
  harness=args.harness,
1211
1227
  policy_action="allow",
1212
1228
  event_name="Notification",
1213
- reason="HOL Guard intercepted the tool request and opened this Claude approval prompt.",
1229
+ reason="HOL Guard intercepted the tool request and is routing it through a HOL Guard approval prompt.",
1214
1230
  system_message=system_message,
1215
1231
  additional_context=additional_context,
1216
1232
  output_stream=output_stream,
@@ -1224,6 +1240,10 @@ def run_guard_command(
1224
1240
  _now(),
1225
1241
  )
1226
1242
  return 0
1243
+ if _canonical_harness_name(args.harness) == "claude-code" and _persist_claude_guard_question_decision(
1244
+ store, payload
1245
+ ):
1246
+ return 0
1227
1247
  if runtime_artifact is not None:
1228
1248
  event_name = _hook_event_name(payload) or "PreToolUse"
1229
1249
  runtime_artifact_hash = artifact_hash(runtime_artifact)
@@ -1704,6 +1724,11 @@ def _should_emit_prequeue_native_hook_response(
1704
1724
  return output_stream is not None
1705
1725
 
1706
1726
 
1727
+ def _emit_claude_permission_request_passthrough(*, output_stream: TextIO | None = None) -> None:
1728
+ if output_stream is not None:
1729
+ output_stream.write("")
1730
+
1731
+
1707
1732
  def _claude_permission_notice_state_key(session_id: str, tool_name: str | None = None) -> str:
1708
1733
  if tool_name is not None:
1709
1734
  return f"claude_permission_notice:{session_id}:{tool_name}"
@@ -1812,6 +1837,82 @@ def _load_claude_permission_notice(store: GuardStore, payload: dict[str, object]
1812
1837
  return None
1813
1838
 
1814
1839
 
1840
+ def _peek_claude_permission_notice(store: GuardStore, payload: dict[str, object]) -> dict[str, object] | None:
1841
+ session_id = _optional_string(payload.get("session_id"))
1842
+ if session_id is None:
1843
+ return None
1844
+ tool_name = _claude_notification_tool_name(payload)
1845
+ try:
1846
+ persisted = store.get_sync_payload(_claude_permission_notice_state_key(session_id, tool_name))
1847
+ if persisted is None and tool_name is not None:
1848
+ persisted = store.get_sync_payload(_claude_permission_notice_state_key(session_id))
1849
+ except (OSError, sqlite3.Error):
1850
+ return None
1851
+ return persisted if isinstance(persisted, dict) else None
1852
+
1853
+
1854
+ def _mark_claude_pending_permission_prompt_seen(
1855
+ *,
1856
+ store: GuardStore,
1857
+ payload: dict[str, object],
1858
+ notice: dict[str, object] | None,
1859
+ ) -> None:
1860
+ session_id = _optional_string(payload.get("session_id"))
1861
+ artifact_id = _optional_string((notice or {}).get("artifact_id"))
1862
+ if session_id is None or artifact_id is None:
1863
+ return
1864
+ pending_key = _claude_pending_permission_state_key(session_id, artifact_id)
1865
+ try:
1866
+ pending = store.get_sync_payload(pending_key)
1867
+ except (OSError, sqlite3.Error):
1868
+ return
1869
+ if not isinstance(pending, dict):
1870
+ return
1871
+ updated = dict(pending)
1872
+ updated["permission_prompt_seen"] = True
1873
+ updated["permission_prompt_seen_at"] = _now()
1874
+ try:
1875
+ store.set_sync_payload(pending_key, updated, _now())
1876
+ except (OSError, sqlite3.Error):
1877
+ return
1878
+
1879
+
1880
+ def _load_single_claude_pending_permission(
1881
+ store: GuardStore,
1882
+ payload: dict[str, object],
1883
+ ) -> tuple[str, dict[str, object]] | None:
1884
+ session_id = _optional_string(payload.get("session_id"))
1885
+ if session_id is None:
1886
+ return None
1887
+ try:
1888
+ index_payload = store.get_sync_payload(_claude_pending_permission_index_key(session_id))
1889
+ except (OSError, sqlite3.Error):
1890
+ return None
1891
+ if not isinstance(index_payload, list):
1892
+ return None
1893
+ pending_keys = [str(item) for item in index_payload]
1894
+ pending_items: list[tuple[str, dict[str, object]]] = []
1895
+ for pending_key in pending_keys:
1896
+ try:
1897
+ pending = store.get_sync_payload(pending_key)
1898
+ except (OSError, sqlite3.Error):
1899
+ continue
1900
+ if isinstance(pending, dict):
1901
+ pending_items.append((pending_key, pending))
1902
+ prompt_seen_items = [item for item in pending_items if item[1].get("permission_prompt_seen") is True]
1903
+ if len(prompt_seen_items) == 1:
1904
+ return prompt_seen_items[0]
1905
+ if len(pending_items) != 1:
1906
+ return None
1907
+ try:
1908
+ pending = store.get_sync_payload(pending_items[0][0])
1909
+ except (OSError, sqlite3.Error):
1910
+ return None
1911
+ if not isinstance(pending, dict):
1912
+ return None
1913
+ return pending_items[0][0], pending
1914
+
1915
+
1815
1916
  def _load_claude_pending_permission(
1816
1917
  store: GuardStore,
1817
1918
  payload: dict[str, object],
@@ -1869,6 +1970,7 @@ def _persist_claude_native_permission_policy(
1869
1970
  action: str,
1870
1971
  reason: str,
1871
1972
  now: str,
1973
+ source: str = "claude-native-approval",
1872
1974
  ) -> bool:
1873
1975
  try:
1874
1976
  store.upsert_policy(
@@ -1879,7 +1981,7 @@ def _persist_claude_native_permission_policy(
1879
1981
  artifact_id=artifact_id,
1880
1982
  artifact_hash=artifact_hash,
1881
1983
  reason=reason,
1882
- source="claude-native-approval",
1984
+ source=source,
1883
1985
  ),
1884
1986
  now,
1885
1987
  )
@@ -1963,6 +2065,8 @@ def _persist_claude_pending_permission_denials(store: GuardStore, payload: dict[
1963
2065
  continue
1964
2066
  if not isinstance(pending, dict):
1965
2067
  continue
2068
+ if pending.get("permission_prompt_seen") is not True:
2069
+ continue
1966
2070
  artifact_id = _optional_string(pending.get("artifact_id"))
1967
2071
  artifact_hash_value = _optional_string(pending.get("artifact_hash"))
1968
2072
  if artifact_id is None or artifact_hash_value is None:
@@ -2011,6 +2115,106 @@ def _persist_claude_pending_permission_denials(store: GuardStore, payload: dict[
2011
2115
  return denied
2012
2116
 
2013
2117
 
2118
+ def _claude_guard_approval_question_message(notice: dict[str, object] | None) -> str:
2119
+ tool_name = _optional_string((notice or {}).get("tool_name")) or "this tool"
2120
+ reason = _optional_string((notice or {}).get("reason"))
2121
+ reason_text = f" HOL Guard reason: {_ensure_terminal_punctuation(reason)}" if reason is not None else ""
2122
+ return (
2123
+ f"HOL Guard needs the user's explicit decision before {tool_name} can run.{reason_text} "
2124
+ "The native Claude permission prompt is not the final decision surface for this request. Call "
2125
+ "AskUserQuestion now with one HOL Guard approval question before retrying the tool. Use header "
2126
+ "'HOL Guard', question 'HOL Guard intercepted this sensitive action. What should Claude do?', and exactly "
2127
+ "these options: 'Allow once', 'Allow during this session', and 'Keep blocked'. If the user chooses an "
2128
+ "allow option, retry the same tool once. If the user chooses Keep blocked, do not retry the sensitive "
2129
+ "action."
2130
+ )
2131
+
2132
+
2133
+ def _is_claude_guard_approval_question(payload: dict[str, object]) -> bool:
2134
+ if _hook_event_name(payload) != "PostToolUse":
2135
+ return False
2136
+ tool_name = _optional_string(payload.get("tool_name"))
2137
+ if tool_name is None or tool_name.lower() != "askuserquestion":
2138
+ return False
2139
+ combined = json.dumps(
2140
+ {
2141
+ "tool_input": payload.get("tool_input"),
2142
+ "tool_response": payload.get("tool_response"),
2143
+ },
2144
+ sort_keys=True,
2145
+ default=str,
2146
+ ).lower()
2147
+ return "hol guard" in combined and (
2148
+ "allow once" in combined or "allow during this session" in combined or "keep blocked" in combined
2149
+ )
2150
+
2151
+
2152
+ def _claude_guard_approval_answer(payload: dict[str, object]) -> str | None:
2153
+ response = payload.get("tool_response")
2154
+ answer_text: str | None = None
2155
+ if isinstance(response, dict):
2156
+ answers = response.get("answers")
2157
+ if isinstance(answers, dict):
2158
+ joined_answers = " ".join(str(answer) for answer in answers.values() if str(answer).strip())
2159
+ if joined_answers:
2160
+ answer_text = joined_answers
2161
+ if answer_text is None:
2162
+ for key in ("answer", "selected_answer", "selected", "choice", "value", "label"):
2163
+ value = response.get(key)
2164
+ if isinstance(value, str) and value.strip():
2165
+ answer_text = value
2166
+ break
2167
+ if answer_text is None and "questions" not in response and "options" not in response:
2168
+ content = response.get("content")
2169
+ if isinstance(content, str) and content.strip():
2170
+ answer_text = content
2171
+ elif isinstance(response, str) and response.strip():
2172
+ answer_text = response
2173
+ if answer_text is None:
2174
+ return None
2175
+ normalized_answer = answer_text.lower()
2176
+ if "keep blocked" in normalized_answer:
2177
+ return "block"
2178
+ if "allow during this session" in normalized_answer or "allow once" in normalized_answer:
2179
+ return "allow"
2180
+ return None
2181
+
2182
+
2183
+ def _persist_claude_guard_question_decision(store: GuardStore, payload: dict[str, object]) -> bool:
2184
+ if not _is_claude_guard_approval_question(payload):
2185
+ return False
2186
+ action = _claude_guard_approval_answer(payload)
2187
+ if action is None:
2188
+ return False
2189
+ pending_pair = _load_single_claude_pending_permission(store, payload)
2190
+ if pending_pair is None:
2191
+ return False
2192
+ pending_key, pending = pending_pair
2193
+ artifact_id = _optional_string(pending.get("artifact_id"))
2194
+ artifact_hash_value = _optional_string(pending.get("artifact_hash"))
2195
+ if artifact_id is None or artifact_hash_value is None:
2196
+ return False
2197
+ saved = _persist_claude_native_permission_policy(
2198
+ store=store,
2199
+ artifact_id=artifact_id,
2200
+ artifact_hash=artifact_hash_value,
2201
+ action=action,
2202
+ reason=(
2203
+ "Allowed through HOL Guard AskUserQuestion approval."
2204
+ if action == "allow"
2205
+ else "Blocked through HOL Guard AskUserQuestion approval."
2206
+ ),
2207
+ now=_now(),
2208
+ source="claude-ask-user-question",
2209
+ )
2210
+ if not saved:
2211
+ return False
2212
+ session_id = _optional_string(payload.get("session_id"))
2213
+ if session_id is not None:
2214
+ _remove_claude_pending_permission(store, session_id=session_id, pending_key=pending_key)
2215
+ return True
2216
+
2217
+
2014
2218
  def _is_claude_permission_prompt_notification(args: argparse.Namespace, payload: dict[str, object]) -> bool:
2015
2219
  return (
2016
2220
  _canonical_harness_name(args.harness) == "claude-code"
@@ -2019,6 +2223,10 @@ def _is_claude_permission_prompt_notification(args: argparse.Namespace, payload:
2019
2223
  )
2020
2224
 
2021
2225
 
2226
+ def _is_claude_permission_request(args: argparse.Namespace, payload: dict[str, object]) -> bool:
2227
+ return _canonical_harness_name(args.harness) == "claude-code" and _hook_event_name(payload) == "PermissionRequest"
2228
+
2229
+
2022
2230
  def _claude_permission_prompt_system_message(
2023
2231
  *,
2024
2232
  payload: dict[str, object],
@@ -2028,20 +2236,23 @@ def _claude_permission_prompt_system_message(
2028
2236
  if tool_name is None and notice is not None:
2029
2237
  tool_name = _optional_string(notice.get("tool_name"))
2030
2238
  reason = _optional_string(notice.get("reason")) if notice is not None else None
2031
- intro = "HOL Guard intercepted a sensitive request and opened this Claude approval prompt."
2239
+ intro = "HOL Guard intercepted a sensitive request and is routing it to a HOL Guard approval question."
2032
2240
  if tool_name is not None:
2033
- intro = f"HOL Guard intercepted Claude's attempt to use {tool_name} and opened this approval prompt."
2241
+ intro = (
2242
+ f"HOL Guard intercepted Claude's attempt to use {tool_name} and is routing it to a HOL Guard approval "
2243
+ "question."
2244
+ )
2034
2245
  if reason is not None:
2035
2246
  return (
2036
- f"{intro} This approval dialog came from HOL Guard, not from Claude alone. "
2247
+ f"{intro} This approval flow came from HOL Guard, not from Claude alone. "
2037
2248
  f"{_ensure_terminal_punctuation(reason)} "
2038
- "Use the Claude choices below: Yes to allow it once, Yes during this session to trust the same action "
2039
- "for the rest of this session, or No to keep the sensitive action blocked."
2249
+ "HOL Guard will ask the user to choose Allow once, Allow during this session, or Keep blocked before "
2250
+ "Claude retries the action."
2040
2251
  )
2041
2252
  return (
2042
- f"{intro} This approval dialog came from HOL Guard, not from Claude alone. "
2043
- "Use the Claude choices below: Yes to allow it once, Yes during this session to trust the same action for "
2044
- "the rest of this session, or No to keep the sensitive action blocked."
2253
+ f"{intro} This approval flow came from HOL Guard, not from Claude alone. "
2254
+ "HOL Guard will ask the user to choose Allow once, Allow during this session, or Keep blocked before Claude "
2255
+ "retries the action."
2045
2256
  )
2046
2257
 
2047
2258
 
@@ -2049,17 +2260,17 @@ def _claude_permission_prompt_additional_context(notice: dict[str, object] | Non
2049
2260
  reason = _optional_string(notice.get("reason")) if notice is not None else None
2050
2261
  if reason is not None:
2051
2262
  return (
2052
- "HOL Guard intercepted the sensitive request and opened the Claude approval dialog that is currently "
2053
- "open. "
2054
- "This approval dialog came from HOL Guard, not from Claude alone. "
2055
- f"{_ensure_terminal_punctuation(reason)} The user can choose Yes, Yes during this session, or No in the "
2056
- "prompt that is already visible. If the user denies it, do not retry the same sensitive access."
2263
+ "HOL Guard intercepted the sensitive request and is routing it into a HOL Guard approval question. "
2264
+ "This approval flow came from HOL Guard, not from Claude alone. "
2265
+ f"{_ensure_terminal_punctuation(reason)} Ask the user with AskUserQuestion and the options Allow once, "
2266
+ "Allow during this session, and Keep blocked. If the user chooses Keep blocked, do not retry the same "
2267
+ "sensitive access."
2057
2268
  )
2058
2269
  return (
2059
- "HOL Guard intercepted the sensitive request and opened the Claude approval dialog that is currently open. "
2060
- "This approval dialog came from HOL Guard, not from Claude alone. "
2061
- "The user can choose Yes, Yes during this session, or No in the prompt that is already visible. "
2062
- "If the user denies it, do not retry the same action."
2270
+ "HOL Guard intercepted the sensitive request and is routing it into a HOL Guard approval question. "
2271
+ "This approval flow came from HOL Guard, not from Claude alone. Ask the user with AskUserQuestion and the "
2272
+ "options Allow once, Allow during this session, and Keep blocked. If the user chooses Keep blocked, do not "
2273
+ "retry the same action."
2063
2274
  )
2064
2275
 
2065
2276
 
@@ -2072,28 +2283,32 @@ def _claude_permission_prompt_terminal_notice(
2072
2283
  reason = _optional_string(notice.get("reason")) if notice is not None else None
2073
2284
  if tool_name is not None and reason is not None:
2074
2285
  return (
2075
- f"HOL Guard opened this Claude approval prompt for {tool_name}. "
2286
+ f"HOL Guard is routing this Claude approval request for {tool_name} into a HOL Guard decision prompt. "
2076
2287
  f"{_ensure_terminal_punctuation(reason)} "
2077
- "Review the request below, then choose Yes, Yes during this session, or No."
2288
+ "Choose Allow once, Allow during this session, or Keep blocked in the HOL Guard prompt."
2078
2289
  )
2079
2290
  if tool_name is not None:
2080
2291
  return (
2081
- f"HOL Guard opened this Claude approval prompt for {tool_name}. "
2082
- "Review the request below, then choose Yes, Yes during this session, or No."
2292
+ f"HOL Guard is routing this Claude approval request for {tool_name} into a HOL Guard decision prompt. "
2293
+ "Choose Allow once, Allow during this session, or Keep blocked in the HOL Guard prompt."
2083
2294
  )
2084
2295
  return (
2085
- "HOL Guard opened this Claude approval prompt to protect a sensitive action. "
2086
- "Review the request below, then choose Yes, Yes during this session, or No."
2296
+ "HOL Guard is routing this Claude approval request into a HOL Guard decision prompt to protect a sensitive "
2297
+ "action. Choose Allow once, Allow during this session, or Keep blocked in the HOL Guard prompt."
2087
2298
  )
2088
2299
 
2089
2300
 
2090
2301
  def _claude_native_pretooluse_terminal_notice(*, payload: dict[str, object], reason: str) -> str:
2091
2302
  tool_name = _claude_notification_tool_name(payload)
2092
2303
  if tool_name is not None:
2093
- return f"HOL Guard opened this Claude approval prompt for {tool_name}. {_ensure_terminal_punctuation(reason)}"
2304
+ return (
2305
+ f"HOL Guard intercepted Claude's attempt to use {tool_name}. {_ensure_terminal_punctuation(reason)} "
2306
+ "Guard will route the next approval through a HOL Guard prompt if Claude asks to continue."
2307
+ )
2094
2308
  return (
2095
- "HOL Guard opened this Claude approval prompt to protect a sensitive action. "
2096
- f"{_ensure_terminal_punctuation(reason)}"
2309
+ "HOL Guard intercepted a sensitive Claude action. "
2310
+ f"{_ensure_terminal_punctuation(reason)} Guard will route the next approval through a HOL Guard prompt if "
2311
+ "Claude asks to continue."
2097
2312
  )
2098
2313
 
2099
2314
 
@@ -2191,9 +2406,8 @@ def _runtime_artifact_native_reason(artifact: GuardArtifact, response_payload: d
2191
2406
  if harness == "claude-code" and policy_action == "require-reapproval":
2192
2407
  return (
2193
2408
  f"HOL Guard intercepted Claude's attempt to use {tool_name} for {path_class} to protect your local "
2194
- "secrets. This approval prompt came from HOL Guard, not from Claude alone. "
2195
- "Choose Yes to allow it once, Yes during this session to trust the same action for the rest of this "
2196
- "session, or No to keep the secret private."
2409
+ "secrets. The approval flow came from HOL Guard, not from Claude alone. HOL Guard will ask you to "
2410
+ "choose Allow once, Allow during this session, or Keep blocked before Claude retries this action."
2197
2411
  )
2198
2412
  return (
2199
2413
  f"HOL Guard blocked Claude's attempt to use {tool_name} for {path_class} to protect your local secrets. "
@@ -2236,7 +2450,10 @@ def _claude_prompt_additional_context(
2236
2450
  )
2237
2451
  return (
2238
2452
  f"{_ensure_terminal_punctuation(native_reason)} "
2239
- "Before you use the first sensitive tool for this request, tell the user exactly: "
2453
+ "Before you use the first sensitive tool for this request, call AskUserQuestion with header 'HOL Guard', "
2454
+ "question 'HOL Guard intercepted this sensitive action. What should Claude do?', and exactly these options: "
2455
+ "'Allow once', 'Allow during this session', and 'Keep blocked'. If the user chooses Keep blocked, do not "
2456
+ "retry the sensitive action. Then tell the user exactly: "
2240
2457
  f"'{briefing_sentence}' "
2241
2458
  "Attempt that sensitive tool at most once. If HOL Guard or Claude denies it, do not retry the same sensitive "
2242
2459
  "action automatically. Instead, tell the user approval is required in Claude to continue."
@@ -2255,12 +2472,11 @@ def _claude_prompt_system_message(
2255
2472
  if "secret_read" in _prompt_request_classes(artifact):
2256
2473
  return (
2257
2474
  "HOL Guard intercepted this prompt because it asks Claude to access local secrets. "
2258
- "If Claude opens a permission dialog on the next tool call, that approval prompt came from HOL "
2259
- "Guard."
2475
+ "If Claude asks to continue, HOL Guard will route the decision through a branded approval prompt."
2260
2476
  )
2261
2477
  return (
2262
2478
  "HOL Guard intercepted this prompt because it leads to a sensitive action. "
2263
- "If Claude opens a permission dialog on the next tool call, that approval prompt came from HOL Guard."
2479
+ "If Claude asks to continue, HOL Guard will route the decision through a branded approval prompt."
2264
2480
  )
2265
2481
  if policy_action in {"block", "sandbox-required"}:
2266
2482
  return _ensure_terminal_punctuation(native_reason)
@@ -2377,7 +2593,18 @@ def _emit_native_hook_response(
2377
2593
  if payload:
2378
2594
  _write_json_line(payload, output_stream=output_stream)
2379
2595
  return
2380
- if event_name == "Notification":
2596
+ if event_name in {"Notification", "PermissionRequest"}:
2597
+ if event_name == "PermissionRequest" and policy_action in {"block", "sandbox-required"}:
2598
+ payload["hookSpecificOutput"] = {
2599
+ "hookEventName": event_name,
2600
+ "decision": {
2601
+ "behavior": "deny",
2602
+ "message": additional_context or reason,
2603
+ "interrupt": False,
2604
+ },
2605
+ }
2606
+ _write_json_line(payload, output_stream=output_stream)
2607
+ return
2381
2608
  if additional_context:
2382
2609
  payload["hookSpecificOutput"] = {
2383
2610
  "hookEventName": event_name,
@@ -1,3 +1,3 @@
1
1
  """Single source of truth for tool version."""
2
2
 
3
- __version__ = "2.0.61"
3
+ __version__ = "2.0.63"