plugin-scanner 2.0.61__tar.gz → 2.0.62__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.62}/PKG-INFO +1 -1
  2. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/pyproject.toml +1 -1
  3. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/pyproject.toml.bak +1 -1
  4. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/adapters/claude_code.py +42 -3
  5. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/cli/commands.py +77 -2
  6. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/version.py +1 -1
  7. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_claude_adapter.py +57 -3
  8. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_runtime.py +156 -18
  9. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_surface_server.py +3 -15
  10. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/uv.lock +22 -22
  11. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.clusterfuzzlite/Dockerfile +0 -0
  12. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.clusterfuzzlite/build.sh +0 -0
  13. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.clusterfuzzlite/project.yaml +0 -0
  14. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.clusterfuzzlite/requirements-atheris.txt +0 -0
  15. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.dockerignore +0 -0
  16. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.github/CODEOWNERS +0 -0
  17. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.github/ISSUE_TEMPLATE/bug-report.yml +0 -0
  18. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  19. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
  20. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.github/dependabot.yml +0 -0
  21. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.github/workflows/ci.yml +0 -0
  22. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.github/workflows/codeql.yml +0 -0
  23. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.github/workflows/dependabot-uv-lock.yml +0 -0
  24. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.github/workflows/fuzz.yml +0 -0
  25. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.github/workflows/harness-smoke.yml +0 -0
  26. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.github/workflows/publish.yml +0 -0
  27. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.github/workflows/scorecard.yml +0 -0
  28. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.gitignore +0 -0
  29. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.pre-commit-hooks.yaml +0 -0
  30. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/CONTRIBUTING.md +0 -0
  31. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/Dockerfile +0 -0
  32. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/LICENSE +0 -0
  33. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/README.md +0 -0
  34. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/SECURITY.md +0 -0
  35. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/index.html +0 -0
  36. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/package.json +0 -0
  37. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/pnpm-lock.yaml +0 -0
  38. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/public/brand/Logo_Whole.png +0 -0
  39. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/src/app.tsx +0 -0
  40. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/src/approval-center-layout.tsx +0 -0
  41. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/src/approval-center-primitives.tsx +0 -0
  42. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/src/approval-center-utils.ts +0 -0
  43. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/src/fleet-workspace.tsx +0 -0
  44. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/src/guard-api.ts +0 -0
  45. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/src/guard-demo.ts +0 -0
  46. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/src/guard-types.ts +0 -0
  47. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/src/main.tsx +0 -0
  48. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/src/receipts-workspace.tsx +0 -0
  49. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/src/runtime-overview.tsx +0 -0
  50. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/src/styles.css +0 -0
  51. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/src/vite-env.d.ts +0 -0
  52. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/tsconfig.json +0 -0
  53. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/vite.config.ts +0 -0
  54. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/docker-requirements.txt +0 -0
  55. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/docs/guard/approval-audit.md +0 -0
  56. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/docs/guard/architecture.md +0 -0
  57. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/docs/guard/get-started.md +0 -0
  58. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/docs/guard/harness-support.md +0 -0
  59. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/docs/guard/local-vs-cloud.md +0 -0
  60. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/docs/guard/testing-matrix.md +0 -0
  61. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/docs/trust/mcp-trust-draft.md +0 -0
  62. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/docs/trust/plugin-trust-draft.md +0 -0
  63. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/docs/trust/skill-trust-local.md +0 -0
  64. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/fuzzers/manifest_fuzzer.py +0 -0
  65. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/requirements.txt +0 -0
  66. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/schemas/plugin-quality.v1.json +0 -0
  67. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/schemas/scan-result.v1.json +0 -0
  68. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/schemas/verify-result.v1.json +0 -0
  69. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/__init__.py +0 -0
  70. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/action_runner.py +0 -0
  71. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/argparse_utils.py +0 -0
  72. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/checks/__init__.py +0 -0
  73. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/checks/best_practices.py +0 -0
  74. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/checks/claude.py +0 -0
  75. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/checks/code_quality.py +0 -0
  76. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/checks/ecosystem_common.py +0 -0
  77. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/checks/gemini.py +0 -0
  78. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/checks/manifest.py +0 -0
  79. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/checks/manifest_support.py +0 -0
  80. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/checks/marketplace.py +0 -0
  81. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/checks/mcp_security.py +0 -0
  82. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/checks/opencode.py +0 -0
  83. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/checks/operational_security.py +0 -0
  84. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/checks/security.py +0 -0
  85. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/checks/skill_security.py +0 -0
  86. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/cli.py +0 -0
  87. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/cli_ui.py +0 -0
  88. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/config.py +0 -0
  89. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/ecosystems/__init__.py +0 -0
  90. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/ecosystems/base.py +0 -0
  91. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/ecosystems/claude.py +0 -0
  92. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/ecosystems/codex.py +0 -0
  93. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/ecosystems/detect.py +0 -0
  94. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/ecosystems/gemini.py +0 -0
  95. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/ecosystems/opencode.py +0 -0
  96. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/ecosystems/registry.py +0 -0
  97. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/ecosystems/types.py +0 -0
  98. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/github_reporting.py +0 -0
  99. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/__init__.py +0 -0
  100. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/adapters/__init__.py +0 -0
  101. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/adapters/antigravity.py +0 -0
  102. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/adapters/base.py +0 -0
  103. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/adapters/codex.py +0 -0
  104. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/adapters/copilot.py +0 -0
  105. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/adapters/cursor.py +0 -0
  106. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/adapters/gemini.py +0 -0
  107. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/adapters/hermes.py +0 -0
  108. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/adapters/mcp_servers.py +0 -0
  109. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/adapters/opencode.py +0 -0
  110. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/adapters/opencode_artifacts.py +0 -0
  111. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/approvals.py +0 -0
  112. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/bridge/__init__.py +0 -0
  113. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/capabilities.py +0 -0
  114. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/cli/__init__.py +0 -0
  115. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/cli/approval_commands.py +0 -0
  116. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/cli/bootstrap.py +0 -0
  117. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/cli/connect_flow.py +0 -0
  118. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/cli/install_commands.py +0 -0
  119. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/cli/product.py +0 -0
  120. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/cli/prompt.py +0 -0
  121. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/cli/render.py +0 -0
  122. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/cli/update_commands.py +0 -0
  123. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/codex_config.py +0 -0
  124. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/config.py +0 -0
  125. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/consumer/__init__.py +0 -0
  126. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/consumer/service.py +0 -0
  127. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/daemon/__init__.py +0 -0
  128. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/daemon/client.py +0 -0
  129. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/daemon/manager.py +0 -0
  130. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/daemon/server.py +0 -0
  131. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/daemon/static/assets/guard-dashboard.js +0 -0
  132. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/daemon/static/assets/index.css +0 -0
  133. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Whole.png +0 -0
  134. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/daemon/static/index.html +0 -0
  135. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/incident.py +0 -0
  136. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/launcher.py +0 -0
  137. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/mcp_tool_calls.py +0 -0
  138. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/models.py +0 -0
  139. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/policy/__init__.py +0 -0
  140. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/policy/engine.py +0 -0
  141. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/protect.py +0 -0
  142. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/proxy/__init__.py +0 -0
  143. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/proxy/remote.py +0 -0
  144. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/proxy/runtime_mcp.py +0 -0
  145. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/proxy/stdio.py +0 -0
  146. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/receipts/__init__.py +0 -0
  147. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/receipts/manager.py +0 -0
  148. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/risk.py +0 -0
  149. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/runtime/__init__.py +0 -0
  150. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/runtime/runner.py +0 -0
  151. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/runtime/secret_file_requests.py +0 -0
  152. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/runtime/surface_server.py +0 -0
  153. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/schemas/__init__.py +0 -0
  154. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/schemas/consumer_mode.py +0 -0
  155. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/schemas/surface_server.py +0 -0
  156. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/shims.py +0 -0
  157. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/store.py +0 -0
  158. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/store_approvals.py +0 -0
  159. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/store_connect.py +0 -0
  160. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/types.py +0 -0
  161. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/integrations/__init__.py +0 -0
  162. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/integrations/cisco_mcp_scanner.py +0 -0
  163. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/integrations/cisco_skill_scanner.py +0 -0
  164. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/lint_fixes.py +0 -0
  165. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/marketplace_support.py +0 -0
  166. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/models.py +0 -0
  167. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/path_support.py +0 -0
  168. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/policy.py +0 -0
  169. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/quality_artifact.py +0 -0
  170. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/repo_detect.py +0 -0
  171. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/reporting.py +0 -0
  172. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/rules/__init__.py +0 -0
  173. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/rules/registry.py +0 -0
  174. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/rules/specs.py +0 -0
  175. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/scanner.py +0 -0
  176. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/submission.py +0 -0
  177. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/suppressions.py +0 -0
  178. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/trust_domain_scoring.py +0 -0
  179. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/trust_helpers.py +0 -0
  180. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/trust_mcp_scoring.py +0 -0
  181. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/trust_models.py +0 -0
  182. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/trust_plugin_scoring.py +0 -0
  183. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/trust_scoring.py +0 -0
  184. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/trust_skill_scoring.py +0 -0
  185. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/trust_specs.py +0 -0
  186. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/verification.py +0 -0
  187. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/__init__.py +0 -0
  188. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/conftest.py +0 -0
  189. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/__init__.py +0 -0
  190. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/bad-plugin/.codex-plugin/plugin.json +0 -0
  191. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/bad-plugin/.mcp.json +0 -0
  192. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/bad-plugin/secrets.js +0 -0
  193. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/claude-plugin-good/.claude-plugin/plugin.json +0 -0
  194. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/claude-plugin-good/LICENSE +0 -0
  195. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/claude-plugin-good/README.md +0 -0
  196. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/claude-plugin-good/SECURITY.md +0 -0
  197. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/claude-plugin-good/hooks/hooks.json +0 -0
  198. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/claude-plugin-good/skills/example/SKILL.md +0 -0
  199. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/code-quality-bad/evil.js +0 -0
  200. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/code-quality-bad/inject.js +0 -0
  201. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/gemini-extension-good/GEMINI.md +0 -0
  202. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/gemini-extension-good/LICENSE +0 -0
  203. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/gemini-extension-good/README.md +0 -0
  204. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/gemini-extension-good/SECURITY.md +0 -0
  205. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/gemini-extension-good/commands/hello.toml +0 -0
  206. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/gemini-extension-good/gemini-extension.json +0 -0
  207. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/good-plugin/.codex-plugin/plugin.json +0 -0
  208. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/good-plugin/.codexignore +0 -0
  209. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/good-plugin/LICENSE +0 -0
  210. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/good-plugin/README.md +0 -0
  211. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/good-plugin/SECURITY.md +0 -0
  212. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/good-plugin/assets/icon.svg +0 -0
  213. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/good-plugin/assets/logo.svg +0 -0
  214. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/good-plugin/assets/screenshot.svg +0 -0
  215. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/good-plugin/skills/example/SKILL.md +0 -0
  216. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/guard-codex-malicious-mcp/.codex/config.toml +0 -0
  217. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/hermes-plugin-evil/config.yaml +0 -0
  218. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/hermes-plugin-evil/mcp_servers.json +0 -0
  219. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/hermes-plugin-evil/skills/security/malicious/SKILL.md +0 -0
  220. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/SKILL.md +0 -0
  221. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/references/api-setup.md +0 -0
  222. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/scripts/deploy.sh +0 -0
  223. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/hermes-plugin-evil/skills/utils/benign/SKILL.md +0 -0
  224. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/malformed-json/.codex-plugin/plugin.json +0 -0
  225. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/malicious-skill-plugin/.codex-plugin/plugin.json +0 -0
  226. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/malicious-skill-plugin/.codexignore +0 -0
  227. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/malicious-skill-plugin/LICENSE +0 -0
  228. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/malicious-skill-plugin/README.md +0 -0
  229. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/malicious-skill-plugin/SECURITY.md +0 -0
  230. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/malicious-skill-plugin/skills/leaky-skill/SKILL.md +0 -0
  231. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/mcp-canary-server.py +0 -0
  232. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/minimal-plugin/.codex-plugin/plugin.json +0 -0
  233. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/missing-fields/.codex-plugin/plugin.json +0 -0
  234. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/mit-license/LICENSE +0 -0
  235. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/multi-ecosystem-repo/codex-plugin/.codex-plugin/plugin.json +0 -0
  236. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/multi-ecosystem-repo/codex-plugin/LICENSE +0 -0
  237. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/multi-ecosystem-repo/codex-plugin/README.md +0 -0
  238. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/multi-ecosystem-repo/codex-plugin/SECURITY.md +0 -0
  239. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/multi-ecosystem-repo/gemini-ext/README.md +0 -0
  240. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/multi-ecosystem-repo/gemini-ext/gemini-extension.json +0 -0
  241. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/multi-plugin-repo/.agents/plugins/marketplace.json +0 -0
  242. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codex-plugin/plugin.json +0 -0
  243. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codexignore +0 -0
  244. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/LICENSE +0 -0
  245. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/README.md +0 -0
  246. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/SECURITY.md +0 -0
  247. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/skills/example/SKILL.md +0 -0
  248. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/.codex-plugin/plugin.json +0 -0
  249. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/skills/example/SKILL.md +0 -0
  250. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/no-version/.codex-plugin/plugin.json +0 -0
  251. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/opencode-good/.opencode/commands/hello.md +0 -0
  252. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/opencode-good/.opencode/plugins/example.ts +0 -0
  253. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/opencode-good/LICENSE +0 -0
  254. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/opencode-good/README.md +0 -0
  255. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/opencode-good/SECURITY.md +0 -0
  256. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/opencode-good/opencode.jsonc +0 -0
  257. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/skills-missing-dir/.codex-plugin/plugin.json +0 -0
  258. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/skills-no-frontmatter/.codex-plugin/plugin.json +0 -0
  259. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/skills-no-frontmatter/skills/bad-skill/SKILL.md +0 -0
  260. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/with-marketplace/.codex-plugin/plugin.json +0 -0
  261. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/with-marketplace/marketplace-broken.json +0 -0
  262. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/with-marketplace/marketplace.json +0 -0
  263. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test-trust-scoring.py +0 -0
  264. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test-trust-specs.py +0 -0
  265. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_action_runner.py +0 -0
  266. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_best_practices.py +0 -0
  267. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_cisco_install_surfaces.py +0 -0
  268. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_cli.py +0 -0
  269. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_code_quality.py +0 -0
  270. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_config.py +0 -0
  271. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_coverage_remaining.py +0 -0
  272. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_ecosystems.py +0 -0
  273. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_edge_cases.py +0 -0
  274. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_final_coverage.py +0 -0
  275. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_approvals.py +0 -0
  276. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_bootstrap.py +0 -0
  277. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_capabilities.py +0 -0
  278. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_cli.py +0 -0
  279. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_codex_e2e.py +0 -0
  280. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_codex_install.py +0 -0
  281. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_codex_proxy.py +0 -0
  282. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_config_paths.py +0 -0
  283. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_connect_flow.py +0 -0
  284. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_consumer_mode.py +0 -0
  285. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_copilot_adapter.py +0 -0
  286. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_copilot_proxy.py +0 -0
  287. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_daemon_manager.py +0 -0
  288. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_events.py +0 -0
  289. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_launch_env.py +0 -0
  290. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_opencode_proxy.py +0 -0
  291. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_product_flow.py +0 -0
  292. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_protect.py +0 -0
  293. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_render.py +0 -0
  294. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_risk.py +0 -0
  295. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_store_migrations.py +0 -0
  296. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_verdicts.py +0 -0
  297. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_hermes_adapter.py +0 -0
  298. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_integration.py +0 -0
  299. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_lint_fixes.py +0 -0
  300. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_live_cisco_smoke.py +0 -0
  301. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_manifest.py +0 -0
  302. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_marketplace.py +0 -0
  303. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_mcp_security.py +0 -0
  304. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_operational_security.py +0 -0
  305. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_policy.py +0 -0
  306. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_quality_artifact.py +0 -0
  307. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_rule_registry.py +0 -0
  308. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_scanner.py +0 -0
  309. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_schema_contracts.py +0 -0
  310. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_security.py +0 -0
  311. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_security_ops.py +0 -0
  312. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_skill_security.py +0 -0
  313. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_submission.py +0 -0
  314. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_trust_scoring.py +0 -0
  315. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_trust_specs.py +0 -0
  316. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_verification.py +0 -0
  317. {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/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.62
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.62"
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.62"
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"
@@ -44,6 +44,7 @@ def _shell_command(command: tuple[str, ...], *, windows: bool | None = None) ->
44
44
  def _sync_runtime_hook_groups(hooks: dict[str, object], hook_command: str) -> None:
45
45
  for key, matcher, timeout in (
46
46
  ("PreToolUse", CLAUDE_GUARD_TOOL_MATCHER, CLAUDE_GUARD_TOOL_TIMEOUT_SECONDS),
47
+ ("PermissionRequest", CLAUDE_GUARD_TOOL_MATCHER, CLAUDE_GUARD_NOTIFICATION_TIMEOUT_SECONDS),
47
48
  ("PostToolUse", CLAUDE_GUARD_TOOL_MATCHER, CLAUDE_GUARD_TOOL_TIMEOUT_SECONDS),
48
49
  ("UserPromptSubmit", None, CLAUDE_GUARD_PROMPT_TIMEOUT_SECONDS),
49
50
  ("Notification", CLAUDE_GUARD_NOTIFICATION_MATCHER, CLAUDE_GUARD_NOTIFICATION_TIMEOUT_SECONDS),
@@ -486,6 +487,7 @@ class ClaudeCodeHarnessAdapter(HarnessAdapter):
486
487
  def _daemon_hook_command_parts(context: HarnessContext) -> tuple[str, ...]:
487
488
  fallback_daemon_url = load_guard_daemon_url(context.guard_home) or guard_daemon_url_for_home(context.guard_home)
488
489
  state_path = context.guard_home / "daemon-state.json"
490
+ fallback_command = ClaudeCodeHarnessAdapter._hook_command_parts(context)
489
491
  query: dict[str, str] = {"guard-home": str(context.guard_home)}
490
492
  if context.home_dir.resolve() != Path.home().resolve():
491
493
  query["home"] = str(context.home_dir)
@@ -496,9 +498,11 @@ class ClaudeCodeHarnessAdapter(HarnessAdapter):
496
498
  "void MARKER;"
497
499
  "const fs=require('fs');"
498
500
  "const http=require('http');"
501
+ "const cp=require('child_process');"
499
502
  "const {URL}=require('url');"
500
503
  f"const statePath={str(state_path)!r};"
501
504
  f"const fallbackUrl={fallback_daemon_url!r};"
505
+ f"const fallbackCommand={list(fallback_command)!r};"
502
506
  f"const query={urlencode(query)!r};"
503
507
  "function daemonUrl(){"
504
508
  "try{"
@@ -507,9 +511,41 @@ class ClaudeCodeHarnessAdapter(HarnessAdapter):
507
511
  "}catch(_error){}"
508
512
  "return fallbackUrl;"
509
513
  "}"
514
+ "function eventName(data){"
515
+ "try{const payload=JSON.parse(data||'{}');"
516
+ "return String(payload.hook_event_name||payload.event||'PreToolUse');}"
517
+ "catch(_error){return 'PreToolUse';}"
518
+ "}"
519
+ "function degraded(reason,data){"
520
+ "const event=eventName(data);"
521
+ "const message=`HOL Guard could not reach the local daemon (${reason}), so it is using Claude's native "
522
+ "approval prompt as a safety fallback.`;"
523
+ "if(event==='UserPromptSubmit'){return '';}"
524
+ "if(event==='PreToolUse'){return JSON.stringify({systemMessage:'HOL Guard opened this Claude approval "
525
+ "prompt because local daemon evaluation was unavailable.',hookSpecificOutput:{hookEventName:'PreToolUse',"
526
+ "permissionDecision:'ask',permissionDecisionReason:`${message} Choose Yes to allow it once, Yes during "
527
+ "this session to trust the same action for this session, or No to keep it blocked.`}});}"
528
+ "return '{}';"
529
+ "}"
530
+ "function shouldSuppressOutput(data,responseBody){"
531
+ "if(eventName(data)!=='UserPromptSubmit')return false;"
532
+ "const trimmed=(responseBody||'').trim();"
533
+ "return trimmed===''||trimmed==='{}';"
534
+ "}"
535
+ "function runLocalFallback(reason,data){"
536
+ "try{"
537
+ "const result=cp.spawnSync(fallbackCommand[0],fallbackCommand.slice(1),{input:data,encoding:'utf8',"
538
+ "timeout:30000,env:process.env});"
539
+ "if(result.error)return degraded(`${reason}; fallback failed: ${result.error.message}`,data);"
540
+ "if(result.status===0){"
541
+ "if(shouldSuppressOutput(data,result.stdout)){process.exit(0);}"
542
+ "process.stdout.write(result.stdout&&result.stdout.trim()?result.stdout:'{}');"
543
+ "process.exit(0);}"
544
+ "return degraded(`${reason}; fallback exited ${result.status}`,data);"
545
+ "}catch(error){return degraded(`${reason}; fallback crashed: ${error.message}`,data);}"
546
+ "}"
510
547
  "function fail(reason){"
511
- "const message=`HOL Guard could not evaluate this action: ${reason}`;"
512
- "process.stdout.write(JSON.stringify({decision:'block',reason:message}));"
548
+ "process.stdout.write(runLocalFallback(reason,body.trim()?body:'{}'));"
513
549
  "process.exit(0);"
514
550
  "}"
515
551
  "let body='';"
@@ -526,7 +562,9 @@ class ClaudeCodeHarnessAdapter(HarnessAdapter):
526
562
  "response.setEncoding('utf8');"
527
563
  "response.on('data',chunk=>{responseBody+=chunk;});"
528
564
  "response.on('end',()=>{"
529
- "if(response.statusCode>=200&&response.statusCode<300){process.stdout.write(responseBody);process.exit(0);}"
565
+ "if(response.statusCode>=200&&response.statusCode<300){"
566
+ "if(shouldSuppressOutput(data,responseBody)){process.exit(0);}"
567
+ "process.stdout.write(responseBody);process.exit(0);}"
530
568
  "fail(`daemon returned HTTP ${response.statusCode||0}`);"
531
569
  "});"
532
570
  "});"
@@ -672,6 +710,7 @@ class ClaudeCodeHarnessAdapter(HarnessAdapter):
672
710
  for key in (
673
711
  "SessionStart",
674
712
  "PreToolUse",
713
+ "PermissionRequest",
675
714
  "PostToolUse",
676
715
  "UserPromptSubmit",
677
716
  "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="require-reapproval",
1199
+ event_name="PermissionRequest",
1200
+ reason="HOL Guard is keeping Claude's native permission prompt open for user review.",
1201
+ system_message=_claude_permission_prompt_system_message(payload=payload, notice=notice),
1202
+ additional_context=_claude_permission_prompt_additional_context(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,7 +1222,6 @@ 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",
@@ -1330,6 +1346,14 @@ def run_guard_command(
1330
1346
  "risk_headline": incident["risk_headline"],
1331
1347
  "path_summary": _runtime_requested_path(runtime_artifact),
1332
1348
  }
1349
+ if (
1350
+ _canonical_harness_name(args.harness) == "claude-code"
1351
+ and event_name == "UserPromptSubmit"
1352
+ and policy_action == "require-reapproval"
1353
+ and not _prompt_requires_hard_block(runtime_artifact)
1354
+ and (not getattr(args, "json", False) or output_stream is not None)
1355
+ ):
1356
+ return 0
1333
1357
  if policy_action in {"block", "sandbox-required", "require-reapproval"}:
1334
1358
  native_reason = _runtime_artifact_native_reason(runtime_artifact, response_payload)
1335
1359
  additional_context = _claude_prompt_additional_context(
@@ -1704,6 +1728,11 @@ def _should_emit_prequeue_native_hook_response(
1704
1728
  return output_stream is not None
1705
1729
 
1706
1730
 
1731
+ def _emit_claude_permission_request_passthrough(*, output_stream: TextIO | None = None) -> None:
1732
+ if output_stream is not None:
1733
+ output_stream.write("")
1734
+
1735
+
1707
1736
  def _claude_permission_notice_state_key(session_id: str, tool_name: str | None = None) -> str:
1708
1737
  if tool_name is not None:
1709
1738
  return f"claude_permission_notice:{session_id}:{tool_name}"
@@ -1812,6 +1841,46 @@ def _load_claude_permission_notice(store: GuardStore, payload: dict[str, object]
1812
1841
  return None
1813
1842
 
1814
1843
 
1844
+ def _peek_claude_permission_notice(store: GuardStore, payload: dict[str, object]) -> dict[str, object] | None:
1845
+ session_id = _optional_string(payload.get("session_id"))
1846
+ if session_id is None:
1847
+ return None
1848
+ tool_name = _claude_notification_tool_name(payload)
1849
+ try:
1850
+ persisted = store.get_sync_payload(_claude_permission_notice_state_key(session_id, tool_name))
1851
+ if persisted is None and tool_name is not None:
1852
+ persisted = store.get_sync_payload(_claude_permission_notice_state_key(session_id))
1853
+ except (OSError, sqlite3.Error):
1854
+ return None
1855
+ return persisted if isinstance(persisted, dict) else None
1856
+
1857
+
1858
+ def _mark_claude_pending_permission_prompt_seen(
1859
+ *,
1860
+ store: GuardStore,
1861
+ payload: dict[str, object],
1862
+ notice: dict[str, object] | None,
1863
+ ) -> None:
1864
+ session_id = _optional_string(payload.get("session_id"))
1865
+ artifact_id = _optional_string((notice or {}).get("artifact_id"))
1866
+ if session_id is None or artifact_id is None:
1867
+ return
1868
+ pending_key = _claude_pending_permission_state_key(session_id, artifact_id)
1869
+ try:
1870
+ pending = store.get_sync_payload(pending_key)
1871
+ except (OSError, sqlite3.Error):
1872
+ return
1873
+ if not isinstance(pending, dict):
1874
+ return
1875
+ updated = dict(pending)
1876
+ updated["permission_prompt_seen"] = True
1877
+ updated["permission_prompt_seen_at"] = _now()
1878
+ try:
1879
+ store.set_sync_payload(pending_key, updated, _now())
1880
+ except (OSError, sqlite3.Error):
1881
+ return
1882
+
1883
+
1815
1884
  def _load_claude_pending_permission(
1816
1885
  store: GuardStore,
1817
1886
  payload: dict[str, object],
@@ -1963,6 +2032,8 @@ def _persist_claude_pending_permission_denials(store: GuardStore, payload: dict[
1963
2032
  continue
1964
2033
  if not isinstance(pending, dict):
1965
2034
  continue
2035
+ if pending.get("permission_prompt_seen") is not True:
2036
+ continue
1966
2037
  artifact_id = _optional_string(pending.get("artifact_id"))
1967
2038
  artifact_hash_value = _optional_string(pending.get("artifact_hash"))
1968
2039
  if artifact_id is None or artifact_hash_value is None:
@@ -2019,6 +2090,10 @@ def _is_claude_permission_prompt_notification(args: argparse.Namespace, payload:
2019
2090
  )
2020
2091
 
2021
2092
 
2093
+ def _is_claude_permission_request(args: argparse.Namespace, payload: dict[str, object]) -> bool:
2094
+ return _canonical_harness_name(args.harness) == "claude-code" and _hook_event_name(payload) == "PermissionRequest"
2095
+
2096
+
2022
2097
  def _claude_permission_prompt_system_message(
2023
2098
  *,
2024
2099
  payload: dict[str, object],
@@ -2377,7 +2452,7 @@ def _emit_native_hook_response(
2377
2452
  if payload:
2378
2453
  _write_json_line(payload, output_stream=output_stream)
2379
2454
  return
2380
- if event_name == "Notification":
2455
+ if event_name in {"Notification", "PermissionRequest"}:
2381
2456
  if additional_context:
2382
2457
  payload["hookSpecificOutput"] = {
2383
2458
  "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.62"
@@ -47,7 +47,7 @@ def _runtime_hook_handlers(payload: dict[str, object]) -> list[dict[str, object]
47
47
  hooks = payload["hooks"]
48
48
  assert isinstance(hooks, dict)
49
49
  handlers: list[dict[str, object]] = []
50
- for key in ("PreToolUse", "PostToolUse", "UserPromptSubmit", "Notification", "Stop"):
50
+ for key in ("PreToolUse", "PermissionRequest", "PostToolUse", "UserPromptSubmit", "Notification", "Stop"):
51
51
  entries = hooks[key]
52
52
  assert isinstance(entries, list)
53
53
  for entry in entries:
@@ -144,6 +144,7 @@ def test_claude_install_writes_session_start_and_command_hook_schema_and_is_idem
144
144
  payload = json.loads(settings_path.read_text(encoding="utf-8"))
145
145
  session_start = payload["hooks"]["SessionStart"]
146
146
  pre_tool_use = payload["hooks"]["PreToolUse"]
147
+ permission_request = payload["hooks"]["PermissionRequest"]
147
148
  post_tool_use = payload["hooks"]["PostToolUse"]
148
149
  prompt_submit = payload["hooks"]["UserPromptSubmit"]
149
150
  notification = payload["hooks"]["Notification"]
@@ -157,6 +158,10 @@ def test_claude_install_writes_session_start_and_command_hook_schema_and_is_idem
157
158
  assert CLAUDE_GUARD_DAEMON_HOOK_MARKER in pre_tool_use[0]["hooks"][0]["command"]
158
159
  assert "url" not in pre_tool_use[0]["hooks"][0]
159
160
  assert pre_tool_use[0]["hooks"][0]["timeout"] == 30
161
+ assert len(permission_request) == 1
162
+ assert permission_request[0]["matcher"] == "Bash|Read|Write|Edit|MultiEdit|WebFetch|WebSearch|mcp__.*"
163
+ assert permission_request[0]["hooks"][0]["type"] == "command"
164
+ assert permission_request[0]["hooks"][0]["timeout"] == 10
160
165
  assert len(post_tool_use) == 1
161
166
  assert post_tool_use[0]["hooks"][0]["type"] == "command"
162
167
  assert len(prompt_submit) == 1
@@ -249,6 +254,7 @@ def test_claude_install_replaces_legacy_http_guard_hooks(tmp_path):
249
254
  "command",
250
255
  "command",
251
256
  "command",
257
+ "command",
252
258
  ]
253
259
  assert all(CLAUDE_GUARD_DAEMON_HOOK_MARKER in str(handler.get("command", "")) for handler in installed_handlers)
254
260
  assert all("url" not in handler for handler in installed_handlers)
@@ -365,12 +371,60 @@ def test_claude_daemon_hook_command_survives_shell_execution(tmp_path):
365
371
  check=False,
366
372
  )
367
373
 
374
+ assert result.returncode == 0
375
+ assert result.stderr == ""
376
+ assert result.stdout == ""
377
+
378
+
379
+ def test_claude_daemon_hook_command_falls_back_without_blocking_prompt_on_daemon_miss(tmp_path):
380
+ context = _build_context(tmp_path)
381
+ adapter = ClaudeCodeHarnessAdapter()
382
+ command = adapter._daemon_hook_command(context)
383
+
384
+ result = subprocess.run(
385
+ ["/bin/sh", "-c", command],
386
+ input=json.dumps(
387
+ {
388
+ "hook_event_name": "UserPromptSubmit",
389
+ "prompt": "Use the Read tool to open ./.env and print the full file contents exactly.",
390
+ }
391
+ ),
392
+ text=True,
393
+ capture_output=True,
394
+ timeout=5,
395
+ check=False,
396
+ )
397
+ assert result.returncode == 0
398
+ assert result.stderr == ""
399
+ assert result.stdout == ""
400
+
401
+
402
+ def test_claude_daemon_hook_command_falls_back_to_native_ask_on_daemon_miss(tmp_path):
403
+ context = _build_context(tmp_path)
404
+ adapter = ClaudeCodeHarnessAdapter()
405
+ command = adapter._daemon_hook_command(context)
406
+
407
+ result = subprocess.run(
408
+ ["/bin/sh", "-c", command],
409
+ input=json.dumps(
410
+ {
411
+ "hook_event_name": "PreToolUse",
412
+ "tool_name": "Read",
413
+ "tool_input": {"file_path": str(context.workspace_dir / ".env")},
414
+ }
415
+ ),
416
+ text=True,
417
+ capture_output=True,
418
+ timeout=5,
419
+ check=False,
420
+ )
368
421
  payload = json.loads(result.stdout)
369
422
 
370
423
  assert result.returncode == 0
371
424
  assert result.stderr == ""
372
- assert payload["decision"] == "block"
373
- assert "HOL Guard could not evaluate this action" in payload["reason"]
425
+ assert payload["hookSpecificOutput"]["hookEventName"] == "PreToolUse"
426
+ assert payload["hookSpecificOutput"]["permissionDecision"] == "ask"
427
+ assert "HOL Guard" in payload["hookSpecificOutput"]["permissionDecisionReason"]
374
428
 
375
429
 
376
430
  def test_claude_install_replaces_prior_session_start_guard_handlers_when_context_changes(tmp_path):
@@ -4317,6 +4317,21 @@ def test_guard_hook_claude_stop_persists_unapproved_native_prompt_as_denied(tmp_
4317
4317
  capsys=capsys,
4318
4318
  monkeypatch=monkeypatch,
4319
4319
  )
4320
+ notification_rc, notification_output = _run_guard_hook(
4321
+ home_dir=home_dir,
4322
+ workspace_dir=workspace_dir,
4323
+ harness="claude-code",
4324
+ event={
4325
+ "session_id": "session-claude-deny",
4326
+ "hook_event_name": "Notification",
4327
+ "notification_type": "permission_prompt",
4328
+ "title": "Permission needed",
4329
+ "message": "Claude needs your permission to use Read",
4330
+ "tool_name": "Read",
4331
+ },
4332
+ capsys=capsys,
4333
+ monkeypatch=monkeypatch,
4334
+ )
4320
4335
  stop_rc, stop_output = _run_guard_hook(
4321
4336
  home_dir=home_dir,
4322
4337
  workspace_dir=workspace_dir,
@@ -4338,6 +4353,8 @@ def test_guard_hook_claude_stop_persists_unapproved_native_prompt_as_denied(tmp_
4338
4353
 
4339
4354
  assert first_rc == 0
4340
4355
  assert first_payload["hookSpecificOutput"]["permissionDecision"] == "ask"
4356
+ assert notification_rc == 0
4357
+ assert "HOL Guard intercepted Claude's attempt to use Read" in json.loads(notification_output)["systemMessage"]
4341
4358
  assert stop_rc == 0
4342
4359
  assert stop_output == ""
4343
4360
  assert second_rc == 0
@@ -4345,6 +4362,59 @@ def test_guard_hook_claude_stop_persists_unapproved_native_prompt_as_denied(tmp_
4345
4362
  assert "HOL Guard blocked Claude's attempt to use Read" in second_payload["systemMessage"]
4346
4363
 
4347
4364
 
4365
+ def test_guard_hook_claude_stop_does_not_persist_denial_without_visible_prompt(
4366
+ tmp_path,
4367
+ capsys,
4368
+ monkeypatch,
4369
+ ):
4370
+ home_dir = tmp_path / "home"
4371
+ workspace_dir = tmp_path / "workspace"
4372
+ _build_guard_fixture(home_dir, workspace_dir)
4373
+ first_event = {
4374
+ "session_id": "session-claude-headless",
4375
+ "hook_event_name": "PreToolUse",
4376
+ "tool_name": "Read",
4377
+ "tool_input": {"file_path": str(workspace_dir / ".env")},
4378
+ "source_scope": "project",
4379
+ }
4380
+ monkeypatch.setattr(guard_commands_module, "ensure_guard_daemon", lambda _guard_home: "http://127.0.0.1:4455")
4381
+
4382
+ first_rc, first_output = _run_guard_hook(
4383
+ home_dir=home_dir,
4384
+ workspace_dir=workspace_dir,
4385
+ harness="claude-code",
4386
+ event=first_event,
4387
+ capsys=capsys,
4388
+ monkeypatch=monkeypatch,
4389
+ )
4390
+ stop_rc, stop_output = _run_guard_hook(
4391
+ home_dir=home_dir,
4392
+ workspace_dir=workspace_dir,
4393
+ harness="claude-code",
4394
+ event={"session_id": "session-claude-headless", "hook_event_name": "Stop", "stop_hook_active": False},
4395
+ capsys=capsys,
4396
+ monkeypatch=monkeypatch,
4397
+ )
4398
+ second_rc, second_output = _run_guard_hook(
4399
+ home_dir=home_dir,
4400
+ workspace_dir=workspace_dir,
4401
+ harness="claude-code",
4402
+ event={**first_event, "session_id": "session-claude-headless-next"},
4403
+ capsys=capsys,
4404
+ monkeypatch=monkeypatch,
4405
+ )
4406
+ first_payload = json.loads(first_output)
4407
+ second_payload = json.loads(second_output)
4408
+
4409
+ assert first_rc == 0
4410
+ assert first_payload["hookSpecificOutput"]["permissionDecision"] == "ask"
4411
+ assert stop_rc == 0
4412
+ assert stop_output == ""
4413
+ assert second_rc == 0
4414
+ assert second_payload["hookSpecificOutput"]["permissionDecision"] == "ask"
4415
+ assert "HOL Guard intercepted Claude's attempt to use Read" in second_payload["systemMessage"]
4416
+
4417
+
4348
4418
  def test_guard_hook_emits_claude_native_ask_response_for_claude_alias(tmp_path, capsys, monkeypatch):
4349
4419
  home_dir = tmp_path / "home"
4350
4420
  workspace_dir = tmp_path / "workspace"
@@ -4521,15 +4591,9 @@ def test_guard_hook_brands_claude_user_prompt_submit_before_native_approval(
4521
4591
  monkeypatch=monkeypatch,
4522
4592
  )
4523
4593
  receipts = GuardStore(home_dir).list_receipts()
4524
- payload = json.loads(output)
4525
- hook_output = payload["hookSpecificOutput"]
4526
4594
 
4527
4595
  assert rc == 0
4528
- assert "decision" not in payload
4529
- assert "HOL Guard intercepted this prompt" in payload["systemMessage"]
4530
- assert hook_output["hookEventName"] == "UserPromptSubmit"
4531
- assert "HOL Guard intercepted Claude's next attempt to access local secrets" in hook_output["additionalContext"]
4532
- assert "approval dialog" in hook_output["additionalContext"]
4596
+ assert output == ""
4533
4597
  assert any(receipt["artifact_id"].startswith("claude-code:session:prompt") for receipt in receipts)
4534
4598
 
4535
4599
 
@@ -4554,15 +4618,9 @@ def test_guard_hook_brands_generic_claude_user_prompt_submit_before_native_appro
4554
4618
  capsys=capsys,
4555
4619
  monkeypatch=monkeypatch,
4556
4620
  )
4557
- payload = json.loads(output)
4558
- hook_output = payload["hookSpecificOutput"]
4559
4621
 
4560
4622
  assert rc == 0
4561
- assert "decision" not in payload
4562
- assert "HOL Guard intercepted this prompt" in payload["systemMessage"]
4563
- assert hook_output["hookEventName"] == "UserPromptSubmit"
4564
- assert "HOL Guard intercepted Claude's next sensitive action" in hook_output["additionalContext"]
4565
- assert "approval dialog" in hook_output["additionalContext"]
4623
+ assert output == ""
4566
4624
 
4567
4625
 
4568
4626
  def test_guard_hook_emits_json_for_claude_user_prompt_submit_overridable_prompts(
@@ -4634,7 +4692,11 @@ def test_guard_hook_emits_claude_user_prompt_submit_block_reason_without_continu
4634
4692
  assert "blocked this prompt" in output["reason"].lower()
4635
4693
 
4636
4694
 
4637
- def test_guard_hook_emits_claude_user_prompt_submit_block_response(tmp_path, capsys, monkeypatch):
4695
+ def test_guard_hook_hard_blocks_claude_user_prompt_submit_bypass(
4696
+ tmp_path,
4697
+ capsys,
4698
+ monkeypatch,
4699
+ ):
4638
4700
  home_dir = tmp_path / "home"
4639
4701
  workspace_dir = tmp_path / "workspace"
4640
4702
  _build_guard_fixture(home_dir, workspace_dir)
@@ -4783,11 +4845,87 @@ def test_guard_hook_emits_claude_notification_notice_for_permission_prompt(tmp_p
4783
4845
 
4784
4846
  assert pre_tool_rc == 0
4785
4847
  assert pre_tool_output["hookSpecificOutput"]["permissionDecision"] == "ask"
4786
- assert notification_rc == 2
4787
- assert notification_capture.out == ""
4848
+ notification_payload = json.loads(notification_capture.out)
4849
+
4850
+ assert notification_rc == 0
4788
4851
  assert "HOL Guard opened this Claude approval prompt for Read." in notification_capture.err
4789
4852
  assert "protect your local secrets" in notification_capture.err
4790
- assert "choose Yes, Yes during this session, or No" in notification_capture.err
4853
+ assert "HOL Guard intercepted Claude's attempt to use Read" in notification_payload["systemMessage"]
4854
+ assert "came from HOL Guard, not from Claude alone" in notification_payload["systemMessage"]
4855
+ assert "Yes during this session" in notification_payload["systemMessage"]
4856
+ assert "No to keep the sensitive action blocked" in notification_payload["systemMessage"]
4857
+
4858
+
4859
+ def test_guard_hook_emits_claude_permission_request_attribution_without_decision(
4860
+ tmp_path,
4861
+ capsys,
4862
+ monkeypatch,
4863
+ ):
4864
+ home_dir = tmp_path / "home"
4865
+ workspace_dir = tmp_path / "workspace"
4866
+ _build_guard_fixture(home_dir, workspace_dir)
4867
+ pre_tool_event = {
4868
+ "session_id": "session-claude-permission-request",
4869
+ "hook_event_name": "PreToolUse",
4870
+ "tool_name": "Read",
4871
+ "tool_input": {"file_path": str(workspace_dir / ".env")},
4872
+ "source_scope": "project",
4873
+ }
4874
+ monkeypatch.setattr(guard_commands_module, "ensure_guard_daemon", lambda _guard_home: "http://127.0.0.1:4455")
4875
+ pre_tool_rc, pre_tool_output = _run_guard_hook(
4876
+ home_dir=home_dir,
4877
+ workspace_dir=workspace_dir,
4878
+ harness="claude-code",
4879
+ event=pre_tool_event,
4880
+ capsys=capsys,
4881
+ monkeypatch=monkeypatch,
4882
+ )
4883
+
4884
+ permission_rc, permission_output = _run_guard_hook(
4885
+ home_dir=home_dir,
4886
+ workspace_dir=workspace_dir,
4887
+ harness="claude-code",
4888
+ event={**pre_tool_event, "hook_event_name": "PermissionRequest"},
4889
+ capsys=capsys,
4890
+ monkeypatch=monkeypatch,
4891
+ )
4892
+ permission_payload = json.loads(permission_output)
4893
+
4894
+ assert pre_tool_rc == 0
4895
+ assert json.loads(pre_tool_output)["hookSpecificOutput"]["permissionDecision"] == "ask"
4896
+ assert permission_rc == 0
4897
+ assert "HOL Guard intercepted Claude's attempt to use Read" in permission_payload["systemMessage"]
4898
+ assert "came from HOL Guard, not from Claude alone" in permission_payload["systemMessage"]
4899
+ assert permission_payload["hookSpecificOutput"]["hookEventName"] == "PermissionRequest"
4900
+ assert "decision" not in permission_payload["hookSpecificOutput"]
4901
+
4902
+
4903
+ def test_guard_hook_ignores_unattributed_claude_permission_request(
4904
+ tmp_path,
4905
+ capsys,
4906
+ monkeypatch,
4907
+ ):
4908
+ home_dir = tmp_path / "home"
4909
+ workspace_dir = tmp_path / "workspace"
4910
+ _build_guard_fixture(home_dir, workspace_dir)
4911
+
4912
+ rc, output = _run_guard_hook(
4913
+ home_dir=home_dir,
4914
+ workspace_dir=workspace_dir,
4915
+ harness="claude-code",
4916
+ event={
4917
+ "session_id": "session-claude-unattributed-permission-request",
4918
+ "hook_event_name": "PermissionRequest",
4919
+ "tool_name": "Read",
4920
+ "tool_input": {"file_path": str(workspace_dir / "README.md")},
4921
+ "source_scope": "project",
4922
+ },
4923
+ capsys=capsys,
4924
+ monkeypatch=monkeypatch,
4925
+ )
4926
+
4927
+ assert rc == 0
4928
+ assert output == ""
4791
4929
 
4792
4930
 
4793
4931
  def test_guard_hook_emits_claude_native_ask_for_sensitive_file_reads(
@@ -251,18 +251,9 @@ class TestGuardSurfaceServer:
251
251
  finally:
252
252
  daemon.stop()
253
253
 
254
- assert "decision" not in hook_payload
255
- assert "HOL Guard intercepted this prompt" in hook_payload["systemMessage"]
256
- assert hook_payload["hookSpecificOutput"]["hookEventName"] == "UserPromptSubmit"
257
- assert (
258
- "HOL Guard intercepted Claude's next attempt to access local secrets"
259
- in hook_payload["hookSpecificOutput"]["additionalContext"]
260
- )
261
- assert "approval dialog" in hook_payload["hookSpecificOutput"]["additionalContext"]
254
+ assert hook_payload == {}
262
255
 
263
- def test_guard_daemon_claude_hook_endpoint_returns_native_user_prompt_submit_block(
264
- self, tmp_path
265
- ) -> None:
256
+ def test_guard_daemon_claude_hook_endpoint_blocks_guard_bypass_user_prompt_submit(self, tmp_path) -> None:
266
257
  home_dir = tmp_path / "home"
267
258
  workspace_dir = tmp_path / "workspace"
268
259
  workspace_dir.mkdir(parents=True, exist_ok=True)
@@ -291,10 +282,7 @@ class TestGuardSurfaceServer:
291
282
  daemon.stop()
292
283
 
293
284
  assert hook_payload["decision"] == "block"
294
- assert (
295
- hook_payload["reason"]
296
- == "HOL Guard blocked this prompt because it asks to bypass or disable Guard."
297
- )
285
+ assert "bypass" in hook_payload["reason"].lower() or "disable" in hook_payload["reason"].lower()
298
286
 
299
287
  def test_guard_daemon_background_start_auto_stops_after_idle_timeout(self, tmp_path) -> None:
300
288
  guard_home = tmp_path / "pytest-of-user" / "guard-home"