plugin-scanner 2.0.78__tar.gz → 2.0.79__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 (333) hide show
  1. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/PKG-INFO +1 -1
  2. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/pyproject.toml +1 -1
  3. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/pyproject.toml.bak +1 -1
  4. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/codex.py +36 -12
  5. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/cli/commands.py +248 -10
  6. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/store.py +34 -2
  7. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/version.py +1 -1
  8. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_guard_codex_install.py +81 -0
  9. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_guard_runtime.py +261 -0
  10. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/.clusterfuzzlite/Dockerfile +0 -0
  11. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/.clusterfuzzlite/build.sh +0 -0
  12. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/.clusterfuzzlite/project.yaml +0 -0
  13. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/.clusterfuzzlite/requirements-atheris.txt +0 -0
  14. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/.dockerignore +0 -0
  15. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/.github/CODEOWNERS +0 -0
  16. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/.github/ISSUE_TEMPLATE/bug-report.yml +0 -0
  17. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  18. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
  19. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/.github/dependabot.yml +0 -0
  20. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/.github/workflows/ci.yml +0 -0
  21. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/.github/workflows/codeql.yml +0 -0
  22. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/.github/workflows/dependabot-uv-lock.yml +0 -0
  23. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/.github/workflows/fuzz.yml +0 -0
  24. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/.github/workflows/harness-smoke.yml +0 -0
  25. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/.github/workflows/publish.yml +0 -0
  26. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/.github/workflows/scorecard.yml +0 -0
  27. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/.gitignore +0 -0
  28. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/.pre-commit-hooks.yaml +0 -0
  29. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/CONTRIBUTING.md +0 -0
  30. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/Dockerfile +0 -0
  31. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/LICENSE +0 -0
  32. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/README.md +0 -0
  33. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/SECURITY.md +0 -0
  34. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/dashboard/index.html +0 -0
  35. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/dashboard/package.json +0 -0
  36. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/dashboard/pnpm-lock.yaml +0 -0
  37. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/dashboard/public/apple-touch-icon.png +0 -0
  38. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/dashboard/public/brand/Logo_Icon_Dark.png +0 -0
  39. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/dashboard/public/brand/Logo_Whole.png +0 -0
  40. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/dashboard/public/favicon-16x16.png +0 -0
  41. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/dashboard/public/favicon-32x32.png +0 -0
  42. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/dashboard/public/favicon.ico +0 -0
  43. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/dashboard/src/app.tsx +0 -0
  44. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/dashboard/src/approval-center-layout.tsx +0 -0
  45. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/dashboard/src/approval-center-primitives.tsx +0 -0
  46. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/dashboard/src/approval-center-utils.ts +0 -0
  47. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/dashboard/src/fleet-workspace.tsx +0 -0
  48. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/dashboard/src/guard-api.ts +0 -0
  49. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/dashboard/src/guard-demo.ts +0 -0
  50. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/dashboard/src/guard-types.ts +0 -0
  51. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/dashboard/src/main.tsx +0 -0
  52. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/dashboard/src/receipts-workspace.tsx +0 -0
  53. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/dashboard/src/runtime-overview.tsx +0 -0
  54. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/dashboard/src/settings-workspace.tsx +0 -0
  55. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/dashboard/src/styles.css +0 -0
  56. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/dashboard/src/vite-env.d.ts +0 -0
  57. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/dashboard/tsconfig.json +0 -0
  58. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/dashboard/vite.config.ts +0 -0
  59. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/docker-requirements.txt +0 -0
  60. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/docs/guard/approval-audit.md +0 -0
  61. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/docs/guard/architecture.md +0 -0
  62. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/docs/guard/get-started.md +0 -0
  63. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/docs/guard/harness-support.md +0 -0
  64. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/docs/guard/local-vs-cloud.md +0 -0
  65. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/docs/guard/testing-matrix.md +0 -0
  66. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/docs/trust/mcp-trust-draft.md +0 -0
  67. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/docs/trust/plugin-trust-draft.md +0 -0
  68. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/docs/trust/skill-trust-local.md +0 -0
  69. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/fuzzers/manifest_fuzzer.py +0 -0
  70. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/requirements.txt +0 -0
  71. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/schemas/plugin-quality.v1.json +0 -0
  72. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/schemas/scan-result.v1.json +0 -0
  73. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/schemas/verify-result.v1.json +0 -0
  74. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/__init__.py +0 -0
  75. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/action_runner.py +0 -0
  76. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/argparse_utils.py +0 -0
  77. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/__init__.py +0 -0
  78. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/best_practices.py +0 -0
  79. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/claude.py +0 -0
  80. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/code_quality.py +0 -0
  81. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/ecosystem_common.py +0 -0
  82. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/gemini.py +0 -0
  83. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/manifest.py +0 -0
  84. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/manifest_support.py +0 -0
  85. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/marketplace.py +0 -0
  86. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/mcp_security.py +0 -0
  87. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/opencode.py +0 -0
  88. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/operational_security.py +0 -0
  89. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/security.py +0 -0
  90. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/skill_security.py +0 -0
  91. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/cli.py +0 -0
  92. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/cli_ui.py +0 -0
  93. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/config.py +0 -0
  94. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/ecosystems/__init__.py +0 -0
  95. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/ecosystems/base.py +0 -0
  96. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/ecosystems/claude.py +0 -0
  97. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/ecosystems/codex.py +0 -0
  98. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/ecosystems/detect.py +0 -0
  99. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/ecosystems/gemini.py +0 -0
  100. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/ecosystems/opencode.py +0 -0
  101. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/ecosystems/registry.py +0 -0
  102. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/ecosystems/types.py +0 -0
  103. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/github_reporting.py +0 -0
  104. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/__init__.py +0 -0
  105. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/__init__.py +0 -0
  106. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/antigravity.py +0 -0
  107. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/base.py +0 -0
  108. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/claude_code.py +0 -0
  109. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/copilot.py +0 -0
  110. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/cursor.py +0 -0
  111. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/gemini.py +0 -0
  112. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/hermes.py +0 -0
  113. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/mcp_servers.py +0 -0
  114. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/opencode.py +0 -0
  115. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/opencode_artifacts.py +0 -0
  116. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/advisory_model.py +0 -0
  117. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/approvals.py +0 -0
  118. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/bridge/__init__.py +0 -0
  119. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/capabilities.py +0 -0
  120. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/cli/__init__.py +0 -0
  121. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/cli/approval_commands.py +0 -0
  122. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/cli/bootstrap.py +0 -0
  123. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/cli/connect_flow.py +0 -0
  124. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/cli/install_commands.py +0 -0
  125. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/cli/product.py +0 -0
  126. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/cli/prompt.py +0 -0
  127. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/cli/render.py +0 -0
  128. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/cli/update_commands.py +0 -0
  129. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/codex_config.py +0 -0
  130. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/config.py +0 -0
  131. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/consumer/__init__.py +0 -0
  132. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/consumer/service.py +0 -0
  133. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/__init__.py +0 -0
  134. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/client.py +0 -0
  135. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/manager.py +0 -0
  136. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/server.py +0 -0
  137. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/static/apple-touch-icon.png +0 -0
  138. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/static/assets/guard-dashboard.js +0 -0
  139. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/static/assets/index.css +0 -0
  140. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Icon_Dark.png +0 -0
  141. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Whole.png +0 -0
  142. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/static/favicon-16x16.png +0 -0
  143. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/static/favicon-32x32.png +0 -0
  144. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/static/favicon.ico +0 -0
  145. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/static/index.html +0 -0
  146. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/edge_events.py +0 -0
  147. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/incident.py +0 -0
  148. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/launcher.py +0 -0
  149. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/mcp_tool_calls.py +0 -0
  150. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/models.py +0 -0
  151. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/policy/__init__.py +0 -0
  152. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/policy/engine.py +0 -0
  153. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/protect.py +0 -0
  154. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/proxy/__init__.py +0 -0
  155. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/proxy/remote.py +0 -0
  156. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/proxy/runtime_mcp.py +0 -0
  157. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/proxy/stdio.py +0 -0
  158. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/receipts/__init__.py +0 -0
  159. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/receipts/manager.py +0 -0
  160. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/redaction.py +0 -0
  161. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/risk.py +0 -0
  162. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/runtime/__init__.py +0 -0
  163. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/runtime/runner.py +0 -0
  164. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/runtime/secret_file_requests.py +0 -0
  165. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/runtime/surface_server.py +0 -0
  166. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/schemas/__init__.py +0 -0
  167. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/schemas/consumer_mode.py +0 -0
  168. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/schemas/guard_event_v1.py +0 -0
  169. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/schemas/surface_server.py +0 -0
  170. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/shims.py +0 -0
  171. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/store_approvals.py +0 -0
  172. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/store_connect.py +0 -0
  173. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/types.py +0 -0
  174. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/integrations/__init__.py +0 -0
  175. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/integrations/cisco_mcp_scanner.py +0 -0
  176. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/integrations/cisco_skill_scanner.py +0 -0
  177. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/lint_fixes.py +0 -0
  178. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/marketplace_support.py +0 -0
  179. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/models.py +0 -0
  180. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/path_support.py +0 -0
  181. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/policy.py +0 -0
  182. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/quality_artifact.py +0 -0
  183. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/repo_detect.py +0 -0
  184. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/reporting.py +0 -0
  185. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/rules/__init__.py +0 -0
  186. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/rules/registry.py +0 -0
  187. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/rules/specs.py +0 -0
  188. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/scanner.py +0 -0
  189. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/submission.py +0 -0
  190. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/suppressions.py +0 -0
  191. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/trust_domain_scoring.py +0 -0
  192. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/trust_helpers.py +0 -0
  193. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/trust_mcp_scoring.py +0 -0
  194. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/trust_models.py +0 -0
  195. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/trust_plugin_scoring.py +0 -0
  196. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/trust_scoring.py +0 -0
  197. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/trust_skill_scoring.py +0 -0
  198. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/trust_specs.py +0 -0
  199. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/verification.py +0 -0
  200. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/__init__.py +0 -0
  201. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/conftest.py +0 -0
  202. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/__init__.py +0 -0
  203. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/bad-plugin/.codex-plugin/plugin.json +0 -0
  204. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/bad-plugin/.mcp.json +0 -0
  205. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/bad-plugin/secrets.js +0 -0
  206. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/claude-plugin-good/.claude-plugin/plugin.json +0 -0
  207. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/claude-plugin-good/LICENSE +0 -0
  208. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/claude-plugin-good/README.md +0 -0
  209. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/claude-plugin-good/SECURITY.md +0 -0
  210. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/claude-plugin-good/hooks/hooks.json +0 -0
  211. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/claude-plugin-good/skills/example/SKILL.md +0 -0
  212. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/code-quality-bad/evil.js +0 -0
  213. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/code-quality-bad/inject.js +0 -0
  214. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/gemini-extension-good/GEMINI.md +0 -0
  215. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/gemini-extension-good/LICENSE +0 -0
  216. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/gemini-extension-good/README.md +0 -0
  217. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/gemini-extension-good/SECURITY.md +0 -0
  218. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/gemini-extension-good/commands/hello.toml +0 -0
  219. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/gemini-extension-good/gemini-extension.json +0 -0
  220. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/good-plugin/.codex-plugin/plugin.json +0 -0
  221. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/good-plugin/.codexignore +0 -0
  222. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/good-plugin/LICENSE +0 -0
  223. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/good-plugin/README.md +0 -0
  224. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/good-plugin/SECURITY.md +0 -0
  225. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/good-plugin/assets/icon.svg +0 -0
  226. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/good-plugin/assets/logo.svg +0 -0
  227. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/good-plugin/assets/screenshot.svg +0 -0
  228. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/good-plugin/skills/example/SKILL.md +0 -0
  229. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/guard-codex-malicious-mcp/.codex/config.toml +0 -0
  230. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/hermes-plugin-evil/config.yaml +0 -0
  231. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/hermes-plugin-evil/mcp_servers.json +0 -0
  232. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/hermes-plugin-evil/skills/security/malicious/SKILL.md +0 -0
  233. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/SKILL.md +0 -0
  234. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/references/api-setup.md +0 -0
  235. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/scripts/deploy.sh +0 -0
  236. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/hermes-plugin-evil/skills/utils/benign/SKILL.md +0 -0
  237. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/malformed-json/.codex-plugin/plugin.json +0 -0
  238. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/malicious-skill-plugin/.codex-plugin/plugin.json +0 -0
  239. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/malicious-skill-plugin/.codexignore +0 -0
  240. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/malicious-skill-plugin/LICENSE +0 -0
  241. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/malicious-skill-plugin/README.md +0 -0
  242. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/malicious-skill-plugin/SECURITY.md +0 -0
  243. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/malicious-skill-plugin/skills/leaky-skill/SKILL.md +0 -0
  244. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/mcp-canary-server.py +0 -0
  245. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/minimal-plugin/.codex-plugin/plugin.json +0 -0
  246. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/missing-fields/.codex-plugin/plugin.json +0 -0
  247. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/mit-license/LICENSE +0 -0
  248. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/multi-ecosystem-repo/codex-plugin/.codex-plugin/plugin.json +0 -0
  249. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/multi-ecosystem-repo/codex-plugin/LICENSE +0 -0
  250. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/multi-ecosystem-repo/codex-plugin/README.md +0 -0
  251. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/multi-ecosystem-repo/codex-plugin/SECURITY.md +0 -0
  252. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/multi-ecosystem-repo/gemini-ext/README.md +0 -0
  253. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/multi-ecosystem-repo/gemini-ext/gemini-extension.json +0 -0
  254. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/multi-plugin-repo/.agents/plugins/marketplace.json +0 -0
  255. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codex-plugin/plugin.json +0 -0
  256. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codexignore +0 -0
  257. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/LICENSE +0 -0
  258. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/README.md +0 -0
  259. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/SECURITY.md +0 -0
  260. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/skills/example/SKILL.md +0 -0
  261. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/.codex-plugin/plugin.json +0 -0
  262. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/skills/example/SKILL.md +0 -0
  263. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/no-version/.codex-plugin/plugin.json +0 -0
  264. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/opencode-good/.opencode/commands/hello.md +0 -0
  265. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/opencode-good/.opencode/plugins/example.ts +0 -0
  266. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/opencode-good/LICENSE +0 -0
  267. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/opencode-good/README.md +0 -0
  268. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/opencode-good/SECURITY.md +0 -0
  269. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/opencode-good/opencode.jsonc +0 -0
  270. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/skills-missing-dir/.codex-plugin/plugin.json +0 -0
  271. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/skills-no-frontmatter/.codex-plugin/plugin.json +0 -0
  272. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/skills-no-frontmatter/skills/bad-skill/SKILL.md +0 -0
  273. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/with-marketplace/.codex-plugin/plugin.json +0 -0
  274. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/with-marketplace/marketplace-broken.json +0 -0
  275. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/fixtures/with-marketplace/marketplace.json +0 -0
  276. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test-trust-scoring.py +0 -0
  277. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test-trust-specs.py +0 -0
  278. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_action_runner.py +0 -0
  279. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_best_practices.py +0 -0
  280. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_cisco_install_surfaces.py +0 -0
  281. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_cli.py +0 -0
  282. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_code_quality.py +0 -0
  283. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_config.py +0 -0
  284. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_coverage_remaining.py +0 -0
  285. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_ecosystems.py +0 -0
  286. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_edge_cases.py +0 -0
  287. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_final_coverage.py +0 -0
  288. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_guard_approvals.py +0 -0
  289. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_guard_bootstrap.py +0 -0
  290. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_guard_capabilities.py +0 -0
  291. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_guard_claude_adapter.py +0 -0
  292. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_guard_cli.py +0 -0
  293. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_guard_codex_e2e.py +0 -0
  294. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_guard_codex_proxy.py +0 -0
  295. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_guard_config_paths.py +0 -0
  296. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_guard_connect_flow.py +0 -0
  297. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_guard_consumer_mode.py +0 -0
  298. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_guard_copilot_adapter.py +0 -0
  299. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_guard_copilot_proxy.py +0 -0
  300. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_guard_daemon_manager.py +0 -0
  301. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_guard_event_schema_v1.py +0 -0
  302. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_guard_events.py +0 -0
  303. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_guard_launch_env.py +0 -0
  304. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_guard_opencode_proxy.py +0 -0
  305. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_guard_product_flow.py +0 -0
  306. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_guard_protect.py +0 -0
  307. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_guard_render.py +0 -0
  308. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_guard_risk.py +0 -0
  309. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_guard_store_migrations.py +0 -0
  310. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_guard_surface_server.py +0 -0
  311. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_guard_verdicts.py +0 -0
  312. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_hermes_adapter.py +0 -0
  313. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_integration.py +0 -0
  314. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_lint_fixes.py +0 -0
  315. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_live_cisco_smoke.py +0 -0
  316. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_manifest.py +0 -0
  317. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_marketplace.py +0 -0
  318. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_mcp_security.py +0 -0
  319. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_operational_security.py +0 -0
  320. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_policy.py +0 -0
  321. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_quality_artifact.py +0 -0
  322. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_rule_registry.py +0 -0
  323. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_scanner.py +0 -0
  324. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_schema_contracts.py +0 -0
  325. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_security.py +0 -0
  326. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_security_ops.py +0 -0
  327. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_skill_security.py +0 -0
  328. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_submission.py +0 -0
  329. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_trust_scoring.py +0 -0
  330. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_trust_specs.py +0 -0
  331. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_verification.py +0 -0
  332. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/tests/test_versioning.py +0 -0
  333. {plugin_scanner-2.0.78 → plugin_scanner-2.0.79}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plugin-scanner
3
- Version: 2.0.78
3
+ Version: 2.0.79
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.78"
7
+ version = "2.0.79"
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.78"
7
+ version = "2.0.79"
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"
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import hashlib
6
6
  import json
7
7
  import os
8
+ import re
8
9
  import shlex
9
10
  import sys
10
11
  from copy import deepcopy
@@ -43,11 +44,13 @@ def _read_toml(path: Path) -> dict[str, object]:
43
44
  _MANAGED_HOOK_STATUS_MESSAGE = "HOL Guard checking tool action"
44
45
  _MANAGED_PROMPT_HOOK_STATUS_MESSAGE = "HOL Guard checking prompt"
45
46
  _MANAGED_PERMISSION_HOOK_STATUS_MESSAGE = "HOL Guard checking Codex approval request"
47
+ _MANAGED_POST_TOOL_HOOK_STATUS_MESSAGE = "HOL Guard checking tool result"
46
48
  _LEGACY_MANAGED_HOOK_STATUS_MESSAGES = {
47
49
  "HOL Guard checking Bash command",
48
50
  _MANAGED_HOOK_STATUS_MESSAGE,
49
51
  _MANAGED_PROMPT_HOOK_STATUS_MESSAGE,
50
52
  _MANAGED_PERMISSION_HOOK_STATUS_MESSAGE,
53
+ _MANAGED_POST_TOOL_HOOK_STATUS_MESSAGE,
51
54
  }
52
55
 
53
56
 
@@ -113,7 +116,7 @@ def _pre_tool_hook_group(context: HarnessContext) -> dict[str, object]:
113
116
  {
114
117
  "type": "command",
115
118
  "command": _hook_command(context),
116
- "timeoutSec": 30,
119
+ "timeout": 30,
117
120
  "statusMessage": _MANAGED_HOOK_STATUS_MESSAGE,
118
121
  }
119
122
  ],
@@ -126,7 +129,7 @@ def _prompt_hook_group(context: HarnessContext) -> dict[str, object]:
126
129
  {
127
130
  "type": "command",
128
131
  "command": _hook_command(context),
129
- "timeoutSec": 30,
132
+ "timeout": 30,
130
133
  "statusMessage": _MANAGED_PROMPT_HOOK_STATUS_MESSAGE,
131
134
  }
132
135
  ],
@@ -140,18 +143,33 @@ def _permission_request_hook_group(context: HarnessContext) -> dict[str, object]
140
143
  {
141
144
  "type": "command",
142
145
  "command": _hook_command(context),
143
- "timeoutSec": 30,
146
+ "timeout": 30,
144
147
  "statusMessage": _MANAGED_PERMISSION_HOOK_STATUS_MESSAGE,
145
148
  }
146
149
  ],
147
150
  }
148
151
 
149
152
 
153
+ def _post_tool_hook_group(context: HarnessContext) -> dict[str, object]:
154
+ return {
155
+ "matcher": "Bash",
156
+ "hooks": [
157
+ {
158
+ "type": "command",
159
+ "command": _hook_command(context),
160
+ "timeout": 30,
161
+ "statusMessage": _MANAGED_POST_TOOL_HOOK_STATUS_MESSAGE,
162
+ }
163
+ ],
164
+ }
165
+
166
+
150
167
  def _managed_hook_groups(context: HarnessContext) -> dict[str, dict[str, object]]:
151
168
  return {
152
169
  "PreToolUse": _pre_tool_hook_group(context),
153
170
  "PermissionRequest": _permission_request_hook_group(context),
154
171
  "UserPromptSubmit": _prompt_hook_group(context),
172
+ "PostToolUse": _post_tool_hook_group(context),
155
173
  }
156
174
 
157
175
 
@@ -179,14 +197,13 @@ def _is_managed_hook_command(command: object) -> bool:
179
197
  if tokens[1] != "-c":
180
198
  return False
181
199
  code = tokens[2]
182
- return (
183
- "codex_plugin_scanner.cli" in code
184
- and "main([" in code
185
- and "'guard'" in code
186
- and "'hook'" in code
187
- and "'--harness'" in code
188
- and "'codex'" in code
200
+ has_guard_call = (
201
+ re.search(r"['\"]guard['\"]", code) is not None
202
+ and re.search(r"['\"]hook['\"]", code) is not None
203
+ and re.search(r"['\"]--harness['\"]", code) is not None
204
+ and re.search(r"['\"]codex['\"]", code) is not None
189
205
  )
206
+ return "codex_plugin_scanner.cli" in code and "main([" in code and has_guard_call
190
207
 
191
208
 
192
209
  def _argv_targets_codex(argv: list[str]) -> bool:
@@ -249,7 +266,7 @@ def _remove_hook_groups(groups: object) -> list[object]:
249
266
  def _remove_managed_hook_events(hooks: dict[str, object]) -> tuple[dict[str, object], bool]:
250
267
  updated_hooks = dict(hooks)
251
268
  changed = False
252
- for event_name in ("PreToolUse", "PermissionRequest", "UserPromptSubmit"):
269
+ for event_name in ("PreToolUse", "PermissionRequest", "UserPromptSubmit", "PostToolUse"):
253
270
  original_groups = deepcopy(updated_hooks.get(event_name))
254
271
  remaining = _remove_hook_groups(original_groups)
255
272
  managed_removed = isinstance(original_groups, list) and remaining != original_groups
@@ -273,6 +290,7 @@ def codex_native_hook_state(context: HarnessContext) -> dict[str, object]:
273
290
  pre_tool_groups = hooks.get("PreToolUse") if isinstance(hooks, dict) else None
274
291
  permission_groups = hooks.get("PermissionRequest") if isinstance(hooks, dict) else None
275
292
  prompt_groups = hooks.get("UserPromptSubmit") if isinstance(hooks, dict) else None
293
+ post_tool_groups = hooks.get("PostToolUse") if isinstance(hooks, dict) else None
276
294
  pre_tool_hook_installed = isinstance(pre_tool_groups, list) and any(
277
295
  _is_managed_hook_group(group) for group in pre_tool_groups
278
296
  )
@@ -282,7 +300,12 @@ def codex_native_hook_state(context: HarnessContext) -> dict[str, object]:
282
300
  prompt_hook_installed = isinstance(prompt_groups, list) and any(
283
301
  _is_managed_hook_group(group) for group in prompt_groups
284
302
  )
285
- managed_hook_installed = pre_tool_hook_installed and permission_hook_installed and prompt_hook_installed
303
+ post_tool_hook_installed = isinstance(post_tool_groups, list) and any(
304
+ _is_managed_hook_group(group) for group in post_tool_groups
305
+ )
306
+ managed_hook_installed = (
307
+ pre_tool_hook_installed and permission_hook_installed and prompt_hook_installed and post_tool_hook_installed
308
+ )
286
309
  return {
287
310
  "config_path": str(config_path),
288
311
  "config_present": config_path.is_file(),
@@ -292,6 +315,7 @@ def codex_native_hook_state(context: HarnessContext) -> dict[str, object]:
292
315
  "managed_pre_tool_hook_installed": pre_tool_hook_installed,
293
316
  "managed_permission_request_hook_installed": permission_hook_installed,
294
317
  "managed_prompt_hook_installed": prompt_hook_installed,
318
+ "managed_post_tool_hook_installed": post_tool_hook_installed,
295
319
  "managed_hook_installed": managed_hook_installed,
296
320
  "protection_active": isinstance(features, dict)
297
321
  and features.get("codex_hooks") is True
@@ -1419,11 +1419,13 @@ def run_guard_command(
1419
1419
  artifact_id = runtime_artifact.artifact_id
1420
1420
  artifact_name = runtime_artifact.name
1421
1421
  policy_harness = _canonical_harness_name(args.harness)
1422
- stored_policy_action = store.resolve_policy(
1423
- policy_harness,
1424
- artifact_id,
1425
- runtime_artifact_hash,
1426
- str(runtime_workspace) if runtime_workspace else None,
1422
+ stored_policy_action = _runtime_stored_policy_action(
1423
+ store=store,
1424
+ harness=policy_harness,
1425
+ artifact=runtime_artifact,
1426
+ artifact_id=artifact_id,
1427
+ artifact_hash=runtime_artifact_hash,
1428
+ workspace=str(runtime_workspace) if runtime_workspace else None,
1427
1429
  )
1428
1430
  if stored_policy_action is None:
1429
1431
  legacy_artifact = _legacy_claude_alias_runtime_artifact(
@@ -1433,11 +1435,13 @@ def run_guard_command(
1433
1435
  workspace=runtime_workspace,
1434
1436
  )
1435
1437
  if legacy_artifact is not None:
1436
- stored_policy_action = store.resolve_policy(
1437
- args.harness,
1438
- legacy_artifact.artifact_id,
1439
- artifact_hash(legacy_artifact),
1440
- str(runtime_workspace) if runtime_workspace else None,
1438
+ stored_policy_action = _runtime_stored_policy_action(
1439
+ store=store,
1440
+ harness=args.harness,
1441
+ artifact=legacy_artifact,
1442
+ artifact_id=legacy_artifact.artifact_id,
1443
+ artifact_hash=artifact_hash(legacy_artifact),
1444
+ workspace=str(runtime_workspace) if runtime_workspace else None,
1441
1445
  )
1442
1446
  policy_action = _coalesce_string(
1443
1447
  getattr(args, "policy_action", None),
@@ -2771,6 +2775,37 @@ def _native_approval_center_context(response_payload: dict[str, object], *, harn
2771
2775
  )
2772
2776
 
2773
2777
 
2778
+ def _runtime_stored_policy_action(
2779
+ *,
2780
+ store: GuardStore,
2781
+ harness: str,
2782
+ artifact: GuardArtifact,
2783
+ artifact_id: str,
2784
+ artifact_hash: str,
2785
+ workspace: str | None,
2786
+ ) -> str | None:
2787
+ decision = store.resolve_policy_decision(
2788
+ harness,
2789
+ artifact_id,
2790
+ artifact_hash,
2791
+ workspace,
2792
+ artifact.publisher,
2793
+ )
2794
+ if decision is None:
2795
+ return None
2796
+ action = _optional_string(decision.get("action"))
2797
+ if action is None:
2798
+ return None
2799
+ scope = _optional_string(decision.get("scope"))
2800
+ if (
2801
+ action in {"allow", "warn", "review"}
2802
+ and scope in {"workspace", "publisher", "harness", "global"}
2803
+ and _runtime_artifact_risk_classes(artifact)
2804
+ ):
2805
+ return None
2806
+ return action
2807
+
2808
+
2774
2809
  def _runtime_artifact_policy_action(config: GuardConfig, artifact: GuardArtifact, harness: str) -> str:
2775
2810
  if _prompt_requires_hard_block(artifact):
2776
2811
  return "block"
@@ -2933,6 +2968,13 @@ def _runtime_artifact_native_reason(artifact: GuardArtifact, response_payload: d
2933
2968
  harness = response_payload.get("harness")
2934
2969
  prompt_classes = _prompt_request_classes(artifact)
2935
2970
  if harness == "codex" and "secret_read" in prompt_classes:
2971
+ prompt_summary = artifact.metadata.get("prompt_summary")
2972
+ if isinstance(prompt_summary, str) and "credential-looking local file" in prompt_summary:
2973
+ return (
2974
+ "HOL Guard stopped this Codex prompt before Codex could open a credential-looking local file. "
2975
+ "Codex does not expose native approval prompts for Read-tool file reads, so Guard blocks this "
2976
+ "request at prompt time."
2977
+ )
2936
2978
  return (
2937
2979
  "HOL Guard stopped this Codex prompt before Codex could open a sensitive local file. Codex does not "
2938
2980
  "expose native approval prompts for Read-tool file reads, so Guard blocks this request at prompt time."
@@ -3164,6 +3206,13 @@ def _emit_native_hook_response(
3164
3206
  if payload:
3165
3207
  _write_json_line(payload, output_stream=output_stream)
3166
3208
  return
3209
+ if event_name == "PostToolUse" and policy_action in {"block", "sandbox-required", "require-reapproval"}:
3210
+ payload["decision"] = "block"
3211
+ payload["reason"] = reason
3212
+ payload["continue"] = False
3213
+ payload["stopReason"] = reason
3214
+ _write_json_line(payload, output_stream=output_stream)
3215
+ return
3167
3216
  permission_decision = _native_hook_permission_decision(policy_action, harness=harness)
3168
3217
  if harness == "codex" and event_name == "PreToolUse" and permission_decision is None:
3169
3218
  return
@@ -3594,6 +3643,14 @@ def _hook_runtime_artifact(
3594
3643
  ) -> GuardArtifact | None:
3595
3644
  harness = _canonical_harness_name(harness)
3596
3645
  event_name = _hook_event_name(payload)
3646
+ if harness == "codex" and event_name == "PostToolUse":
3647
+ output_artifact = _codex_post_tool_output_artifact(
3648
+ payload=payload,
3649
+ config_path=str(_runtime_policy_path(harness, home_dir, workspace)),
3650
+ source_scope=_coalesce_string(payload.get("source_scope"), "project"),
3651
+ )
3652
+ if output_artifact is not None:
3653
+ return output_artifact
3597
3654
  if event_name == "UserPromptSubmit":
3598
3655
  prompt_text = payload.get("prompt")
3599
3656
  if isinstance(prompt_text, str) and prompt_text.strip():
@@ -3619,6 +3676,13 @@ def _hook_runtime_artifact(
3619
3676
  )
3620
3677
  if prompt_artifacts:
3621
3678
  return _merged_prompt_runtime_artifact(harness, prompt_artifacts)
3679
+ prompt_file_artifact = _codex_prompt_credential_file_artifact(
3680
+ prompt_text=prompt_text,
3681
+ cwd=workspace,
3682
+ config_path=config_path,
3683
+ )
3684
+ if prompt_file_artifact is not None:
3685
+ return prompt_file_artifact
3622
3686
  request = extract_sensitive_file_read_request(
3623
3687
  payload.get("tool_name"),
3624
3688
  payload.get("tool_input", payload.get("arguments")),
@@ -3650,6 +3714,180 @@ def _hook_runtime_artifact(
3650
3714
  )
3651
3715
 
3652
3716
 
3717
+ _CODEX_SECRET_OUTPUT_PATTERN = re.compile(
3718
+ r"(?i)(?:fake[_-]?credential|fake[_-]?secret|"
3719
+ r"(?:api[_-]?key|auth[_-]?token|credential|npm[_-]?token|private[_-]?key|secret|token|password)\s*[:=])"
3720
+ )
3721
+ _CODEX_TOOL_RESPONSE_MAX_DEPTH = 5
3722
+ _CODEX_TOOL_RESPONSE_TEXT_LIMIT = 20000
3723
+ _CODEX_PROMPT_FILE_FINGERPRINT_LENGTH = 24
3724
+
3725
+
3726
+ def _codex_post_tool_output_artifact(
3727
+ *,
3728
+ payload: dict[str, object],
3729
+ config_path: str,
3730
+ source_scope: str,
3731
+ ) -> GuardArtifact | None:
3732
+ response_text = _collect_codex_tool_response_text(payload.get("tool_response"))
3733
+ if not response_text or _CODEX_SECRET_OUTPUT_PATTERN.search(response_text) is None:
3734
+ return None
3735
+ tool_name = _coalesce_string(payload.get("tool_name"), "Bash")
3736
+ tool_input = payload.get("tool_input")
3737
+ command_text = ""
3738
+ if isinstance(tool_input, dict):
3739
+ command = tool_input.get("command")
3740
+ if isinstance(command, str):
3741
+ command_text = command.strip()
3742
+ if not command_text:
3743
+ command_text = tool_name
3744
+ fingerprint = hashlib.sha256(
3745
+ json.dumps(
3746
+ {
3747
+ "tool_name": tool_name,
3748
+ "command_text": command_text,
3749
+ "output_class": "credential-looking output",
3750
+ },
3751
+ sort_keys=True,
3752
+ ).encode("utf-8")
3753
+ ).hexdigest()
3754
+ return GuardArtifact(
3755
+ artifact_id=f"codex:{source_scope}:tool-output:{fingerprint}",
3756
+ name=f"{tool_name} credential-looking output",
3757
+ harness="codex",
3758
+ artifact_type="tool_action_request",
3759
+ source_scope=source_scope,
3760
+ config_path=config_path,
3761
+ metadata={
3762
+ "tool_name": tool_name,
3763
+ "command_text": command_text,
3764
+ "action_class": "credential exfiltration shell command",
3765
+ "request_summary": (
3766
+ f"Codex tool `{tool_name}` produced credential-looking output while running `{command_text}`."
3767
+ ),
3768
+ "runtime_request_signals": ["tool output contains credential-looking material"],
3769
+ "runtime_request_summary": (
3770
+ "Requests a sensitive native tool action: credential-looking output reached Codex."
3771
+ ),
3772
+ "runtime_request_reason": (
3773
+ "Guard inspects supported Codex tool output before Codex uses it, so accidental secret reads can be "
3774
+ "stopped even when the filename was not obviously sensitive."
3775
+ ),
3776
+ },
3777
+ )
3778
+
3779
+
3780
+ def _collect_codex_tool_response_text(value: object, *, depth: int = 0) -> str:
3781
+ if depth > _CODEX_TOOL_RESPONSE_MAX_DEPTH:
3782
+ return ""
3783
+ if isinstance(value, str):
3784
+ return value[:_CODEX_TOOL_RESPONSE_TEXT_LIMIT]
3785
+ if isinstance(value, dict):
3786
+ parts: list[str] = []
3787
+ for key, child in value.items():
3788
+ key_text = str(key).lower()
3789
+ if key_text in {"stdout", "stderr", "output", "text", "content", "result", "message"} or depth > 0:
3790
+ text = _collect_codex_tool_response_text(child, depth=depth + 1)
3791
+ if text:
3792
+ parts.append(text)
3793
+ return "\n".join(parts)[:_CODEX_TOOL_RESPONSE_TEXT_LIMIT]
3794
+ if isinstance(value, list):
3795
+ return "\n".join(_collect_codex_tool_response_text(item, depth=depth + 1) for item in value)[
3796
+ :_CODEX_TOOL_RESPONSE_TEXT_LIMIT
3797
+ ]
3798
+ return ""
3799
+
3800
+
3801
+ _PROMPT_PATH_TOKEN_PATTERN = re.compile(
3802
+ r"(?<![\w/.-])\.[A-Za-z0-9][A-Za-z0-9_.-]{0,255}|"
3803
+ r"(?:~|\.{1,2}|/)[^\s'\"`<>|;(){}\[\]]{0,255}"
3804
+ )
3805
+ _PROMPT_FILE_READ_VERB_PATTERN = re.compile(r"\b(?:read|open|print|show|dump|cat|head|tail|less|view|display)\b", re.I)
3806
+ _PROMPT_CONTENT_SCAN_MAX_BYTES = 64 * 1024
3807
+ _PROMPT_CONTENT_SCAN_SKIP_BASENAMES = frozenset(
3808
+ {
3809
+ ".env",
3810
+ ".npmrc",
3811
+ ".pypirc",
3812
+ ".netrc",
3813
+ ".git-credentials",
3814
+ }
3815
+ )
3816
+
3817
+
3818
+ def _codex_prompt_credential_file_artifact(
3819
+ *,
3820
+ prompt_text: str,
3821
+ cwd: Path | None,
3822
+ config_path: str,
3823
+ ) -> GuardArtifact | None:
3824
+ if _PROMPT_FILE_READ_VERB_PATTERN.search(prompt_text) is None:
3825
+ return None
3826
+ for match in _PROMPT_PATH_TOKEN_PATTERN.finditer(prompt_text):
3827
+ requested_path = match.group(0)
3828
+ path = _resolve_prompt_scan_path(requested_path, cwd=cwd)
3829
+ if path is None or path.name in _PROMPT_CONTENT_SCAN_SKIP_BASENAMES:
3830
+ continue
3831
+ if not path.name.startswith("."):
3832
+ continue
3833
+ if not path.is_file():
3834
+ continue
3835
+ try:
3836
+ with path.open("rb") as handle:
3837
+ content = handle.read(_PROMPT_CONTENT_SCAN_MAX_BYTES).decode("utf-8", errors="ignore")
3838
+ except OSError:
3839
+ continue
3840
+ if _CODEX_SECRET_OUTPUT_PATTERN.search(content) is None:
3841
+ continue
3842
+ normalized_path = str(path)
3843
+ fingerprint = hashlib.sha256(
3844
+ json.dumps(
3845
+ {
3846
+ "harness": "codex",
3847
+ "prompt_path": normalized_path,
3848
+ "content_class": "credential-looking local file",
3849
+ },
3850
+ sort_keys=True,
3851
+ ).encode("utf-8")
3852
+ ).hexdigest()[:_CODEX_PROMPT_FILE_FINGERPRINT_LENGTH]
3853
+ return GuardArtifact(
3854
+ artifact_id=f"codex:project:prompt-file:{fingerprint}",
3855
+ name=f"credential-looking local file {path.name}",
3856
+ harness="codex",
3857
+ artifact_type="prompt_request",
3858
+ source_scope="project",
3859
+ config_path=config_path,
3860
+ metadata={
3861
+ "prompt_signals": ["requested file content contains credential-looking material"],
3862
+ "prompt_summary": "Prompt asks Codex to read a credential-looking local file.",
3863
+ "prompt_matched_text": requested_path,
3864
+ "prompt_request_class": "secret_read",
3865
+ "prompt_request_classes": ["secret_read"],
3866
+ "runtime_request_summary": "Prompt requests direct access to a credential-looking local file.",
3867
+ "runtime_request_reason": (
3868
+ "Guard scanned a small local dotfile before Codex read it and found credential-looking text."
3869
+ ),
3870
+ "normalized_path": normalized_path,
3871
+ },
3872
+ )
3873
+ return None
3874
+
3875
+
3876
+ def _resolve_prompt_scan_path(requested_path: str, *, cwd: Path | None) -> Path | None:
3877
+ stripped = requested_path.strip().strip("'\"").rstrip(".,;:!?)]}")
3878
+ if not stripped:
3879
+ return None
3880
+ try:
3881
+ expanded = Path(stripped).expanduser()
3882
+ except RuntimeError:
3883
+ return None
3884
+ if not expanded.is_absolute():
3885
+ expanded = (cwd or Path.cwd()) / expanded
3886
+ with suppress(OSError):
3887
+ return expanded.resolve(strict=False)
3888
+ return expanded
3889
+
3890
+
3653
3891
  def _legacy_claude_alias_runtime_artifact(
3654
3892
  *,
3655
3893
  artifact: GuardArtifact,
@@ -1170,11 +1170,31 @@ class GuardStore:
1170
1170
  publisher: str | None = None,
1171
1171
  now: str | None = None,
1172
1172
  ) -> str | None:
1173
+ decision = self.resolve_policy_decision(
1174
+ harness,
1175
+ artifact_id,
1176
+ artifact_hash,
1177
+ workspace,
1178
+ publisher,
1179
+ now,
1180
+ )
1181
+ return str(decision["action"]) if decision is not None else None
1182
+
1183
+ def resolve_policy_decision(
1184
+ self,
1185
+ harness: str,
1186
+ artifact_id: str | None,
1187
+ artifact_hash: str | None = None,
1188
+ workspace: str | None = None,
1189
+ publisher: str | None = None,
1190
+ now: str | None = None,
1191
+ ) -> dict[str, str | None] | None:
1173
1192
  current_time = now or _now()
1174
1193
  with self._connect() as connection:
1175
1194
  rows = connection.execute(
1176
1195
  """
1177
- select scope, action, artifact_hash from policy_decisions
1196
+ select harness, scope, artifact_id, action, artifact_hash, workspace, publisher, source
1197
+ from policy_decisions
1178
1198
  where (harness = ? or harness = '*') and (
1179
1199
  (
1180
1200
  scope = 'artifact' and artifact_id = ? and (
@@ -1193,7 +1213,19 @@ class GuardStore:
1193
1213
  """,
1194
1214
  (harness, artifact_id, artifact_hash, artifact_hash, workspace, publisher, current_time),
1195
1215
  ).fetchall()
1196
- return str(rows[0]["action"]) if rows else None
1216
+ if not rows:
1217
+ return None
1218
+ row = rows[0]
1219
+ return {
1220
+ "harness": row["harness"],
1221
+ "scope": row["scope"],
1222
+ "artifact_id": row["artifact_id"],
1223
+ "artifact_hash": row["artifact_hash"],
1224
+ "workspace": row["workspace"],
1225
+ "publisher": row["publisher"],
1226
+ "action": row["action"],
1227
+ "source": row["source"],
1228
+ }
1197
1229
 
1198
1230
  @staticmethod
1199
1231
  def _normalized_policy_keys(decision: PolicyDecision) -> tuple[str | None, str | None, str | None, str | None]:
@@ -1,3 +1,3 @@
1
1
  """Single source of truth for tool version."""
2
2
 
3
- __version__ = "2.0.78"
3
+ __version__ = "2.0.79"
@@ -2,7 +2,9 @@ from __future__ import annotations
2
2
 
3
3
  import json
4
4
  import os
5
+ import shlex
5
6
  import subprocess
7
+ import sys
6
8
  from pathlib import Path
7
9
 
8
10
  try:
@@ -271,6 +273,85 @@ def test_guard_install_codex_migrates_legacy_bash_only_managed_hook(tmp_path, ca
271
273
  assert len(hooks_payload["hooks"]["UserPromptSubmit"]) == 1
272
274
 
273
275
 
276
+ def test_guard_install_codex_migrates_stale_python_c_managed_hooks(tmp_path, capsys):
277
+ home_dir = tmp_path / "home"
278
+ workspace_dir = tmp_path / "workspace"
279
+ stale_worktree = tmp_path / "deleted-worktree"
280
+ _write_text(workspace_dir / ".codex" / "config.toml", 'approval_policy = "never"\n')
281
+ stale_command = (
282
+ f"{sys.executable} -c "
283
+ + shlex.quote(
284
+ "import sys;"
285
+ f"sys.path[:0]=[{str(stale_worktree / 'src')!r}];"
286
+ "from codex_plugin_scanner.cli import main;"
287
+ "raise SystemExit(main(["
288
+ '"guard", "hook", "--guard-home", '
289
+ f"{str(home_dir / '.hol-guard')!r}, "
290
+ '"--harness", "codex"'
291
+ "]))"
292
+ )
293
+ )
294
+ _write_text(
295
+ workspace_dir / ".codex" / "hooks.json",
296
+ json.dumps(
297
+ {
298
+ "hooks": {
299
+ "PreToolUse": [
300
+ {
301
+ "matcher": "Bash",
302
+ "hooks": [
303
+ {
304
+ "type": "command",
305
+ "command": stale_command,
306
+ "timeoutSec": 30,
307
+ "statusMessage": "HOL Guard checking tool action",
308
+ }
309
+ ],
310
+ }
311
+ ],
312
+ "UserPromptSubmit": [
313
+ {
314
+ "hooks": [
315
+ {
316
+ "type": "command",
317
+ "command": stale_command,
318
+ "timeoutSec": 30,
319
+ "statusMessage": "HOL Guard checking prompt",
320
+ }
321
+ ]
322
+ }
323
+ ],
324
+ }
325
+ },
326
+ indent=2,
327
+ )
328
+ + "\n",
329
+ )
330
+
331
+ install_rc = main(
332
+ [
333
+ "guard",
334
+ "install",
335
+ "codex",
336
+ "--home",
337
+ str(home_dir),
338
+ "--workspace",
339
+ str(workspace_dir),
340
+ "--json",
341
+ ]
342
+ )
343
+ json.loads(capsys.readouterr().out)
344
+ hooks_payload = json.loads((workspace_dir / ".codex" / "hooks.json").read_text(encoding="utf-8"))
345
+
346
+ assert install_rc == 0
347
+ assert len(hooks_payload["hooks"]["PreToolUse"]) == 1
348
+ assert len(hooks_payload["hooks"]["UserPromptSubmit"]) == 1
349
+ assert len(hooks_payload["hooks"]["PermissionRequest"]) == 1
350
+ assert len(hooks_payload["hooks"]["PostToolUse"]) == 1
351
+ all_commands = json.dumps(hooks_payload)
352
+ assert str(stale_worktree) not in all_commands
353
+
354
+
274
355
  def test_guard_install_codex_workspace_cleans_stale_global_managed_hook(tmp_path, capsys):
275
356
  home_dir = tmp_path / "home"
276
357
  workspace_dir = tmp_path / "workspace"