plugin-scanner 2.0.66__tar.gz → 2.0.68__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 (321) hide show
  1. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/PKG-INFO +1 -1
  2. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/pyproject.toml +1 -1
  3. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/pyproject.toml.bak +1 -1
  4. plugin_scanner-2.0.68/src/codex_plugin_scanner/guard/cli/approval_commands.py +119 -0
  5. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/cli/commands.py +205 -40
  6. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/cli/render.py +21 -0
  7. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/store.py +16 -0
  8. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/version.py +1 -1
  9. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_guard_approvals.py +180 -0
  10. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_guard_runtime.py +685 -67
  11. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_guard_surface_server.py +1 -1
  12. plugin_scanner-2.0.66/src/codex_plugin_scanner/guard/cli/approval_commands.py +0 -63
  13. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/.clusterfuzzlite/Dockerfile +0 -0
  14. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/.clusterfuzzlite/build.sh +0 -0
  15. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/.clusterfuzzlite/project.yaml +0 -0
  16. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/.clusterfuzzlite/requirements-atheris.txt +0 -0
  17. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/.dockerignore +0 -0
  18. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/.github/CODEOWNERS +0 -0
  19. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/.github/ISSUE_TEMPLATE/bug-report.yml +0 -0
  20. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  21. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
  22. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/.github/dependabot.yml +0 -0
  23. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/.github/workflows/ci.yml +0 -0
  24. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/.github/workflows/codeql.yml +0 -0
  25. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/.github/workflows/dependabot-uv-lock.yml +0 -0
  26. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/.github/workflows/fuzz.yml +0 -0
  27. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/.github/workflows/harness-smoke.yml +0 -0
  28. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/.github/workflows/publish.yml +0 -0
  29. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/.github/workflows/scorecard.yml +0 -0
  30. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/.gitignore +0 -0
  31. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/.pre-commit-hooks.yaml +0 -0
  32. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/CONTRIBUTING.md +0 -0
  33. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/Dockerfile +0 -0
  34. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/LICENSE +0 -0
  35. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/README.md +0 -0
  36. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/SECURITY.md +0 -0
  37. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/dashboard/index.html +0 -0
  38. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/dashboard/package.json +0 -0
  39. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/dashboard/pnpm-lock.yaml +0 -0
  40. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/dashboard/public/brand/Logo_Whole.png +0 -0
  41. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/dashboard/src/app.tsx +0 -0
  42. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/dashboard/src/approval-center-layout.tsx +0 -0
  43. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/dashboard/src/approval-center-primitives.tsx +0 -0
  44. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/dashboard/src/approval-center-utils.ts +0 -0
  45. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/dashboard/src/fleet-workspace.tsx +0 -0
  46. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/dashboard/src/guard-api.ts +0 -0
  47. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/dashboard/src/guard-demo.ts +0 -0
  48. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/dashboard/src/guard-types.ts +0 -0
  49. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/dashboard/src/main.tsx +0 -0
  50. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/dashboard/src/receipts-workspace.tsx +0 -0
  51. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/dashboard/src/runtime-overview.tsx +0 -0
  52. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/dashboard/src/styles.css +0 -0
  53. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/dashboard/src/vite-env.d.ts +0 -0
  54. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/dashboard/tsconfig.json +0 -0
  55. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/dashboard/vite.config.ts +0 -0
  56. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/docker-requirements.txt +0 -0
  57. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/docs/guard/approval-audit.md +0 -0
  58. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/docs/guard/architecture.md +0 -0
  59. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/docs/guard/get-started.md +0 -0
  60. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/docs/guard/harness-support.md +0 -0
  61. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/docs/guard/local-vs-cloud.md +0 -0
  62. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/docs/guard/testing-matrix.md +0 -0
  63. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/docs/trust/mcp-trust-draft.md +0 -0
  64. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/docs/trust/plugin-trust-draft.md +0 -0
  65. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/docs/trust/skill-trust-local.md +0 -0
  66. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/fuzzers/manifest_fuzzer.py +0 -0
  67. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/requirements.txt +0 -0
  68. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/schemas/plugin-quality.v1.json +0 -0
  69. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/schemas/scan-result.v1.json +0 -0
  70. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/schemas/verify-result.v1.json +0 -0
  71. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/__init__.py +0 -0
  72. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/action_runner.py +0 -0
  73. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/argparse_utils.py +0 -0
  74. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/checks/__init__.py +0 -0
  75. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/checks/best_practices.py +0 -0
  76. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/checks/claude.py +0 -0
  77. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/checks/code_quality.py +0 -0
  78. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/checks/ecosystem_common.py +0 -0
  79. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/checks/gemini.py +0 -0
  80. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/checks/manifest.py +0 -0
  81. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/checks/manifest_support.py +0 -0
  82. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/checks/marketplace.py +0 -0
  83. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/checks/mcp_security.py +0 -0
  84. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/checks/opencode.py +0 -0
  85. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/checks/operational_security.py +0 -0
  86. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/checks/security.py +0 -0
  87. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/checks/skill_security.py +0 -0
  88. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/cli.py +0 -0
  89. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/cli_ui.py +0 -0
  90. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/config.py +0 -0
  91. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/ecosystems/__init__.py +0 -0
  92. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/ecosystems/base.py +0 -0
  93. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/ecosystems/claude.py +0 -0
  94. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/ecosystems/codex.py +0 -0
  95. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/ecosystems/detect.py +0 -0
  96. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/ecosystems/gemini.py +0 -0
  97. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/ecosystems/opencode.py +0 -0
  98. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/ecosystems/registry.py +0 -0
  99. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/ecosystems/types.py +0 -0
  100. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/github_reporting.py +0 -0
  101. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/__init__.py +0 -0
  102. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/adapters/__init__.py +0 -0
  103. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/adapters/antigravity.py +0 -0
  104. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/adapters/base.py +0 -0
  105. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/adapters/claude_code.py +0 -0
  106. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/adapters/codex.py +0 -0
  107. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/adapters/copilot.py +0 -0
  108. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/adapters/cursor.py +0 -0
  109. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/adapters/gemini.py +0 -0
  110. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/adapters/hermes.py +0 -0
  111. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/adapters/mcp_servers.py +0 -0
  112. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/adapters/opencode.py +0 -0
  113. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/adapters/opencode_artifacts.py +0 -0
  114. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/approvals.py +0 -0
  115. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/bridge/__init__.py +0 -0
  116. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/capabilities.py +0 -0
  117. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/cli/__init__.py +0 -0
  118. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/cli/bootstrap.py +0 -0
  119. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/cli/connect_flow.py +0 -0
  120. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/cli/install_commands.py +0 -0
  121. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/cli/product.py +0 -0
  122. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/cli/prompt.py +0 -0
  123. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/cli/update_commands.py +0 -0
  124. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/codex_config.py +0 -0
  125. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/config.py +0 -0
  126. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/consumer/__init__.py +0 -0
  127. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/consumer/service.py +0 -0
  128. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/daemon/__init__.py +0 -0
  129. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/daemon/client.py +0 -0
  130. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/daemon/manager.py +0 -0
  131. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/daemon/server.py +0 -0
  132. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/daemon/static/assets/guard-dashboard.js +0 -0
  133. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/daemon/static/assets/index.css +0 -0
  134. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Whole.png +0 -0
  135. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/daemon/static/index.html +0 -0
  136. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/edge_events.py +0 -0
  137. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/incident.py +0 -0
  138. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/launcher.py +0 -0
  139. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/mcp_tool_calls.py +0 -0
  140. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/models.py +0 -0
  141. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/policy/__init__.py +0 -0
  142. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/policy/engine.py +0 -0
  143. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/protect.py +0 -0
  144. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/proxy/__init__.py +0 -0
  145. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/proxy/remote.py +0 -0
  146. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/proxy/runtime_mcp.py +0 -0
  147. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/proxy/stdio.py +0 -0
  148. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/receipts/__init__.py +0 -0
  149. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/receipts/manager.py +0 -0
  150. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/risk.py +0 -0
  151. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/runtime/__init__.py +0 -0
  152. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/runtime/runner.py +0 -0
  153. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/runtime/secret_file_requests.py +0 -0
  154. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/runtime/surface_server.py +0 -0
  155. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/schemas/__init__.py +0 -0
  156. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/schemas/consumer_mode.py +0 -0
  157. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/schemas/guard_event_v1.py +0 -0
  158. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/schemas/surface_server.py +0 -0
  159. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/shims.py +0 -0
  160. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/store_approvals.py +0 -0
  161. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/store_connect.py +0 -0
  162. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/guard/types.py +0 -0
  163. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/integrations/__init__.py +0 -0
  164. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/integrations/cisco_mcp_scanner.py +0 -0
  165. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/integrations/cisco_skill_scanner.py +0 -0
  166. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/lint_fixes.py +0 -0
  167. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/marketplace_support.py +0 -0
  168. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/models.py +0 -0
  169. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/path_support.py +0 -0
  170. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/policy.py +0 -0
  171. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/quality_artifact.py +0 -0
  172. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/repo_detect.py +0 -0
  173. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/reporting.py +0 -0
  174. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/rules/__init__.py +0 -0
  175. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/rules/registry.py +0 -0
  176. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/rules/specs.py +0 -0
  177. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/scanner.py +0 -0
  178. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/submission.py +0 -0
  179. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/suppressions.py +0 -0
  180. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/trust_domain_scoring.py +0 -0
  181. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/trust_helpers.py +0 -0
  182. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/trust_mcp_scoring.py +0 -0
  183. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/trust_models.py +0 -0
  184. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/trust_plugin_scoring.py +0 -0
  185. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/trust_scoring.py +0 -0
  186. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/trust_skill_scoring.py +0 -0
  187. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/trust_specs.py +0 -0
  188. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/src/codex_plugin_scanner/verification.py +0 -0
  189. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/__init__.py +0 -0
  190. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/conftest.py +0 -0
  191. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/__init__.py +0 -0
  192. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/bad-plugin/.codex-plugin/plugin.json +0 -0
  193. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/bad-plugin/.mcp.json +0 -0
  194. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/bad-plugin/secrets.js +0 -0
  195. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/claude-plugin-good/.claude-plugin/plugin.json +0 -0
  196. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/claude-plugin-good/LICENSE +0 -0
  197. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/claude-plugin-good/README.md +0 -0
  198. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/claude-plugin-good/SECURITY.md +0 -0
  199. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/claude-plugin-good/hooks/hooks.json +0 -0
  200. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/claude-plugin-good/skills/example/SKILL.md +0 -0
  201. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/code-quality-bad/evil.js +0 -0
  202. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/code-quality-bad/inject.js +0 -0
  203. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/gemini-extension-good/GEMINI.md +0 -0
  204. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/gemini-extension-good/LICENSE +0 -0
  205. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/gemini-extension-good/README.md +0 -0
  206. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/gemini-extension-good/SECURITY.md +0 -0
  207. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/gemini-extension-good/commands/hello.toml +0 -0
  208. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/gemini-extension-good/gemini-extension.json +0 -0
  209. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/good-plugin/.codex-plugin/plugin.json +0 -0
  210. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/good-plugin/.codexignore +0 -0
  211. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/good-plugin/LICENSE +0 -0
  212. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/good-plugin/README.md +0 -0
  213. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/good-plugin/SECURITY.md +0 -0
  214. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/good-plugin/assets/icon.svg +0 -0
  215. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/good-plugin/assets/logo.svg +0 -0
  216. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/good-plugin/assets/screenshot.svg +0 -0
  217. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/good-plugin/skills/example/SKILL.md +0 -0
  218. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/guard-codex-malicious-mcp/.codex/config.toml +0 -0
  219. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/hermes-plugin-evil/config.yaml +0 -0
  220. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/hermes-plugin-evil/mcp_servers.json +0 -0
  221. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/hermes-plugin-evil/skills/security/malicious/SKILL.md +0 -0
  222. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/SKILL.md +0 -0
  223. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/references/api-setup.md +0 -0
  224. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/scripts/deploy.sh +0 -0
  225. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/hermes-plugin-evil/skills/utils/benign/SKILL.md +0 -0
  226. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/malformed-json/.codex-plugin/plugin.json +0 -0
  227. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/malicious-skill-plugin/.codex-plugin/plugin.json +0 -0
  228. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/malicious-skill-plugin/.codexignore +0 -0
  229. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/malicious-skill-plugin/LICENSE +0 -0
  230. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/malicious-skill-plugin/README.md +0 -0
  231. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/malicious-skill-plugin/SECURITY.md +0 -0
  232. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/malicious-skill-plugin/skills/leaky-skill/SKILL.md +0 -0
  233. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/mcp-canary-server.py +0 -0
  234. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/minimal-plugin/.codex-plugin/plugin.json +0 -0
  235. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/missing-fields/.codex-plugin/plugin.json +0 -0
  236. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/mit-license/LICENSE +0 -0
  237. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/multi-ecosystem-repo/codex-plugin/.codex-plugin/plugin.json +0 -0
  238. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/multi-ecosystem-repo/codex-plugin/LICENSE +0 -0
  239. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/multi-ecosystem-repo/codex-plugin/README.md +0 -0
  240. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/multi-ecosystem-repo/codex-plugin/SECURITY.md +0 -0
  241. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/multi-ecosystem-repo/gemini-ext/README.md +0 -0
  242. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/multi-ecosystem-repo/gemini-ext/gemini-extension.json +0 -0
  243. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/multi-plugin-repo/.agents/plugins/marketplace.json +0 -0
  244. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codex-plugin/plugin.json +0 -0
  245. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codexignore +0 -0
  246. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/LICENSE +0 -0
  247. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/README.md +0 -0
  248. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/SECURITY.md +0 -0
  249. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/skills/example/SKILL.md +0 -0
  250. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/.codex-plugin/plugin.json +0 -0
  251. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/skills/example/SKILL.md +0 -0
  252. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/no-version/.codex-plugin/plugin.json +0 -0
  253. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/opencode-good/.opencode/commands/hello.md +0 -0
  254. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/opencode-good/.opencode/plugins/example.ts +0 -0
  255. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/opencode-good/LICENSE +0 -0
  256. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/opencode-good/README.md +0 -0
  257. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/opencode-good/SECURITY.md +0 -0
  258. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/opencode-good/opencode.jsonc +0 -0
  259. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/skills-missing-dir/.codex-plugin/plugin.json +0 -0
  260. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/skills-no-frontmatter/.codex-plugin/plugin.json +0 -0
  261. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/skills-no-frontmatter/skills/bad-skill/SKILL.md +0 -0
  262. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/with-marketplace/.codex-plugin/plugin.json +0 -0
  263. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/with-marketplace/marketplace-broken.json +0 -0
  264. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/fixtures/with-marketplace/marketplace.json +0 -0
  265. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test-trust-scoring.py +0 -0
  266. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test-trust-specs.py +0 -0
  267. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_action_runner.py +0 -0
  268. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_best_practices.py +0 -0
  269. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_cisco_install_surfaces.py +0 -0
  270. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_cli.py +0 -0
  271. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_code_quality.py +0 -0
  272. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_config.py +0 -0
  273. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_coverage_remaining.py +0 -0
  274. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_ecosystems.py +0 -0
  275. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_edge_cases.py +0 -0
  276. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_final_coverage.py +0 -0
  277. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_guard_bootstrap.py +0 -0
  278. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_guard_capabilities.py +0 -0
  279. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_guard_claude_adapter.py +0 -0
  280. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_guard_cli.py +0 -0
  281. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_guard_codex_e2e.py +0 -0
  282. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_guard_codex_install.py +0 -0
  283. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_guard_codex_proxy.py +0 -0
  284. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_guard_config_paths.py +0 -0
  285. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_guard_connect_flow.py +0 -0
  286. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_guard_consumer_mode.py +0 -0
  287. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_guard_copilot_adapter.py +0 -0
  288. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_guard_copilot_proxy.py +0 -0
  289. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_guard_daemon_manager.py +0 -0
  290. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_guard_event_schema_v1.py +0 -0
  291. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_guard_events.py +0 -0
  292. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_guard_launch_env.py +0 -0
  293. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_guard_opencode_proxy.py +0 -0
  294. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_guard_product_flow.py +0 -0
  295. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_guard_protect.py +0 -0
  296. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_guard_render.py +0 -0
  297. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_guard_risk.py +0 -0
  298. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_guard_store_migrations.py +0 -0
  299. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_guard_verdicts.py +0 -0
  300. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_hermes_adapter.py +0 -0
  301. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_integration.py +0 -0
  302. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_lint_fixes.py +0 -0
  303. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_live_cisco_smoke.py +0 -0
  304. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_manifest.py +0 -0
  305. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_marketplace.py +0 -0
  306. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_mcp_security.py +0 -0
  307. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_operational_security.py +0 -0
  308. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_policy.py +0 -0
  309. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_quality_artifact.py +0 -0
  310. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_rule_registry.py +0 -0
  311. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_scanner.py +0 -0
  312. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_schema_contracts.py +0 -0
  313. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_security.py +0 -0
  314. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_security_ops.py +0 -0
  315. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_skill_security.py +0 -0
  316. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_submission.py +0 -0
  317. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_trust_scoring.py +0 -0
  318. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_trust_specs.py +0 -0
  319. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_verification.py +0 -0
  320. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/tests/test_versioning.py +0 -0
  321. {plugin_scanner-2.0.66 → plugin_scanner-2.0.68}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plugin-scanner
3
- Version: 2.0.66
3
+ Version: 2.0.68
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.66"
7
+ version = "2.0.68"
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.66"
7
+ version = "2.0.68"
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"
@@ -0,0 +1,119 @@
1
+ """CLI helpers for Guard approval queue workflows."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from datetime import datetime, timezone
7
+ from pathlib import Path
8
+
9
+ from ..approvals import apply_approval_resolution, build_runtime_snapshot
10
+ from ..daemon import load_guard_daemon_url
11
+ from ..store import GuardStore
12
+
13
+
14
+ def add_approval_parser(
15
+ guard_subparsers: argparse._SubParsersAction[argparse.ArgumentParser],
16
+ add_common_args,
17
+ ) -> None:
18
+ approvals_parser = guard_subparsers.add_parser(
19
+ "approvals",
20
+ help="List, resolve, or clear Guard approval history",
21
+ )
22
+ approvals_subparsers = approvals_parser.add_subparsers(dest="approvals_command")
23
+
24
+ add_common_args(approvals_parser)
25
+ approvals_parser.add_argument("--json", action="store_true")
26
+
27
+ for name, action in (("approve", "allow"), ("deny", "block")):
28
+ decision_parser = approvals_subparsers.add_parser(name, help=f"{name.title()} a pending approval request")
29
+ decision_parser.add_argument("request_id")
30
+ decision_parser.add_argument(
31
+ "--scope",
32
+ choices=("artifact", "publisher", "workspace", "harness", "global"),
33
+ default="artifact",
34
+ )
35
+ decision_parser.add_argument("--reason")
36
+ add_common_args(decision_parser)
37
+ decision_parser.add_argument("--json", action="store_true")
38
+ decision_parser.set_defaults(approval_action=action)
39
+
40
+ clear_history_parser = approvals_subparsers.add_parser(
41
+ "clear-history",
42
+ help="Clear saved allow/deny history so flows can be re-tested",
43
+ )
44
+ clear_history_parser.add_argument(
45
+ "--harness",
46
+ help="The harness to clear history for (for example: codex, claude-code, opencode, copilot)",
47
+ )
48
+ clear_history_parser.add_argument(
49
+ "--all",
50
+ action="store_true",
51
+ help="Clear approval history across every harness; cannot be combined with --harness",
52
+ )
53
+ clear_history_parser.add_argument(
54
+ "--source",
55
+ help="Optional source filter for policy decisions (for example: manual, claude-ask-user-question)",
56
+ )
57
+ add_common_args(clear_history_parser)
58
+ clear_history_parser.add_argument("--json", action="store_true")
59
+
60
+
61
+ def run_approval_command(
62
+ args: argparse.Namespace,
63
+ *,
64
+ store: GuardStore,
65
+ workspace: Path | None,
66
+ ) -> dict[str, object]:
67
+ command = getattr(args, "approvals_command", None)
68
+ if command is None:
69
+ return build_runtime_snapshot(
70
+ store=store,
71
+ approval_center_url=load_guard_daemon_url(store.guard_home),
72
+ now=_now(),
73
+ )
74
+ if command == "clear-history":
75
+ harness = getattr(args, "harness", None)
76
+ clear_all = bool(getattr(args, "all", False))
77
+ if clear_all and harness is not None:
78
+ return {
79
+ "history_cleared": False,
80
+ "error": "Choose either --all or --harness <name> when clearing approval history.",
81
+ "cleared_policies": 0,
82
+ "cleared_resolved_requests": 0,
83
+ "exit_code": 2,
84
+ }
85
+ if not clear_all and harness is None:
86
+ return {
87
+ "history_cleared": False,
88
+ "error": "Choose --harness <name> or --all when clearing approval history.",
89
+ "cleared_policies": 0,
90
+ "cleared_resolved_requests": 0,
91
+ "exit_code": 2,
92
+ }
93
+ target_harness = None if clear_all else harness
94
+ source = getattr(args, "source", None)
95
+ cleared_resolved_requests = 0
96
+ if source is None:
97
+ cleared_resolved_requests = store.clear_approval_requests(harness=target_harness, status="resolved")
98
+ return {
99
+ "history_cleared": True,
100
+ "harness": target_harness,
101
+ "source": source,
102
+ "cleared_policies": store.clear_policy_decisions(target_harness, source),
103
+ "cleared_resolved_requests": cleared_resolved_requests,
104
+ "exit_code": 0,
105
+ }
106
+ item = apply_approval_resolution(
107
+ store=store,
108
+ request_id=args.request_id,
109
+ action=args.approval_action,
110
+ scope=args.scope,
111
+ workspace=str(workspace) if workspace is not None else None,
112
+ reason=args.reason,
113
+ now=_now(),
114
+ )
115
+ return {"resolved": True, "item": item}
116
+
117
+
118
+ def _now() -> str:
119
+ return datetime.now(timezone.utc).isoformat()
@@ -7,6 +7,7 @@ import hashlib
7
7
  import json
8
8
  import os
9
9
  import re
10
+ import secrets
10
11
  import shlex
11
12
  import sqlite3
12
13
  import subprocess
@@ -130,6 +131,13 @@ _GUARD_HELP_GROUPS = (
130
131
  " update Update hol-guard in the current environment"
131
132
  )
132
133
 
134
+ _CLAUDE_GUARD_APPROVAL_HEADER = "HOL Guard"
135
+ _CLAUDE_GUARD_APPROVAL_OPTIONS = (
136
+ "Allow once",
137
+ "Allow during this session",
138
+ "Keep blocked",
139
+ )
140
+
133
141
 
134
142
  def _now() -> str:
135
143
  return datetime.now(timezone.utc).isoformat()
@@ -855,7 +863,7 @@ def run_guard_command(
855
863
  if args.guard_command == "approvals":
856
864
  payload = run_approval_command(args, store=store, workspace=workspace)
857
865
  _emit("approvals", payload, getattr(args, "json", False))
858
- return 0
866
+ return int(payload.get("exit_code", 0))
859
867
 
860
868
  if args.guard_command == "explain":
861
869
  payload = _build_explain_payload_with_mode(store, args.target, cisco_mode=args.cisco_mode)
@@ -1859,6 +1867,10 @@ def _append_claude_pending_permission_key(
1859
1867
  )
1860
1868
 
1861
1869
 
1870
+ def _claude_guard_approval_question_text(approval_code: str) -> str:
1871
+ return f"HOL Guard intercepted this sensitive action (approval code: {approval_code}). What should Claude do?"
1872
+
1873
+
1862
1874
  def _record_claude_permission_notice(
1863
1875
  *,
1864
1876
  store: GuardStore,
@@ -1870,9 +1882,12 @@ def _record_claude_permission_notice(
1870
1882
  session_id = _optional_string(payload.get("session_id"))
1871
1883
  if session_id is None:
1872
1884
  return
1885
+ saved_at = _now()
1873
1886
  tool_name = _optional_string(payload.get("tool_name"))
1887
+ approval_code = secrets.token_hex(6)
1888
+ approval_question = _claude_guard_approval_question_text(approval_code)
1874
1889
  notice_payload: dict[str, object] = {
1875
- "saved_at": _now(),
1890
+ "saved_at": saved_at,
1876
1891
  "reason": reason,
1877
1892
  "artifact_id": artifact.artifact_id,
1878
1893
  "artifact_hash": artifact_hash,
@@ -1880,14 +1895,18 @@ def _record_claude_permission_notice(
1880
1895
  "artifact_type": artifact.artifact_type,
1881
1896
  "config_path": artifact.config_path,
1882
1897
  "source_scope": artifact.source_scope,
1898
+ "approval_header": _CLAUDE_GUARD_APPROVAL_HEADER,
1899
+ "approval_question": approval_question,
1900
+ "approval_options": list(_CLAUDE_GUARD_APPROVAL_OPTIONS),
1901
+ "approval_code": approval_code,
1883
1902
  }
1884
1903
  if tool_name is not None:
1885
1904
  notice_payload["tool_name"] = tool_name
1886
1905
  try:
1887
- store.set_sync_payload(_claude_permission_notice_state_key(session_id, tool_name), notice_payload, _now())
1906
+ store.set_sync_payload(_claude_permission_notice_state_key(session_id, tool_name), notice_payload, saved_at)
1888
1907
  pending_key = _claude_pending_permission_state_key(session_id, artifact.artifact_id)
1889
- store.set_sync_payload(pending_key, notice_payload, _now())
1890
- _append_claude_pending_permission_key(store, session_id=session_id, pending_key=pending_key, now=_now())
1908
+ store.set_sync_payload(pending_key, notice_payload, saved_at)
1909
+ _append_claude_pending_permission_key(store, session_id=session_id, pending_key=pending_key, now=saved_at)
1891
1910
  except (OSError, sqlite3.Error):
1892
1911
  return
1893
1912
 
@@ -1903,8 +1922,17 @@ def _load_claude_permission_notice(store: GuardStore, payload: dict[str, object]
1903
1922
  if persisted is None and tool_name is not None:
1904
1923
  selected_key = _claude_permission_notice_state_key(session_id)
1905
1924
  persisted = store.get_sync_payload(selected_key)
1906
- if persisted is not None:
1907
- store.delete_sync_payload(selected_key)
1925
+ if isinstance(persisted, dict):
1926
+ artifact_id = _optional_string(persisted.get("artifact_id"))
1927
+ if artifact_id is not None:
1928
+ pending_key = _claude_pending_permission_state_key(session_id, artifact_id)
1929
+ pending = store.get_sync_payload(pending_key)
1930
+ if not isinstance(pending, dict):
1931
+ store.delete_sync_payload(selected_key)
1932
+ persisted = None
1933
+ else:
1934
+ store.delete_sync_payload(selected_key)
1935
+ persisted = None
1908
1936
  except (OSError, sqlite3.Error):
1909
1937
  return None
1910
1938
  if isinstance(persisted, dict):
@@ -2214,51 +2242,185 @@ def _persist_claude_pending_permission_denials(store: GuardStore, payload: dict[
2214
2242
  def _claude_guard_approval_question_message(notice: dict[str, object] | None) -> str:
2215
2243
  tool_name = _optional_string((notice or {}).get("tool_name")) or "this tool"
2216
2244
  reason = _optional_string((notice or {}).get("reason"))
2245
+ header = _optional_string((notice or {}).get("approval_header")) or _CLAUDE_GUARD_APPROVAL_HEADER
2246
+ question = _optional_string((notice or {}).get("approval_question")) or (
2247
+ "HOL Guard intercepted this sensitive action. What should Claude do?"
2248
+ )
2249
+ options = _claude_guard_approval_options_from_value((notice or {}).get("approval_options"))
2250
+ if not options:
2251
+ options = _CLAUDE_GUARD_APPROVAL_OPTIONS
2252
+ options_text = "', '".join(options)
2217
2253
  reason_text = f" HOL Guard reason: {_ensure_terminal_punctuation(reason)}" if reason is not None else ""
2218
2254
  return (
2219
2255
  f"HOL Guard needs the user's explicit decision before {tool_name} can run.{reason_text} "
2220
2256
  "The native Claude permission prompt is not the final decision surface for this request. Call "
2221
2257
  "AskUserQuestion now with one HOL Guard approval question before retrying the tool. Use header "
2222
- "'HOL Guard', question 'HOL Guard intercepted this sensitive action. What should Claude do?', and exactly "
2223
- "these options: 'Allow once', 'Allow during this session', and 'Keep blocked'. If the user chooses an "
2224
- "allow option, retry the same tool once. If the user chooses Keep blocked, do not retry the sensitive "
2225
- "action."
2258
+ f"'{header}', question '{question}', and exactly these options: '{options_text}'. If the user chooses an "
2259
+ "allow option, retry the same tool once. If the user chooses Keep blocked, do not retry the sensitive action."
2226
2260
  )
2227
2261
 
2228
2262
 
2229
- def _is_claude_guard_approval_question(payload: dict[str, object]) -> bool:
2263
+ def _normalize_claude_guard_approval_text(value: str) -> str:
2264
+ return " ".join(value.strip().lower().split())
2265
+
2266
+
2267
+ def _claude_guard_approval_options_from_value(value: object) -> tuple[str, ...]:
2268
+ if not isinstance(value, list):
2269
+ return ()
2270
+ labels: list[str] = []
2271
+ for item in value:
2272
+ label: str | None
2273
+ if isinstance(item, dict):
2274
+ label = _optional_string(item.get("label"))
2275
+ elif isinstance(item, str):
2276
+ label = item.strip()
2277
+ else:
2278
+ label = None
2279
+ if label is None:
2280
+ return ()
2281
+ labels.append(label)
2282
+ return tuple(labels)
2283
+
2284
+
2285
+ def _claude_guard_prompt_contract_from_pending(
2286
+ pending: dict[str, object],
2287
+ ) -> tuple[str, str, tuple[str, ...]] | None:
2288
+ header = _optional_string(pending.get("approval_header"))
2289
+ question = _optional_string(pending.get("approval_question"))
2290
+ approval_code = _optional_string(pending.get("approval_code"))
2291
+ options = _claude_guard_approval_options_from_value(pending.get("approval_options"))
2292
+ if approval_code is None:
2293
+ if header is None and question is None and not options:
2294
+ return (
2295
+ _CLAUDE_GUARD_APPROVAL_HEADER,
2296
+ "HOL Guard intercepted this sensitive action. What should Claude do?",
2297
+ _CLAUDE_GUARD_APPROVAL_OPTIONS,
2298
+ )
2299
+ if header is None or question is None or not options:
2300
+ return None
2301
+ expected_question = "HOL Guard intercepted this sensitive action. What should Claude do?"
2302
+ else:
2303
+ if header is None or question is None or not options:
2304
+ return None
2305
+ expected_question = _claude_guard_approval_question_text(approval_code)
2306
+ normalized_expected_options = tuple(
2307
+ _normalize_claude_guard_approval_text(option) for option in _CLAUDE_GUARD_APPROVAL_OPTIONS
2308
+ )
2309
+ normalized_pending_options = tuple(_normalize_claude_guard_approval_text(option) for option in options)
2310
+ if _normalize_claude_guard_approval_text(question) != _normalize_claude_guard_approval_text(expected_question):
2311
+ return None
2312
+ if normalized_pending_options != normalized_expected_options:
2313
+ return None
2314
+ return header, question, options
2315
+
2316
+
2317
+ def _claude_guard_prompt_contract_from_question_list(
2318
+ payload_section: object,
2319
+ ) -> tuple[str, str, tuple[str, ...]] | None:
2320
+ if not isinstance(payload_section, dict):
2321
+ return None
2322
+ questions = payload_section.get("questions")
2323
+ if not isinstance(questions, list) or len(questions) != 1:
2324
+ return None
2325
+ first_question = questions[0]
2326
+ if not isinstance(first_question, dict):
2327
+ return None
2328
+ header = _optional_string(first_question.get("header"))
2329
+ question = _optional_string(first_question.get("question"))
2330
+ options = _claude_guard_approval_options_from_value(first_question.get("options"))
2331
+ if header is None or question is None or not options:
2332
+ return None
2333
+ return header, question, options
2334
+
2335
+
2336
+ def _claude_guard_prompt_contract_matches(
2337
+ expected_contract: tuple[str, str, tuple[str, ...]],
2338
+ actual_contract: tuple[str, str, tuple[str, ...]],
2339
+ ) -> bool:
2340
+ expected_header, expected_question, expected_options = expected_contract
2341
+ actual_header, actual_question, actual_options = actual_contract
2342
+ if _normalize_claude_guard_approval_text(actual_header) != _normalize_claude_guard_approval_text(expected_header):
2343
+ return False
2344
+ if _normalize_claude_guard_approval_text(actual_question) != _normalize_claude_guard_approval_text(
2345
+ expected_question
2346
+ ):
2347
+ return False
2348
+ expected_labels = tuple(_normalize_claude_guard_approval_text(option) for option in expected_options)
2349
+ actual_labels = tuple(_normalize_claude_guard_approval_text(option) for option in actual_options)
2350
+ return actual_labels == expected_labels
2351
+
2352
+
2353
+ def _is_claude_guard_approval_question(
2354
+ payload: dict[str, object],
2355
+ pending: dict[str, object],
2356
+ ) -> bool:
2230
2357
  if _hook_event_name(payload) != "PostToolUse":
2231
2358
  return False
2232
2359
  tool_name = _optional_string(payload.get("tool_name"))
2233
2360
  if tool_name is None or tool_name.lower() != "askuserquestion":
2234
2361
  return False
2235
- combined = json.dumps(
2236
- {
2237
- "tool_input": payload.get("tool_input"),
2238
- "tool_response": payload.get("tool_response"),
2239
- },
2240
- sort_keys=True,
2241
- default=str,
2242
- ).lower()
2243
- return "hol guard" in combined and (
2244
- "allow once" in combined or "allow during this session" in combined or "keep blocked" in combined
2245
- )
2362
+ expected_contract = _claude_guard_prompt_contract_from_pending(pending)
2363
+ if expected_contract is None:
2364
+ return False
2365
+ tool_input_contract = _claude_guard_prompt_contract_from_question_list(payload.get("tool_input"))
2366
+ if tool_input_contract is None:
2367
+ return False
2368
+ if not _claude_guard_prompt_contract_matches(expected_contract, tool_input_contract):
2369
+ return False
2370
+ response_contract = _claude_guard_prompt_contract_from_question_list(payload.get("tool_response"))
2371
+ return response_contract is None or _claude_guard_prompt_contract_matches(expected_contract, response_contract)
2372
+
2373
+
2374
+ def _claude_guard_approval_action_for_answer(answer_text: str) -> str | None:
2375
+ normalized_answer = _normalize_claude_guard_approval_text(answer_text)
2376
+ if normalized_answer == _normalize_claude_guard_approval_text("Keep blocked"):
2377
+ return "block"
2378
+ if normalized_answer in {
2379
+ _normalize_claude_guard_approval_text("Allow once"),
2380
+ _normalize_claude_guard_approval_text("Allow during this session"),
2381
+ }:
2382
+ return "allow"
2383
+ return None
2246
2384
 
2247
2385
 
2248
- def _claude_guard_approval_answer(payload: dict[str, object]) -> str | None:
2386
+ def _claude_guard_answer_text_from_value(value: object) -> str | None:
2387
+ if isinstance(value, str) and value.strip():
2388
+ return value
2389
+ if isinstance(value, dict):
2390
+ label = _optional_string(value.get("label"))
2391
+ if label is not None:
2392
+ return label
2393
+ return None
2394
+
2395
+
2396
+ def _claude_guard_approval_answer(payload: dict[str, object], *, expected_question: str | None = None) -> str | None:
2249
2397
  response = payload.get("tool_response")
2250
2398
  answer_text: str | None = None
2251
2399
  if isinstance(response, dict):
2252
2400
  answers = response.get("answers")
2253
2401
  if isinstance(answers, dict):
2254
- joined_answers = " ".join(str(answer) for answer in answers.values() if str(answer).strip())
2255
- if joined_answers:
2256
- answer_text = joined_answers
2402
+ normalized_expected_question = (
2403
+ _normalize_claude_guard_approval_text(expected_question) if isinstance(expected_question, str) else None
2404
+ )
2405
+ if normalized_expected_question is not None:
2406
+ for question, answer in answers.items():
2407
+ if not isinstance(question, str):
2408
+ continue
2409
+ if _normalize_claude_guard_approval_text(question) != normalized_expected_question:
2410
+ continue
2411
+ parsed_answer_text = _claude_guard_answer_text_from_value(answer)
2412
+ if parsed_answer_text is not None:
2413
+ answer_text = parsed_answer_text
2414
+ break
2415
+ if answer_text is None and len(answers) == 1:
2416
+ only_answer = next(iter(answers.values()))
2417
+ answer_text = _claude_guard_answer_text_from_value(only_answer)
2257
2418
  if answer_text is None:
2258
2419
  for key in ("answer", "selected_answer", "selected", "choice", "value", "label"):
2259
2420
  value = response.get(key)
2260
- if isinstance(value, str) and value.strip():
2261
- answer_text = value
2421
+ parsed_answer_text = _claude_guard_answer_text_from_value(value)
2422
+ if parsed_answer_text is not None:
2423
+ answer_text = parsed_answer_text
2262
2424
  break
2263
2425
  if answer_text is None and "questions" not in response and "options" not in response:
2264
2426
  content = response.get("content")
@@ -2268,24 +2430,25 @@ def _claude_guard_approval_answer(payload: dict[str, object]) -> str | None:
2268
2430
  answer_text = response
2269
2431
  if answer_text is None:
2270
2432
  return None
2271
- normalized_answer = answer_text.lower()
2272
- if "keep blocked" in normalized_answer:
2273
- return "block"
2274
- if "allow during this session" in normalized_answer or "allow once" in normalized_answer:
2275
- return "allow"
2276
- return None
2433
+ return _claude_guard_approval_action_for_answer(answer_text)
2277
2434
 
2278
2435
 
2279
2436
  def _persist_claude_guard_question_decision(store: GuardStore, payload: dict[str, object]) -> bool:
2280
- if not _is_claude_guard_approval_question(payload):
2281
- return False
2282
- action = _claude_guard_approval_answer(payload)
2283
- if action is None:
2284
- return False
2285
2437
  pending_pair = _load_single_claude_pending_permission(store, payload)
2286
2438
  if pending_pair is None:
2287
2439
  return False
2288
2440
  pending_key, pending = pending_pair
2441
+ approval_code = _optional_string(pending.get("approval_code"))
2442
+ if approval_code is None and pending.get("permission_prompt_seen") is not True:
2443
+ return False
2444
+ if not _is_claude_guard_approval_question(payload, pending):
2445
+ return False
2446
+ action = _claude_guard_approval_answer(
2447
+ payload,
2448
+ expected_question=_optional_string(pending.get("approval_question")),
2449
+ )
2450
+ if action is None:
2451
+ return False
2289
2452
  artifact_id = _optional_string(pending.get("artifact_id"))
2290
2453
  artifact_hash_value = _optional_string(pending.get("artifact_hash"))
2291
2454
  if artifact_id is None or artifact_hash_value is None:
@@ -2353,6 +2516,8 @@ def _claude_permission_prompt_system_message(
2353
2516
 
2354
2517
 
2355
2518
  def _claude_permission_prompt_additional_context(notice: dict[str, object] | None) -> str:
2519
+ if notice is not None:
2520
+ return _claude_guard_approval_question_message(notice)
2356
2521
  reason = _optional_string(notice.get("reason")) if notice is not None else None
2357
2522
  if reason is not None:
2358
2523
  return (
@@ -392,6 +392,27 @@ def _render_events(console: Console, payload: dict[str, object]) -> None:
392
392
 
393
393
 
394
394
  def _render_approvals(console: Console, payload: dict[str, object]) -> None:
395
+ if "history_cleared" in payload or "cleared_policies" in payload:
396
+ error = payload.get("error")
397
+ body = Table.grid(padding=(0, 1))
398
+ body.add_row(
399
+ "Outcome",
400
+ str(error) if error else "approval history reset",
401
+ )
402
+ body.add_row("Harness", str(payload.get("harness") or "all harnesses"))
403
+ source = payload.get("source")
404
+ if source:
405
+ body.add_row("Source", str(source))
406
+ body.add_row("Policy decisions", str(int(payload.get("cleared_policies", 0) or 0)))
407
+ body.add_row("Resolved requests", str(int(payload.get("cleared_resolved_requests", 0) or 0)))
408
+ console.print(
409
+ Panel(
410
+ body,
411
+ title="Approval history",
412
+ border_style="red" if error else "green",
413
+ )
414
+ )
415
+ return
395
416
  if payload.get("resolved"):
396
417
  item = payload.get("item")
397
418
  if isinstance(item, dict):
@@ -1526,6 +1526,22 @@ class GuardStore:
1526
1526
  with self._connect() as connection:
1527
1527
  return count_pending_approval_requests(connection, status=status)
1528
1528
 
1529
+ def clear_approval_requests(self, *, harness: str | None = None, status: str | None = None) -> int:
1530
+ conditions: list[str] = []
1531
+ params: list[object] = []
1532
+ if harness is not None:
1533
+ conditions.append("harness = ?")
1534
+ params.append(harness)
1535
+ if status is not None:
1536
+ conditions.append("status = ?")
1537
+ params.append(status)
1538
+ query = "delete from approval_requests"
1539
+ if conditions:
1540
+ query += " where " + " and ".join(conditions)
1541
+ with self._connect() as connection:
1542
+ cursor = connection.execute(query, tuple(params))
1543
+ return int(cursor.rowcount if cursor.rowcount is not None else 0)
1544
+
1529
1545
  def list_policy_decisions(self, harness: str | None = None) -> list[dict[str, object]]:
1530
1546
  query = """
1531
1547
  select harness, scope, artifact_id, artifact_hash, workspace, publisher, action, reason, owner, source,
@@ -1,3 +1,3 @@
1
1
  """Single source of truth for tool version."""
2
2
 
3
- __version__ = "2.0.66"
3
+ __version__ = "2.0.68"