plugin-scanner 2.0.63__tar.gz → 2.0.65__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 (320) hide show
  1. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/PKG-INFO +1 -1
  2. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/pyproject.toml +1 -1
  3. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/pyproject.toml.bak +1 -1
  4. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/cli/commands.py +6 -11
  5. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/cli/connect_flow.py +10 -10
  6. plugin_scanner-2.0.65/src/codex_plugin_scanner/guard/edge_events.py +88 -0
  7. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/runtime/__init__.py +2 -0
  8. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/runtime/runner.py +98 -0
  9. plugin_scanner-2.0.65/src/codex_plugin_scanner/guard/schemas/guard_event_v1.py +74 -0
  10. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/store.py +95 -0
  11. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/version.py +1 -1
  12. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_guard_claude_adapter.py +5 -1
  13. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_guard_cli.py +33 -21
  14. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_guard_connect_flow.py +9 -9
  15. plugin_scanner-2.0.65/tests/test_guard_event_schema_v1.py +260 -0
  16. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_guard_runtime.py +10 -2
  17. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_guard_surface_server.py +5 -1
  18. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/.clusterfuzzlite/Dockerfile +0 -0
  19. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/.clusterfuzzlite/build.sh +0 -0
  20. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/.clusterfuzzlite/project.yaml +0 -0
  21. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/.clusterfuzzlite/requirements-atheris.txt +0 -0
  22. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/.dockerignore +0 -0
  23. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/.github/CODEOWNERS +0 -0
  24. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/.github/ISSUE_TEMPLATE/bug-report.yml +0 -0
  25. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  26. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
  27. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/.github/dependabot.yml +0 -0
  28. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/.github/workflows/ci.yml +0 -0
  29. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/.github/workflows/codeql.yml +0 -0
  30. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/.github/workflows/dependabot-uv-lock.yml +0 -0
  31. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/.github/workflows/fuzz.yml +0 -0
  32. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/.github/workflows/harness-smoke.yml +0 -0
  33. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/.github/workflows/publish.yml +0 -0
  34. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/.github/workflows/scorecard.yml +0 -0
  35. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/.gitignore +0 -0
  36. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/.pre-commit-hooks.yaml +0 -0
  37. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/CONTRIBUTING.md +0 -0
  38. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/Dockerfile +0 -0
  39. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/LICENSE +0 -0
  40. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/README.md +0 -0
  41. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/SECURITY.md +0 -0
  42. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/dashboard/index.html +0 -0
  43. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/dashboard/package.json +0 -0
  44. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/dashboard/pnpm-lock.yaml +0 -0
  45. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/dashboard/public/brand/Logo_Whole.png +0 -0
  46. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/dashboard/src/app.tsx +0 -0
  47. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/dashboard/src/approval-center-layout.tsx +0 -0
  48. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/dashboard/src/approval-center-primitives.tsx +0 -0
  49. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/dashboard/src/approval-center-utils.ts +0 -0
  50. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/dashboard/src/fleet-workspace.tsx +0 -0
  51. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/dashboard/src/guard-api.ts +0 -0
  52. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/dashboard/src/guard-demo.ts +0 -0
  53. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/dashboard/src/guard-types.ts +0 -0
  54. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/dashboard/src/main.tsx +0 -0
  55. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/dashboard/src/receipts-workspace.tsx +0 -0
  56. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/dashboard/src/runtime-overview.tsx +0 -0
  57. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/dashboard/src/styles.css +0 -0
  58. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/dashboard/src/vite-env.d.ts +0 -0
  59. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/dashboard/tsconfig.json +0 -0
  60. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/dashboard/vite.config.ts +0 -0
  61. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/docker-requirements.txt +0 -0
  62. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/docs/guard/approval-audit.md +0 -0
  63. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/docs/guard/architecture.md +0 -0
  64. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/docs/guard/get-started.md +0 -0
  65. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/docs/guard/harness-support.md +0 -0
  66. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/docs/guard/local-vs-cloud.md +0 -0
  67. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/docs/guard/testing-matrix.md +0 -0
  68. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/docs/trust/mcp-trust-draft.md +0 -0
  69. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/docs/trust/plugin-trust-draft.md +0 -0
  70. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/docs/trust/skill-trust-local.md +0 -0
  71. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/fuzzers/manifest_fuzzer.py +0 -0
  72. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/requirements.txt +0 -0
  73. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/schemas/plugin-quality.v1.json +0 -0
  74. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/schemas/scan-result.v1.json +0 -0
  75. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/schemas/verify-result.v1.json +0 -0
  76. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/__init__.py +0 -0
  77. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/action_runner.py +0 -0
  78. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/argparse_utils.py +0 -0
  79. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/__init__.py +0 -0
  80. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/best_practices.py +0 -0
  81. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/claude.py +0 -0
  82. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/code_quality.py +0 -0
  83. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/ecosystem_common.py +0 -0
  84. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/gemini.py +0 -0
  85. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/manifest.py +0 -0
  86. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/manifest_support.py +0 -0
  87. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/marketplace.py +0 -0
  88. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/mcp_security.py +0 -0
  89. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/opencode.py +0 -0
  90. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/operational_security.py +0 -0
  91. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/security.py +0 -0
  92. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/checks/skill_security.py +0 -0
  93. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/cli.py +0 -0
  94. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/cli_ui.py +0 -0
  95. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/config.py +0 -0
  96. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/ecosystems/__init__.py +0 -0
  97. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/ecosystems/base.py +0 -0
  98. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/ecosystems/claude.py +0 -0
  99. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/ecosystems/codex.py +0 -0
  100. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/ecosystems/detect.py +0 -0
  101. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/ecosystems/gemini.py +0 -0
  102. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/ecosystems/opencode.py +0 -0
  103. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/ecosystems/registry.py +0 -0
  104. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/ecosystems/types.py +0 -0
  105. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/github_reporting.py +0 -0
  106. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/__init__.py +0 -0
  107. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/__init__.py +0 -0
  108. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/antigravity.py +0 -0
  109. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/base.py +0 -0
  110. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/claude_code.py +0 -0
  111. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/codex.py +0 -0
  112. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/copilot.py +0 -0
  113. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/cursor.py +0 -0
  114. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/gemini.py +0 -0
  115. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/hermes.py +0 -0
  116. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/mcp_servers.py +0 -0
  117. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/opencode.py +0 -0
  118. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/adapters/opencode_artifacts.py +0 -0
  119. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/approvals.py +0 -0
  120. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/bridge/__init__.py +0 -0
  121. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/capabilities.py +0 -0
  122. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/cli/__init__.py +0 -0
  123. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/cli/approval_commands.py +0 -0
  124. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/cli/bootstrap.py +0 -0
  125. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/cli/install_commands.py +0 -0
  126. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/cli/product.py +0 -0
  127. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/cli/prompt.py +0 -0
  128. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/cli/render.py +0 -0
  129. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/cli/update_commands.py +0 -0
  130. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/codex_config.py +0 -0
  131. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/config.py +0 -0
  132. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/consumer/__init__.py +0 -0
  133. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/consumer/service.py +0 -0
  134. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/daemon/__init__.py +0 -0
  135. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/daemon/client.py +0 -0
  136. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/daemon/manager.py +0 -0
  137. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/daemon/server.py +0 -0
  138. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/daemon/static/assets/guard-dashboard.js +0 -0
  139. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/daemon/static/assets/index.css +0 -0
  140. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Whole.png +0 -0
  141. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/daemon/static/index.html +0 -0
  142. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/incident.py +0 -0
  143. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/launcher.py +0 -0
  144. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/mcp_tool_calls.py +0 -0
  145. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/models.py +0 -0
  146. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/policy/__init__.py +0 -0
  147. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/policy/engine.py +0 -0
  148. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/protect.py +0 -0
  149. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/proxy/__init__.py +0 -0
  150. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/proxy/remote.py +0 -0
  151. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/proxy/runtime_mcp.py +0 -0
  152. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/proxy/stdio.py +0 -0
  153. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/receipts/__init__.py +0 -0
  154. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/receipts/manager.py +0 -0
  155. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/risk.py +0 -0
  156. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/runtime/secret_file_requests.py +0 -0
  157. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/runtime/surface_server.py +0 -0
  158. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/schemas/__init__.py +0 -0
  159. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/schemas/consumer_mode.py +0 -0
  160. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/schemas/surface_server.py +0 -0
  161. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/shims.py +0 -0
  162. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/store_approvals.py +0 -0
  163. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/store_connect.py +0 -0
  164. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/guard/types.py +0 -0
  165. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/integrations/__init__.py +0 -0
  166. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/integrations/cisco_mcp_scanner.py +0 -0
  167. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/integrations/cisco_skill_scanner.py +0 -0
  168. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/lint_fixes.py +0 -0
  169. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/marketplace_support.py +0 -0
  170. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/models.py +0 -0
  171. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/path_support.py +0 -0
  172. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/policy.py +0 -0
  173. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/quality_artifact.py +0 -0
  174. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/repo_detect.py +0 -0
  175. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/reporting.py +0 -0
  176. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/rules/__init__.py +0 -0
  177. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/rules/registry.py +0 -0
  178. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/rules/specs.py +0 -0
  179. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/scanner.py +0 -0
  180. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/submission.py +0 -0
  181. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/suppressions.py +0 -0
  182. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/trust_domain_scoring.py +0 -0
  183. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/trust_helpers.py +0 -0
  184. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/trust_mcp_scoring.py +0 -0
  185. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/trust_models.py +0 -0
  186. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/trust_plugin_scoring.py +0 -0
  187. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/trust_scoring.py +0 -0
  188. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/trust_skill_scoring.py +0 -0
  189. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/trust_specs.py +0 -0
  190. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/src/codex_plugin_scanner/verification.py +0 -0
  191. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/__init__.py +0 -0
  192. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/conftest.py +0 -0
  193. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/__init__.py +0 -0
  194. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/bad-plugin/.codex-plugin/plugin.json +0 -0
  195. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/bad-plugin/.mcp.json +0 -0
  196. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/bad-plugin/secrets.js +0 -0
  197. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/claude-plugin-good/.claude-plugin/plugin.json +0 -0
  198. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/claude-plugin-good/LICENSE +0 -0
  199. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/claude-plugin-good/README.md +0 -0
  200. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/claude-plugin-good/SECURITY.md +0 -0
  201. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/claude-plugin-good/hooks/hooks.json +0 -0
  202. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/claude-plugin-good/skills/example/SKILL.md +0 -0
  203. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/code-quality-bad/evil.js +0 -0
  204. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/code-quality-bad/inject.js +0 -0
  205. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/gemini-extension-good/GEMINI.md +0 -0
  206. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/gemini-extension-good/LICENSE +0 -0
  207. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/gemini-extension-good/README.md +0 -0
  208. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/gemini-extension-good/SECURITY.md +0 -0
  209. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/gemini-extension-good/commands/hello.toml +0 -0
  210. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/gemini-extension-good/gemini-extension.json +0 -0
  211. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/good-plugin/.codex-plugin/plugin.json +0 -0
  212. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/good-plugin/.codexignore +0 -0
  213. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/good-plugin/LICENSE +0 -0
  214. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/good-plugin/README.md +0 -0
  215. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/good-plugin/SECURITY.md +0 -0
  216. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/good-plugin/assets/icon.svg +0 -0
  217. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/good-plugin/assets/logo.svg +0 -0
  218. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/good-plugin/assets/screenshot.svg +0 -0
  219. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/good-plugin/skills/example/SKILL.md +0 -0
  220. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/guard-codex-malicious-mcp/.codex/config.toml +0 -0
  221. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/hermes-plugin-evil/config.yaml +0 -0
  222. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/hermes-plugin-evil/mcp_servers.json +0 -0
  223. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/hermes-plugin-evil/skills/security/malicious/SKILL.md +0 -0
  224. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/SKILL.md +0 -0
  225. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/references/api-setup.md +0 -0
  226. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/scripts/deploy.sh +0 -0
  227. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/hermes-plugin-evil/skills/utils/benign/SKILL.md +0 -0
  228. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/malformed-json/.codex-plugin/plugin.json +0 -0
  229. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/malicious-skill-plugin/.codex-plugin/plugin.json +0 -0
  230. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/malicious-skill-plugin/.codexignore +0 -0
  231. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/malicious-skill-plugin/LICENSE +0 -0
  232. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/malicious-skill-plugin/README.md +0 -0
  233. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/malicious-skill-plugin/SECURITY.md +0 -0
  234. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/malicious-skill-plugin/skills/leaky-skill/SKILL.md +0 -0
  235. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/mcp-canary-server.py +0 -0
  236. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/minimal-plugin/.codex-plugin/plugin.json +0 -0
  237. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/missing-fields/.codex-plugin/plugin.json +0 -0
  238. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/mit-license/LICENSE +0 -0
  239. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/multi-ecosystem-repo/codex-plugin/.codex-plugin/plugin.json +0 -0
  240. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/multi-ecosystem-repo/codex-plugin/LICENSE +0 -0
  241. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/multi-ecosystem-repo/codex-plugin/README.md +0 -0
  242. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/multi-ecosystem-repo/codex-plugin/SECURITY.md +0 -0
  243. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/multi-ecosystem-repo/gemini-ext/README.md +0 -0
  244. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/multi-ecosystem-repo/gemini-ext/gemini-extension.json +0 -0
  245. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/multi-plugin-repo/.agents/plugins/marketplace.json +0 -0
  246. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codex-plugin/plugin.json +0 -0
  247. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codexignore +0 -0
  248. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/LICENSE +0 -0
  249. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/README.md +0 -0
  250. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/SECURITY.md +0 -0
  251. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/skills/example/SKILL.md +0 -0
  252. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/.codex-plugin/plugin.json +0 -0
  253. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/skills/example/SKILL.md +0 -0
  254. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/no-version/.codex-plugin/plugin.json +0 -0
  255. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/opencode-good/.opencode/commands/hello.md +0 -0
  256. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/opencode-good/.opencode/plugins/example.ts +0 -0
  257. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/opencode-good/LICENSE +0 -0
  258. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/opencode-good/README.md +0 -0
  259. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/opencode-good/SECURITY.md +0 -0
  260. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/opencode-good/opencode.jsonc +0 -0
  261. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/skills-missing-dir/.codex-plugin/plugin.json +0 -0
  262. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/skills-no-frontmatter/.codex-plugin/plugin.json +0 -0
  263. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/skills-no-frontmatter/skills/bad-skill/SKILL.md +0 -0
  264. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/with-marketplace/.codex-plugin/plugin.json +0 -0
  265. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/with-marketplace/marketplace-broken.json +0 -0
  266. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/fixtures/with-marketplace/marketplace.json +0 -0
  267. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test-trust-scoring.py +0 -0
  268. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test-trust-specs.py +0 -0
  269. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_action_runner.py +0 -0
  270. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_best_practices.py +0 -0
  271. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_cisco_install_surfaces.py +0 -0
  272. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_cli.py +0 -0
  273. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_code_quality.py +0 -0
  274. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_config.py +0 -0
  275. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_coverage_remaining.py +0 -0
  276. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_ecosystems.py +0 -0
  277. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_edge_cases.py +0 -0
  278. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_final_coverage.py +0 -0
  279. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_guard_approvals.py +0 -0
  280. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_guard_bootstrap.py +0 -0
  281. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_guard_capabilities.py +0 -0
  282. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_guard_codex_e2e.py +0 -0
  283. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_guard_codex_install.py +0 -0
  284. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_guard_codex_proxy.py +0 -0
  285. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_guard_config_paths.py +0 -0
  286. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_guard_consumer_mode.py +0 -0
  287. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_guard_copilot_adapter.py +0 -0
  288. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_guard_copilot_proxy.py +0 -0
  289. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_guard_daemon_manager.py +0 -0
  290. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_guard_events.py +0 -0
  291. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_guard_launch_env.py +0 -0
  292. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_guard_opencode_proxy.py +0 -0
  293. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_guard_product_flow.py +0 -0
  294. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_guard_protect.py +0 -0
  295. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_guard_render.py +0 -0
  296. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_guard_risk.py +0 -0
  297. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_guard_store_migrations.py +0 -0
  298. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_guard_verdicts.py +0 -0
  299. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_hermes_adapter.py +0 -0
  300. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_integration.py +0 -0
  301. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_lint_fixes.py +0 -0
  302. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_live_cisco_smoke.py +0 -0
  303. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_manifest.py +0 -0
  304. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_marketplace.py +0 -0
  305. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_mcp_security.py +0 -0
  306. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_operational_security.py +0 -0
  307. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_policy.py +0 -0
  308. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_quality_artifact.py +0 -0
  309. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_rule_registry.py +0 -0
  310. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_scanner.py +0 -0
  311. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_schema_contracts.py +0 -0
  312. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_security.py +0 -0
  313. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_security_ops.py +0 -0
  314. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_skill_security.py +0 -0
  315. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_submission.py +0 -0
  316. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_trust_scoring.py +0 -0
  317. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_trust_specs.py +0 -0
  318. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_verification.py +0 -0
  319. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/tests/test_versioning.py +0 -0
  320. {plugin_scanner-2.0.63 → plugin_scanner-2.0.65}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plugin-scanner
3
- Version: 2.0.63
3
+ Version: 2.0.65
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.63"
7
+ version = "2.0.65"
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.63"
7
+ version = "2.0.65"
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"
@@ -2438,22 +2438,17 @@ def _claude_prompt_additional_context(
2438
2438
  return None
2439
2439
  if _prompt_requires_hard_block(artifact):
2440
2440
  return None
2441
- briefing_sentence = "HOL Guard intercepted the next sensitive action and opened the approval dialog below."
2441
+ briefing_sentence = "HOL Guard will intercept Claude's next sensitive action and open a branded approval question."
2442
2442
  if "secret_read" in _prompt_request_classes(artifact):
2443
2443
  briefing_sentence = (
2444
- "HOL Guard intercepted Claude's next attempt to access local secrets and opened the approval dialog "
2445
- "shown below to protect you."
2446
- )
2447
- else:
2448
- briefing_sentence = (
2449
- "HOL Guard intercepted Claude's next sensitive action and opened the approval dialog shown below."
2444
+ "HOL Guard will intercept Claude's next attempt to access local secrets and open a branded approval "
2445
+ "question to protect you."
2450
2446
  )
2451
2447
  return (
2452
2448
  f"{_ensure_terminal_punctuation(native_reason)} "
2453
- "Before you use the first sensitive tool for this request, call AskUserQuestion with header 'HOL Guard', "
2454
- "question 'HOL Guard intercepted this sensitive action. What should Claude do?', and exactly these options: "
2455
- "'Allow once', 'Allow during this session', and 'Keep blocked'. If the user chooses Keep blocked, do not "
2456
- "retry the sensitive action. Then tell the user exactly: "
2449
+ "Do not ask for approval at the prompt stage. Attempt the intended sensitive tool once so HOL Guard can "
2450
+ "evaluate the exact tool, path, and arguments, then route that concrete action into a HOL Guard approval "
2451
+ "question with Allow once, Allow during this session, and Keep blocked. First tell the user exactly: "
2457
2452
  f"'{briefing_sentence}' "
2458
2453
  "Attempt that sensitive tool at most once. If HOL Guard or Claude denies it, do not retry the same sensitive "
2459
2454
  "action automatically. Instead, tell the user approval is required in Claude to continue."
@@ -107,36 +107,37 @@ def run_guard_connect_command(
107
107
  sync_payload = sync_receipts(store)
108
108
  except GuardSyncNotAvailableError as plan_error:
109
109
  plan_msg = str(plan_error).strip() or "Cloud sync requires a paid Guard plan."
110
+ pending_sync_payload = dict(runtime_sync_summary)
111
+ pending_sync_payload["synced_at"] = None
110
112
  pending_state = _record_connect_result(
111
113
  daemon_client=daemon_client,
112
114
  store=store,
113
115
  request_id=str(connect_request["request_id"]),
114
- status="connected",
115
- milestone="sync_not_available",
116
+ status="retry_required",
117
+ milestone="first_sync_failed",
116
118
  reason=plan_msg,
119
+ sync=pending_sync_payload,
117
120
  )
118
121
  return build_connect_payload(
119
122
  state=pending_state,
120
123
  browser_opened=browser_opened,
121
124
  connect_url=browser_url,
122
125
  sync_url=sync_url,
123
- connected=True,
126
+ connected=False,
127
+ sync=pending_sync_payload,
124
128
  sync_available=False,
125
129
  sync_message=plan_msg,
126
130
  )
127
131
  except (RuntimeError, OSError, urllib.error.URLError, json.JSONDecodeError) as error:
128
132
  sync_message = str(error)
129
- if runtime_sync_error and not _is_paid_plan_sync_error(sync_message):
130
- sync_message = runtime_sync_error
131
- sync_is_plan_limited = _is_paid_plan_sync_error(sync_message)
132
133
  pending_sync_payload = dict(runtime_sync_summary)
133
134
  pending_sync_payload["synced_at"] = None
134
135
  pending_state = _record_connect_result(
135
136
  daemon_client=daemon_client,
136
137
  store=store,
137
138
  request_id=str(connect_request["request_id"]),
138
- status="connected",
139
- milestone="first_sync_pending",
139
+ status="retry_required",
140
+ milestone="first_sync_failed",
140
141
  reason=sync_message,
141
142
  sync=pending_sync_payload,
142
143
  )
@@ -145,10 +146,9 @@ def run_guard_connect_command(
145
146
  browser_opened=browser_opened,
146
147
  connect_url=browser_url,
147
148
  sync_url=sync_url,
148
- connected=True,
149
+ connected=False,
149
150
  sync=pending_sync_payload,
150
151
  sync_message=sync_message,
151
- sync_available=False if sync_is_plan_limited else None,
152
152
  )
153
153
  sync_payload["runtime_session_synced_at"] = runtime_sync_summary["runtime_session_synced_at"]
154
154
  sync_payload["runtime_session_id"] = runtime_sync_summary["runtime_session_id"]
@@ -0,0 +1,88 @@
1
+ """Builders for Guard Cloud v1 events emitted by the local edge runtime."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ from typing import cast
7
+
8
+ from .models import GuardReceipt
9
+ from .schemas.guard_event_v1 import GuardEventType, GuardEventV1
10
+
11
+
12
+ def build_receipt_event(
13
+ receipt: GuardReceipt,
14
+ *,
15
+ device_id: str | None = None,
16
+ workspace_id: str | None = None,
17
+ ) -> GuardEventV1:
18
+ payload: dict[str, object] = {
19
+ "receiptId": receipt.receipt_id,
20
+ "harness": receipt.harness,
21
+ "artifactId": receipt.artifact_id,
22
+ "artifactHash": receipt.artifact_hash,
23
+ "artifactName": receipt.artifact_name,
24
+ "sourceScope": receipt.source_scope,
25
+ "policyDecision": receipt.policy_decision,
26
+ "capabilitiesSummary": receipt.capabilities_summary,
27
+ "changedCapabilities": list(receipt.changed_capabilities),
28
+ "provenanceSummary": receipt.provenance_summary,
29
+ "userOverride": receipt.user_override,
30
+ }
31
+ event_id = f"guard-event-{_fingerprint('receipt.created', receipt.receipt_id)[:32]}"
32
+ return GuardEventV1(
33
+ event_id=event_id,
34
+ idempotency_key=f"receipt.created:{receipt.receipt_id}",
35
+ event_type="receipt.created",
36
+ source="edge",
37
+ occurred_at=receipt.timestamp,
38
+ workspace_id=workspace_id,
39
+ device_id=device_id,
40
+ payload=payload,
41
+ )
42
+
43
+
44
+ def build_approval_event(
45
+ *,
46
+ request_id: str,
47
+ event_type: str,
48
+ occurred_at: str,
49
+ payload: dict[str, object],
50
+ device_id: str | None = None,
51
+ workspace_id: str | None = None,
52
+ ) -> GuardEventV1:
53
+ if event_type not in {"approval.created", "approval.resolved"}:
54
+ raise ValueError("Approval event type must be approval.created or approval.resolved")
55
+ return GuardEventV1(
56
+ event_id=f"guard-event-{_fingerprint(event_type, request_id, occurred_at)[:32]}",
57
+ idempotency_key=f"{event_type}:{request_id}:{occurred_at}",
58
+ event_type=cast(GuardEventType, event_type),
59
+ source="approval-center",
60
+ occurred_at=occurred_at,
61
+ workspace_id=workspace_id,
62
+ device_id=device_id,
63
+ payload=payload,
64
+ )
65
+
66
+
67
+ def build_policy_event(
68
+ *,
69
+ policy_key: str,
70
+ occurred_at: str,
71
+ payload: dict[str, object],
72
+ device_id: str | None = None,
73
+ workspace_id: str | None = None,
74
+ ) -> GuardEventV1:
75
+ return GuardEventV1(
76
+ event_id=f"guard-event-{_fingerprint('policy.changed', policy_key, occurred_at)[:32]}",
77
+ idempotency_key=f"policy.changed:{policy_key}:{occurred_at}",
78
+ event_type="policy.changed",
79
+ source="policy",
80
+ occurred_at=occurred_at,
81
+ workspace_id=workspace_id,
82
+ device_id=device_id,
83
+ payload=payload,
84
+ )
85
+
86
+
87
+ def _fingerprint(*parts: str) -> str:
88
+ return hashlib.sha256(":".join(parts).encode()).hexdigest()
@@ -4,6 +4,7 @@ from .runner import (
4
4
  GuardSyncNotAvailableError,
5
5
  GuardSyncNotConfiguredError,
6
6
  guard_run,
7
+ sync_guard_events,
7
8
  sync_receipts,
8
9
  sync_runtime_session,
9
10
  )
@@ -12,6 +13,7 @@ __all__ = [
12
13
  "GuardSyncNotAvailableError",
13
14
  "GuardSyncNotConfiguredError",
14
15
  "guard_run",
16
+ "sync_guard_events",
15
17
  "sync_receipts",
16
18
  "sync_runtime_session",
17
19
  ]
@@ -480,10 +480,69 @@ def sync_receipts(store: GuardStore) -> dict[str, object]:
480
480
  "inventory": 0,
481
481
  "inventory_tracked": len(inventory),
482
482
  }
483
+ summary["guard_events_v1"] = sync_guard_events(store)
483
484
  store.set_sync_payload("sync_summary", summary, now)
484
485
  return summary
485
486
 
486
487
 
488
+ def sync_guard_events(store: GuardStore) -> dict[str, object]:
489
+ """Push pending GuardEventV1 envelopes to Guard Cloud."""
490
+
491
+ credentials = store.get_sync_credentials()
492
+ if credentials is None:
493
+ raise GuardSyncNotConfiguredError("Guard is not logged in.")
494
+ sync_url = _guard_events_sync_url(str(credentials["sync_url"]))
495
+ total_events = 0
496
+ total_accepted = 0
497
+ synced_at = _now()
498
+ while True:
499
+ pending_events = store.list_guard_events_v1(uploaded=False, limit=200)
500
+ if not pending_events:
501
+ break
502
+ body = json.dumps({"events": [event["payload"] for event in pending_events]}).encode("utf-8")
503
+ request = urllib.request.Request(
504
+ sync_url,
505
+ data=body,
506
+ method="POST",
507
+ headers=_guard_sync_headers(str(credentials["token"])),
508
+ )
509
+ try:
510
+ payload = _urlopen_json_with_timeout_retry(
511
+ request=request,
512
+ timeout_seconds=_SYNC_HTTP_TIMEOUT_SECONDS,
513
+ retry_timeout_seconds=_SYNC_HTTP_RETRY_TIMEOUT_SECONDS,
514
+ )
515
+ except urllib.error.HTTPError as error:
516
+ if error.code == 404:
517
+ summary = {
518
+ "synced_at": synced_at,
519
+ "events": total_events,
520
+ "accepted": total_accepted,
521
+ "sync_skipped": True,
522
+ "sync_reason": "guard_events_endpoint_unavailable",
523
+ }
524
+ store.set_sync_payload("guard_events_v1_summary", summary, synced_at)
525
+ return summary
526
+ if error.code == 403:
527
+ is_plan, message = _check_plan_restriction_403(error)
528
+ if is_plan:
529
+ raise GuardSyncNotAvailableError(message) from error
530
+ raise RuntimeError(message) from error
531
+ raise RuntimeError(_sync_http_error_message(error)) from error
532
+ except OSError as error:
533
+ raise RuntimeError(_sync_url_error_message(error)) from error
534
+ completed_ids = _completed_guard_event_ids(payload)
535
+ synced_at = _sync_timestamp(payload)
536
+ uploaded = store.mark_guard_events_v1_uploaded(completed_ids, synced_at)
537
+ total_events += len(pending_events)
538
+ total_accepted += uploaded
539
+ if uploaded == 0 or len(pending_events) < 200:
540
+ break
541
+ summary = {"synced_at": synced_at, "events": total_events, "accepted": total_accepted}
542
+ store.set_sync_payload("guard_events_v1_summary", summary, synced_at)
543
+ return summary
544
+
545
+
487
546
  def sync_runtime_session(
488
547
  store: GuardStore,
489
548
  *,
@@ -951,6 +1010,45 @@ def _normalized_runtime_sessions_sync_url(sync_url: str) -> str:
951
1010
  )
952
1011
 
953
1012
 
1013
+ def _guard_events_sync_url(sync_url: str) -> str:
1014
+ parsed = urllib.parse.urlsplit(_normalized_receipts_sync_url(sync_url))
1015
+ if parsed.path.rstrip("/").endswith("/api/v1/guard/events"):
1016
+ return urllib.parse.urlunsplit((parsed.scheme, parsed.netloc, parsed.path.rstrip("/"), parsed.query, ""))
1017
+ path = parsed.path.rstrip("/")
1018
+ for suffix in (
1019
+ "/api/guard/receipts/sync",
1020
+ "/guard/receipts/sync",
1021
+ "/registry/api/v1/guard/receipts/sync",
1022
+ ):
1023
+ if path.endswith(suffix):
1024
+ path = path[: -len(suffix)]
1025
+ break
1026
+ return urllib.parse.urlunsplit(
1027
+ (
1028
+ parsed.scheme,
1029
+ parsed.netloc,
1030
+ path.rstrip("/") + "/api/v1/guard/events",
1031
+ parsed.query,
1032
+ "",
1033
+ )
1034
+ )
1035
+
1036
+
1037
+ def _completed_guard_event_ids(payload: dict[str, object]) -> list[str]:
1038
+ statuses = payload.get("statuses")
1039
+ if not isinstance(statuses, list):
1040
+ return []
1041
+ completed: list[str] = []
1042
+ for item in statuses:
1043
+ if not isinstance(item, dict):
1044
+ continue
1045
+ status = str(item.get("status") or "")
1046
+ event_id = item.get("eventId")
1047
+ if status in {"accepted", "duplicate", "rejected"} and isinstance(event_id, str):
1048
+ completed.append(event_id)
1049
+ return completed
1050
+
1051
+
954
1052
  def _cloud_sync_receipts_payload(store: GuardStore, receipts: list[dict[str, object]]) -> list[dict[str, object]]:
955
1053
  device_id, device_name = _guard_device_metadata(store)
956
1054
  return [_cloud_sync_receipt_payload(receipt, device_id=device_id, device_name=device_name) for receipt in receipts]
@@ -0,0 +1,74 @@
1
+ """Guard Cloud event schema shared by the edge runtime and v1 ingest API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Literal, cast
7
+
8
+ GuardEventSource = Literal["edge", "approval-center", "policy", "protect-api"]
9
+ GuardEventType = Literal["receipt.created", "approval.created", "approval.resolved", "policy.changed"]
10
+
11
+
12
+ @dataclass(frozen=True, slots=True)
13
+ class GuardEventV1:
14
+ """Versioned event envelope for replay-safe Guard Cloud sync."""
15
+
16
+ event_id: str
17
+ idempotency_key: str
18
+ event_type: GuardEventType
19
+ source: GuardEventSource
20
+ occurred_at: str
21
+ workspace_id: str | None = None
22
+ device_id: str | None = None
23
+ payload: dict[str, object] = field(default_factory=dict)
24
+ schema_version: str = "guard.event.v1"
25
+
26
+ def to_dict(self) -> dict[str, object]:
27
+ return {
28
+ "schemaVersion": self.schema_version,
29
+ "eventId": self.event_id,
30
+ "idempotencyKey": self.idempotency_key,
31
+ "eventType": self.event_type,
32
+ "source": self.source,
33
+ "occurredAt": self.occurred_at,
34
+ "workspaceId": self.workspace_id,
35
+ "deviceId": self.device_id,
36
+ "payload": self.payload,
37
+ }
38
+
39
+ @classmethod
40
+ def from_dict(cls, payload: dict[str, object]) -> GuardEventV1:
41
+ schema_version = str(payload.get("schemaVersion") or "")
42
+ if schema_version != "guard.event.v1":
43
+ raise ValueError("Guard event schemaVersion must be guard.event.v1")
44
+ event_id = _required_string(payload, "eventId")
45
+ idempotency_key = _required_string(payload, "idempotencyKey")
46
+ event_type = _required_string(payload, "eventType")
47
+ source = _required_string(payload, "source")
48
+ occurred_at = _required_string(payload, "occurredAt")
49
+ event_payload = payload.get("payload")
50
+ if not isinstance(event_payload, dict):
51
+ raise ValueError("Guard event payload must be an object")
52
+ if event_type not in {"receipt.created", "approval.created", "approval.resolved", "policy.changed"}:
53
+ raise ValueError(f"Unsupported Guard event type: {event_type}")
54
+ if source not in {"edge", "approval-center", "policy", "protect-api"}:
55
+ raise ValueError(f"Unsupported Guard event source: {source}")
56
+ workspace_id = payload.get("workspaceId")
57
+ device_id = payload.get("deviceId")
58
+ return cls(
59
+ event_id=event_id,
60
+ idempotency_key=idempotency_key,
61
+ event_type=cast(GuardEventType, event_type),
62
+ source=cast(GuardEventSource, source),
63
+ occurred_at=occurred_at,
64
+ workspace_id=workspace_id if isinstance(workspace_id, str) else None,
65
+ device_id=device_id if isinstance(device_id, str) else None,
66
+ payload={str(key): value for key, value in event_payload.items()},
67
+ )
68
+
69
+
70
+ def _required_string(payload: dict[str, object], key: str) -> str:
71
+ value = payload.get(key)
72
+ if not isinstance(value, str) or not value.strip():
73
+ raise ValueError(f"Guard event {key} must be a non-empty string")
74
+ return value
@@ -17,7 +17,9 @@ from uuid import uuid4
17
17
 
18
18
  from cryptography.fernet import Fernet, InvalidToken
19
19
 
20
+ from .edge_events import build_receipt_event
20
21
  from .models import GuardApprovalRequest, GuardArtifact, GuardReceipt, GuardRuntimeState, PolicyDecision
22
+ from .schemas.guard_event_v1 import GuardEventV1
21
23
  from .store_approvals import (
22
24
  add_approval_request as persist_approval_request,
23
25
  )
@@ -548,6 +550,20 @@ class GuardStore:
548
550
  )
549
551
  """,
550
552
  """
553
+ create table if not exists guard_cloud_events (
554
+ event_id text primary key,
555
+ idempotency_key text not null unique,
556
+ event_type text not null,
557
+ payload_json text not null,
558
+ occurred_at text not null,
559
+ uploaded_at text
560
+ )
561
+ """,
562
+ """
563
+ create index if not exists idx_guard_cloud_events_sync
564
+ on guard_cloud_events (uploaded_at, occurred_at)
565
+ """,
566
+ """
551
567
  create table if not exists guard_runtime_state (
552
568
  state_key text primary key,
553
569
  session_id text not null,
@@ -1213,6 +1229,19 @@ class GuardStore:
1213
1229
  receipt.timestamp,
1214
1230
  ),
1215
1231
  )
1232
+ self._ensure_local_device(connection)
1233
+ row = connection.execute(
1234
+ "select installation_id from guard_devices where device_key = ?",
1235
+ (_DEVICE_ROW_KEY,),
1236
+ ).fetchone()
1237
+ device_id = str(row["installation_id"]) if row is not None else None
1238
+ self._add_guard_event_v1(
1239
+ connection,
1240
+ build_receipt_event(
1241
+ receipt,
1242
+ device_id=device_id,
1243
+ ),
1244
+ )
1216
1245
 
1217
1246
  def list_receipts(self, limit: int = 50) -> list[dict[str, object]]:
1218
1247
  with self._connect() as connection:
@@ -1698,6 +1727,72 @@ class GuardStore:
1698
1727
  (state_key,),
1699
1728
  )
1700
1729
 
1730
+ def add_guard_event_v1(self, event: GuardEventV1) -> None:
1731
+ with self._connect() as connection:
1732
+ self._add_guard_event_v1(connection, event)
1733
+
1734
+ @staticmethod
1735
+ def _add_guard_event_v1(connection: sqlite3.Connection, event: GuardEventV1) -> None:
1736
+ payload = event.to_dict()
1737
+ connection.execute(
1738
+ """
1739
+ insert or ignore into guard_cloud_events (
1740
+ event_id, idempotency_key, event_type, payload_json, occurred_at, uploaded_at
1741
+ )
1742
+ values (?, ?, ?, ?, ?, null)
1743
+ """,
1744
+ (
1745
+ event.event_id,
1746
+ event.idempotency_key,
1747
+ event.event_type,
1748
+ json.dumps(payload, sort_keys=True),
1749
+ event.occurred_at,
1750
+ ),
1751
+ )
1752
+
1753
+ def list_guard_events_v1(self, *, uploaded: bool | None = None, limit: int = 200) -> list[dict[str, object]]:
1754
+ query = """
1755
+ select event_id, idempotency_key, event_type, payload_json, occurred_at, uploaded_at
1756
+ from guard_cloud_events
1757
+ """
1758
+ params: list[object] = []
1759
+ if uploaded is True:
1760
+ query += " where uploaded_at is not null"
1761
+ elif uploaded is False:
1762
+ query += " where uploaded_at is null"
1763
+ query += " order by occurred_at asc, event_id asc limit ?"
1764
+ params.append(limit)
1765
+ with self._connect() as connection:
1766
+ rows = connection.execute(query, tuple(params)).fetchall()
1767
+ events: list[dict[str, object]] = []
1768
+ for row in rows:
1769
+ payload = json.loads(str(row["payload_json"]))
1770
+ if not isinstance(payload, dict):
1771
+ payload = {}
1772
+ events.append(
1773
+ {
1774
+ "event_id": str(row["event_id"]),
1775
+ "idempotency_key": str(row["idempotency_key"]),
1776
+ "event_type": str(row["event_type"]),
1777
+ "occurred_at": str(row["occurred_at"]),
1778
+ "uploaded_at": row["uploaded_at"],
1779
+ "payload": payload,
1780
+ }
1781
+ )
1782
+ return events
1783
+
1784
+ def mark_guard_events_v1_uploaded(self, event_ids: list[str], uploaded_at: str) -> int:
1785
+ clean_ids = [event_id for event_id in event_ids if event_id.strip()]
1786
+ if not clean_ids:
1787
+ return 0
1788
+ placeholders = ", ".join("?" for _ in clean_ids)
1789
+ with self._connect() as connection:
1790
+ cursor = connection.execute(
1791
+ f"update guard_cloud_events set uploaded_at = ? where event_id in ({placeholders})",
1792
+ (uploaded_at, *clean_ids),
1793
+ )
1794
+ return int(cursor.rowcount)
1795
+
1701
1796
  def add_event(self, event_name: str, payload: dict[str, object], now: str) -> None:
1702
1797
  with self._connect() as connection:
1703
1798
  connection.execute(
@@ -1,3 +1,3 @@
1
1
  """Single source of truth for tool version."""
2
2
 
3
- __version__ = "2.0.63"
3
+ __version__ = "2.0.65"
@@ -399,7 +399,11 @@ def test_claude_daemon_hook_command_falls_back_without_blocking_prompt_on_daemon
399
399
  assert result.stderr == ""
400
400
  payload = json.loads(result.stdout)
401
401
  assert payload["hookSpecificOutput"]["hookEventName"] == "UserPromptSubmit"
402
- assert "AskUserQuestion" in payload["hookSpecificOutput"]["additionalContext"]
402
+ assert "Do not ask for approval at the prompt stage" in payload["hookSpecificOutput"]["additionalContext"]
403
+ assert (
404
+ "route that concrete action into a HOL Guard approval question"
405
+ in payload["hookSpecificOutput"]["additionalContext"]
406
+ )
403
407
  assert "Keep blocked" in payload["hookSpecificOutput"]["additionalContext"]
404
408
 
405
409