plugin-scanner 2.0.133__tar.gz → 2.0.135__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 (427) hide show
  1. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/PKG-INFO +1 -1
  2. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/pyproject.toml +1 -1
  3. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/pyproject.toml.bak +1 -1
  4. plugin_scanner-2.0.135/src/codex_plugin_scanner/guard/runtime/action_identity.py +87 -0
  5. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/store.py +1 -0
  6. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/store_approvals.py +36 -5
  7. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/version.py +1 -1
  8. plugin_scanner-2.0.135/tests/test_guard_action_identity.py +155 -0
  9. plugin_scanner-2.0.135/tests/test_guard_approval_store_dedup.py +219 -0
  10. plugin_scanner-2.0.135/tests/test_guard_decision_propagation.py +162 -0
  11. plugin_scanner-2.0.135/tests/test_guard_policy_dedup.py +301 -0
  12. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/.clusterfuzzlite/Dockerfile +0 -0
  13. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/.clusterfuzzlite/build.sh +0 -0
  14. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/.clusterfuzzlite/project.yaml +0 -0
  15. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/.clusterfuzzlite/requirements-atheris.txt +0 -0
  16. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/.dockerignore +0 -0
  17. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/.github/CODEOWNERS +0 -0
  18. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/.github/ISSUE_TEMPLATE/bug-report.yml +0 -0
  19. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  20. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
  21. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/.github/dependabot.yml +0 -0
  22. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/.github/workflows/ci.yml +0 -0
  23. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/.github/workflows/codeql.yml +0 -0
  24. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/.github/workflows/dependabot-uv-lock.yml +0 -0
  25. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/.github/workflows/fuzz.yml +0 -0
  26. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/.github/workflows/harness-smoke.yml +0 -0
  27. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/.github/workflows/publish.yml +0 -0
  28. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/.github/workflows/scorecard.yml +0 -0
  29. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/.gitignore +0 -0
  30. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/.pre-commit-hooks.yaml +0 -0
  31. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/CONTRIBUTING.md +0 -0
  32. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/Dockerfile +0 -0
  33. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/LICENSE +0 -0
  34. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/README.md +0 -0
  35. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/SECURITY.md +0 -0
  36. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/index.html +0 -0
  37. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/package.json +0 -0
  38. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/pnpm-lock.yaml +0 -0
  39. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/public/apple-touch-icon.png +0 -0
  40. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/public/brand/Logo_Icon_Dark.png +0 -0
  41. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/public/brand/Logo_Whole.png +0 -0
  42. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/public/favicon-16x16.png +0 -0
  43. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/public/favicon-32x32.png +0 -0
  44. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/public/favicon.ico +0 -0
  45. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/src/app.tsx +0 -0
  46. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/src/approval-center-layout.test.ts +0 -0
  47. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/src/approval-center-layout.tsx +0 -0
  48. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/src/approval-center-primitives.tsx +0 -0
  49. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/src/approval-center-review-cards.tsx +0 -0
  50. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/src/approval-center-utils.ts +0 -0
  51. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/src/data-flow-evidence-card.tsx +0 -0
  52. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/src/fleet-workspace.tsx +0 -0
  53. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/src/guard-api.test.ts +0 -0
  54. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/src/guard-api.ts +0 -0
  55. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/src/guard-demo.ts +0 -0
  56. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/src/guard-types.ts +0 -0
  57. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/src/main.tsx +0 -0
  58. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/src/receipts-workspace.test.ts +0 -0
  59. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/src/receipts-workspace.tsx +0 -0
  60. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/src/runtime-overview.test.ts +0 -0
  61. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/src/runtime-overview.tsx +0 -0
  62. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/src/settings-workspace.test.ts +0 -0
  63. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/src/settings-workspace.tsx +0 -0
  64. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/src/styles.css +0 -0
  65. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/src/vite-env.d.ts +0 -0
  66. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/tsconfig.json +0 -0
  67. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/dashboard/vite.config.ts +0 -0
  68. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/docker-requirements.txt +0 -0
  69. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/docs/guard/approval-audit.md +0 -0
  70. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/docs/guard/architecture.md +0 -0
  71. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/docs/guard/get-started.md +0 -0
  72. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/docs/guard/harness-support.md +0 -0
  73. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/docs/guard/local-vs-cloud.md +0 -0
  74. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/docs/guard/release-checklist.md +0 -0
  75. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/docs/guard/smoke-tests.md +0 -0
  76. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/docs/guard/testing-matrix.md +0 -0
  77. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/docs/trust/mcp-trust-draft.md +0 -0
  78. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/docs/trust/plugin-trust-draft.md +0 -0
  79. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/docs/trust/skill-trust-local.md +0 -0
  80. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/fuzzers/manifest_fuzzer.py +0 -0
  81. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/requirements.txt +0 -0
  82. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/schemas/plugin-quality.v1.json +0 -0
  83. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/schemas/scan-result.v1.json +0 -0
  84. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/schemas/verify-result.v1.json +0 -0
  85. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/__init__.py +0 -0
  86. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/action_runner.py +0 -0
  87. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/argparse_utils.py +0 -0
  88. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/checks/__init__.py +0 -0
  89. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/checks/best_practices.py +0 -0
  90. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/checks/claude.py +0 -0
  91. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/checks/code_quality.py +0 -0
  92. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/checks/ecosystem_common.py +0 -0
  93. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/checks/gemini.py +0 -0
  94. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/checks/manifest.py +0 -0
  95. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/checks/manifest_support.py +0 -0
  96. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/checks/marketplace.py +0 -0
  97. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/checks/mcp_security.py +0 -0
  98. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/checks/opencode.py +0 -0
  99. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/checks/operational_security.py +0 -0
  100. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/checks/security.py +0 -0
  101. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/checks/skill_security.py +0 -0
  102. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/cli.py +0 -0
  103. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/cli_ui.py +0 -0
  104. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/config.py +0 -0
  105. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/ecosystems/__init__.py +0 -0
  106. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/ecosystems/base.py +0 -0
  107. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/ecosystems/claude.py +0 -0
  108. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/ecosystems/codex.py +0 -0
  109. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/ecosystems/detect.py +0 -0
  110. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/ecosystems/gemini.py +0 -0
  111. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/ecosystems/opencode.py +0 -0
  112. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/ecosystems/registry.py +0 -0
  113. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/ecosystems/types.py +0 -0
  114. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/github_reporting.py +0 -0
  115. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/__init__.py +0 -0
  116. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/access_graph_events.py +0 -0
  117. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/adapters/__init__.py +0 -0
  118. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/adapters/antigravity.py +0 -0
  119. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/adapters/base.py +0 -0
  120. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/adapters/claude_code.py +0 -0
  121. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/adapters/cloud_identity.py +0 -0
  122. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/adapters/codex.py +0 -0
  123. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/adapters/contracts.py +0 -0
  124. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/adapters/copilot.py +0 -0
  125. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/adapters/cursor.py +0 -0
  126. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/adapters/gemini.py +0 -0
  127. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/adapters/hermes.py +0 -0
  128. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/adapters/mcp_servers.py +0 -0
  129. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/adapters/openclaw.py +0 -0
  130. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/adapters/openclaw_config.py +0 -0
  131. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/adapters/openclaw_support.py +0 -0
  132. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/adapters/opencode.py +0 -0
  133. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/adapters/opencode_artifacts.py +0 -0
  134. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/advisory_model.py +0 -0
  135. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/approvals.py +0 -0
  136. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/bridge/__init__.py +0 -0
  137. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/capabilities.py +0 -0
  138. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/cli/__init__.py +0 -0
  139. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/cli/approval_commands.py +0 -0
  140. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/cli/bootstrap.py +0 -0
  141. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/cli/commands.py +0 -0
  142. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/cli/connect_flow.py +0 -0
  143. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/cli/install_commands.py +0 -0
  144. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/cli/product.py +0 -0
  145. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/cli/prompt.py +0 -0
  146. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/cli/render.py +0 -0
  147. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/cli/update_commands.py +0 -0
  148. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/codex_config.py +0 -0
  149. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/config.py +0 -0
  150. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/consumer/__init__.py +0 -0
  151. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/consumer/service.py +0 -0
  152. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/daemon/__init__.py +0 -0
  153. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/daemon/client.py +0 -0
  154. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/daemon/manager.py +0 -0
  155. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/daemon/server.py +0 -0
  156. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/daemon/static/apple-touch-icon.png +0 -0
  157. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/daemon/static/assets/guard-dashboard.js +0 -0
  158. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/daemon/static/assets/index.css +0 -0
  159. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Icon_Dark.png +0 -0
  160. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Whole.png +0 -0
  161. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/daemon/static/favicon-16x16.png +0 -0
  162. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/daemon/static/favicon-32x32.png +0 -0
  163. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/daemon/static/favicon.ico +0 -0
  164. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/daemon/static/index.html +0 -0
  165. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/edge_events.py +0 -0
  166. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/incident.py +0 -0
  167. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/launcher.py +0 -0
  168. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/mcp_tool_calls.py +0 -0
  169. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/models.py +0 -0
  170. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/policy/__init__.py +0 -0
  171. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/policy/engine.py +0 -0
  172. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/protect.py +0 -0
  173. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/proxy/__init__.py +0 -0
  174. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/proxy/remote.py +0 -0
  175. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/proxy/runtime_mcp.py +0 -0
  176. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/proxy/stdio.py +0 -0
  177. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/receipts/__init__.py +0 -0
  178. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/receipts/manager.py +0 -0
  179. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/redaction.py +0 -0
  180. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/risk.py +0 -0
  181. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/runtime/__init__.py +0 -0
  182. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/runtime/actions.py +0 -0
  183. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/runtime/advisory_escalation.py +0 -0
  184. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/runtime/advisory_matchers.py +0 -0
  185. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/runtime/data_flow.py +0 -0
  186. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/runtime/data_flow_rules.py +0 -0
  187. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/runtime/data_flow_variables.py +0 -0
  188. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/runtime/decisions.py +0 -0
  189. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/runtime/detectors.py +0 -0
  190. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/runtime/mcp_protection.py +0 -0
  191. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/runtime/prompt_injection.py +0 -0
  192. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/runtime/runner.py +0 -0
  193. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/runtime/safe_decode.py +0 -0
  194. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/runtime/sandbox.py +0 -0
  195. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/runtime/secret_file_requests.py +0 -0
  196. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/runtime/secret_sensitivity.py +0 -0
  197. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/runtime/secret_sources.py +0 -0
  198. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/runtime/shell_commands.py +0 -0
  199. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/runtime/signals.py +0 -0
  200. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/runtime/skill_protection.py +0 -0
  201. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/runtime/supply_chain.py +0 -0
  202. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/runtime/surface_server.py +0 -0
  203. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/runtime/temp_files.py +0 -0
  204. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/runtime/threat_intel.py +0 -0
  205. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/schemas/__init__.py +0 -0
  206. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/schemas/consumer_mode.py +0 -0
  207. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/schemas/guard_event_v1.py +0 -0
  208. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/schemas/surface_server.py +0 -0
  209. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/shims.py +0 -0
  210. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/store_connect.py +0 -0
  211. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/store_evidence.py +0 -0
  212. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/store_threat_intel.py +0 -0
  213. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/guard/types.py +0 -0
  214. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/integrations/__init__.py +0 -0
  215. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/integrations/cisco_mcp_scanner.py +0 -0
  216. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/integrations/cisco_skill_scanner.py +0 -0
  217. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/lint_fixes.py +0 -0
  218. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/marketplace_support.py +0 -0
  219. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/models.py +0 -0
  220. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/path_support.py +0 -0
  221. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/policy.py +0 -0
  222. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/quality_artifact.py +0 -0
  223. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/repo_detect.py +0 -0
  224. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/reporting.py +0 -0
  225. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/rules/__init__.py +0 -0
  226. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/rules/registry.py +0 -0
  227. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/rules/specs.py +0 -0
  228. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/scanner.py +0 -0
  229. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/submission.py +0 -0
  230. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/suppressions.py +0 -0
  231. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/trust_domain_scoring.py +0 -0
  232. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/trust_helpers.py +0 -0
  233. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/trust_mcp_scoring.py +0 -0
  234. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/trust_models.py +0 -0
  235. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/trust_plugin_scoring.py +0 -0
  236. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/trust_scoring.py +0 -0
  237. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/trust_skill_scoring.py +0 -0
  238. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/trust_specs.py +0 -0
  239. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/src/codex_plugin_scanner/verification.py +0 -0
  240. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/__init__.py +0 -0
  241. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/conftest.py +0 -0
  242. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/__init__.py +0 -0
  243. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/bad-plugin/.codex-plugin/plugin.json +0 -0
  244. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/bad-plugin/.mcp.json +0 -0
  245. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/bad-plugin/secrets.js +0 -0
  246. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/claude-plugin-good/.claude-plugin/plugin.json +0 -0
  247. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/claude-plugin-good/LICENSE +0 -0
  248. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/claude-plugin-good/README.md +0 -0
  249. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/claude-plugin-good/SECURITY.md +0 -0
  250. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/claude-plugin-good/hooks/hooks.json +0 -0
  251. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/claude-plugin-good/skills/example/SKILL.md +0 -0
  252. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/code-quality-bad/evil.js +0 -0
  253. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/code-quality-bad/inject.js +0 -0
  254. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/gemini-extension-good/GEMINI.md +0 -0
  255. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/gemini-extension-good/LICENSE +0 -0
  256. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/gemini-extension-good/README.md +0 -0
  257. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/gemini-extension-good/SECURITY.md +0 -0
  258. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/gemini-extension-good/commands/hello.toml +0 -0
  259. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/gemini-extension-good/gemini-extension.json +0 -0
  260. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/good-plugin/.codex-plugin/plugin.json +0 -0
  261. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/good-plugin/.codexignore +0 -0
  262. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/good-plugin/LICENSE +0 -0
  263. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/good-plugin/README.md +0 -0
  264. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/good-plugin/SECURITY.md +0 -0
  265. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/good-plugin/assets/icon.svg +0 -0
  266. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/good-plugin/assets/logo.svg +0 -0
  267. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/good-plugin/assets/screenshot.svg +0 -0
  268. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/good-plugin/skills/example/SKILL.md +0 -0
  269. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/guard-codex-malicious-mcp/.codex/config.toml +0 -0
  270. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/guard-red-team/README.md +0 -0
  271. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/guard-red-team/benign-docs-fake-token.py +0 -0
  272. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/guard-red-team/benign-health-endpoint.py +0 -0
  273. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/guard-red-team/benign-nvmrc-fake-creds.py +0 -0
  274. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/guard-red-team/benign-source-search.py +0 -0
  275. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/guard-red-team/canary-exfil-encoded.py +0 -0
  276. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/guard-red-team/canary-exfil.py +0 -0
  277. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/guard-red-team/expected-decisions.json +0 -0
  278. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/guard-red-team/malicious-dockerfile.txt +0 -0
  279. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/guard-red-team/malicious-encoded-shell-exfil.py +0 -0
  280. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/guard-red-team/malicious-github-action.yml +0 -0
  281. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/guard-red-team/malicious-mcp-delete.md +0 -0
  282. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/guard-red-team/malicious-mcp-secret-read.md +0 -0
  283. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/guard-red-team/malicious-mcp-skill-exfil.md +0 -0
  284. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/guard-red-team/malicious-npm-postinstall.js +0 -0
  285. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/guard-red-team/malicious-prompt-env-read.md +0 -0
  286. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/guard-red-team/malicious-prompt-guard-bypass.md +0 -0
  287. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/guard-red-team/malicious-prompt-npmrc-read.md +0 -0
  288. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/guard-red-team/malicious-python-setup.py +0 -0
  289. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/guard-red-team/smoke-evidence-template.json +0 -0
  290. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/hermes-plugin-evil/config.yaml +0 -0
  291. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/hermes-plugin-evil/mcp_servers.json +0 -0
  292. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/hermes-plugin-evil/skills/security/malicious/SKILL.md +0 -0
  293. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/SKILL.md +0 -0
  294. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/references/api-setup.md +0 -0
  295. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/scripts/deploy.sh +0 -0
  296. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/hermes-plugin-evil/skills/utils/benign/SKILL.md +0 -0
  297. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/malformed-json/.codex-plugin/plugin.json +0 -0
  298. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/malicious-skill-plugin/.codex-plugin/plugin.json +0 -0
  299. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/malicious-skill-plugin/.codexignore +0 -0
  300. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/malicious-skill-plugin/LICENSE +0 -0
  301. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/malicious-skill-plugin/README.md +0 -0
  302. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/malicious-skill-plugin/SECURITY.md +0 -0
  303. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/malicious-skill-plugin/skills/leaky-skill/SKILL.md +0 -0
  304. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/mcp-canary-server.py +0 -0
  305. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/minimal-plugin/.codex-plugin/plugin.json +0 -0
  306. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/missing-fields/.codex-plugin/plugin.json +0 -0
  307. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/mit-license/LICENSE +0 -0
  308. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/multi-ecosystem-repo/codex-plugin/.codex-plugin/plugin.json +0 -0
  309. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/multi-ecosystem-repo/codex-plugin/LICENSE +0 -0
  310. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/multi-ecosystem-repo/codex-plugin/README.md +0 -0
  311. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/multi-ecosystem-repo/codex-plugin/SECURITY.md +0 -0
  312. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/multi-ecosystem-repo/gemini-ext/README.md +0 -0
  313. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/multi-ecosystem-repo/gemini-ext/gemini-extension.json +0 -0
  314. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/multi-plugin-repo/.agents/plugins/marketplace.json +0 -0
  315. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codex-plugin/plugin.json +0 -0
  316. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codexignore +0 -0
  317. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/LICENSE +0 -0
  318. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/README.md +0 -0
  319. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/SECURITY.md +0 -0
  320. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/skills/example/SKILL.md +0 -0
  321. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/.codex-plugin/plugin.json +0 -0
  322. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/skills/example/SKILL.md +0 -0
  323. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/no-version/.codex-plugin/plugin.json +0 -0
  324. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/opencode-good/.opencode/commands/hello.md +0 -0
  325. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/opencode-good/.opencode/plugins/example.ts +0 -0
  326. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/opencode-good/LICENSE +0 -0
  327. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/opencode-good/README.md +0 -0
  328. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/opencode-good/SECURITY.md +0 -0
  329. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/opencode-good/opencode.jsonc +0 -0
  330. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/skills-missing-dir/.codex-plugin/plugin.json +0 -0
  331. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/skills-no-frontmatter/.codex-plugin/plugin.json +0 -0
  332. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/skills-no-frontmatter/skills/bad-skill/SKILL.md +0 -0
  333. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/supply-chain/benign-npm-package.json +0 -0
  334. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/supply-chain/benign-pnpm-package.json +0 -0
  335. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/supply-chain/benign-pyproject.toml +0 -0
  336. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/supply-chain/malicious-Dockerfile +0 -0
  337. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/supply-chain/malicious-action.yml +0 -0
  338. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/supply-chain/malicious-npm-package.json +0 -0
  339. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/supply-chain/malicious-setup.py +0 -0
  340. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/with-marketplace/.codex-plugin/plugin.json +0 -0
  341. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/with-marketplace/marketplace-broken.json +0 -0
  342. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/fixtures/with-marketplace/marketplace.json +0 -0
  343. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test-trust-scoring.py +0 -0
  344. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test-trust-specs.py +0 -0
  345. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_action_runner.py +0 -0
  346. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_best_practices.py +0 -0
  347. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_cisco_install_surfaces.py +0 -0
  348. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_cli.py +0 -0
  349. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_code_quality.py +0 -0
  350. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_config.py +0 -0
  351. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_coverage_remaining.py +0 -0
  352. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_ecosystems.py +0 -0
  353. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_edge_cases.py +0 -0
  354. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_final_coverage.py +0 -0
  355. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_access_graph.py +0 -0
  356. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_advisory_escalation.py +0 -0
  357. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_approval_continuity.py +0 -0
  358. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_approval_store_scale.py +0 -0
  359. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_approvals.py +0 -0
  360. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_bootstrap.py +0 -0
  361. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_canary_fixtures.py +0 -0
  362. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_capabilities.py +0 -0
  363. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_claude_adapter.py +0 -0
  364. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_cli.py +0 -0
  365. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_cloud_local_sync.py +0 -0
  366. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_codex_e2e.py +0 -0
  367. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_codex_install.py +0 -0
  368. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_codex_proxy.py +0 -0
  369. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_config_paths.py +0 -0
  370. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_connect_flow.py +0 -0
  371. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_consumer_mode.py +0 -0
  372. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_copilot_adapter.py +0 -0
  373. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_copilot_proxy.py +0 -0
  374. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_daemon_manager.py +0 -0
  375. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_daemon_perf.py +0 -0
  376. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_data_flow.py +0 -0
  377. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_event_schema_v1.py +0 -0
  378. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_events.py +0 -0
  379. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_evidence_store.py +0 -0
  380. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_harness_contracts.py +0 -0
  381. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_launch_env.py +0 -0
  382. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_mcp_protection.py +0 -0
  383. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_opencode_proxy.py +0 -0
  384. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_product_flow.py +0 -0
  385. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_prompt_injection.py +0 -0
  386. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_protect.py +0 -0
  387. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_red_team.py +0 -0
  388. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_render.py +0 -0
  389. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_risk.py +0 -0
  390. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_runtime.py +0 -0
  391. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_runtime_action_harnesses.py +0 -0
  392. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_runtime_actions.py +0 -0
  393. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_runtime_decisions.py +0 -0
  394. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_runtime_detectors.py +0 -0
  395. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_runtime_signals.py +0 -0
  396. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_safe_decode.py +0 -0
  397. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_sandbox.py +0 -0
  398. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_skill_protection.py +0 -0
  399. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_store_migrations.py +0 -0
  400. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_supply_chain.py +0 -0
  401. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_surface_server.py +0 -0
  402. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_threat_intel.py +0 -0
  403. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_verdicts.py +0 -0
  404. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_guard_web_recovery.py +0 -0
  405. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_hermes_adapter.py +0 -0
  406. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_integration.py +0 -0
  407. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_lint_fixes.py +0 -0
  408. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_live_cisco_smoke.py +0 -0
  409. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_manifest.py +0 -0
  410. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_marketplace.py +0 -0
  411. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_mcp_security.py +0 -0
  412. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_openclaw_adapter.py +0 -0
  413. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_operational_security.py +0 -0
  414. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_policy.py +0 -0
  415. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_quality_artifact.py +0 -0
  416. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_rule_registry.py +0 -0
  417. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_scanner.py +0 -0
  418. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_schema_contracts.py +0 -0
  419. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_security.py +0 -0
  420. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_security_ops.py +0 -0
  421. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_skill_security.py +0 -0
  422. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_submission.py +0 -0
  423. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_trust_scoring.py +0 -0
  424. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_trust_specs.py +0 -0
  425. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_verification.py +0 -0
  426. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/tests/test_versioning.py +0 -0
  427. {plugin_scanner-2.0.133 → plugin_scanner-2.0.135}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plugin-scanner
3
- Version: 2.0.133
3
+ Version: 2.0.135
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.133"
7
+ version = "2.0.135"
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.133"
7
+ version = "2.0.135"
8
8
  description = "Protect local AI harnesses with HOL Guard and run scanner checks for Codex, Claude, Cursor, Gemini, and OpenCode."
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -0,0 +1,87 @@
1
+ """Stable action identity normalization for Guard policy deduplication.
2
+
3
+ Provides normalizers for command, prompt, and MCP tool call identities.
4
+ The output of each normalizer is a stable string suitable for comparison
5
+ or hashing across repeated calls with transient variation stripped out.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import hashlib
11
+ import json
12
+ import re
13
+
14
+ _ANSI_ESCAPE = re.compile(r"\x1b\[[0-9;]*[mABCDEFGHJKSTfhinsu]")
15
+
16
+ _REQUEST_ID_PATTERN = re.compile(
17
+ r"\b(?:req|request|approval|id)[-_][a-zA-Z0-9_-]{4,64}\b",
18
+ re.IGNORECASE,
19
+ )
20
+
21
+ _TIMESTAMP_PATTERN = re.compile(r"\b\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2}))?\b")
22
+
23
+ _PORT_FLAG_PATTERN = re.compile(
24
+ r"(?:--port|-p)\s+\d{2,5}\b",
25
+ re.IGNORECASE,
26
+ )
27
+
28
+ _MARKDOWN_BOLD_ITALIC = re.compile(r"\*{1,3}|(?<!\w)_{1,3}(?=\w)|(?<=\w)_{1,3}(?!\w)")
29
+
30
+ _BACKTICK_INLINE_CODE = re.compile(r"`([^`]+)`")
31
+
32
+ _GENERIC_REQUEST_ID_IN_ARGS = re.compile(r"\b[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\b")
33
+
34
+
35
+ def normalize_command_identity(command: str) -> str:
36
+ """Return a stable identity string for a shell command.
37
+
38
+ Strips ANSI codes, daemon ports, timestamps, approval request IDs,
39
+ UUIDs, and excess whitespace while preserving the command name,
40
+ meaningful arguments, target paths, and network hosts.
41
+ """
42
+ normalized = _ANSI_ESCAPE.sub("", command)
43
+ normalized = _REQUEST_ID_PATTERN.sub("<request-id>", normalized)
44
+ normalized = _GENERIC_REQUEST_ID_IN_ARGS.sub("<uuid>", normalized)
45
+ normalized = _TIMESTAMP_PATTERN.sub("<timestamp>", normalized)
46
+ normalized = _PORT_FLAG_PATTERN.sub("<port-flag>", normalized)
47
+ normalized = re.sub(r"\s+", " ", normalized).strip()
48
+ return normalized
49
+
50
+
51
+ def normalize_prompt_identity(prompt: str) -> str:
52
+ """Return a stable identity string for a prompt text.
53
+
54
+ Strips markdown bold/italic formatting and normalises whitespace,
55
+ while preserving the requested sensitive targets (file paths, keys,
56
+ tokens, named secrets).
57
+ """
58
+ normalized = _MARKDOWN_BOLD_ITALIC.sub("", prompt)
59
+ normalized = _BACKTICK_INLINE_CODE.sub(r"\1", normalized)
60
+ normalized = re.sub(r"\s+", " ", normalized).strip()
61
+ normalized = normalized.lower()
62
+ return normalized
63
+
64
+
65
+ def normalize_mcp_identity(call: dict[str, object]) -> str:
66
+ """Return a stable identity hash for an MCP tool call.
67
+
68
+ The identity is derived from:
69
+ - server_id
70
+ - tool_name
71
+ - arguments (sorted keys, stable JSON)
72
+ - schema_hash (when present)
73
+
74
+ Returns a hex digest suitable for equality comparison.
75
+ """
76
+ server_id = str(call.get("server_id", ""))
77
+ tool_name = str(call.get("tool_name", ""))
78
+ arguments = call.get("arguments", {})
79
+ schema_hash = str(call.get("schema_hash", ""))
80
+
81
+ stable_args = json.dumps(
82
+ {k: v for k, v in sorted(arguments.items())} if isinstance(arguments, dict) else arguments,
83
+ sort_keys=True,
84
+ ensure_ascii=True,
85
+ )
86
+ identity_source = f"{server_id}:{tool_name}:{stable_args}:{schema_hash}"
87
+ return hashlib.sha256(identity_source.encode("utf-8")).hexdigest()
@@ -686,6 +686,7 @@ class GuardStore:
686
686
  self._ensure_approval_column(connection, "action_envelope_json", "text")
687
687
  self._ensure_approval_column(connection, "decision_v2_json", "text")
688
688
  self._ensure_approval_column(connection, "workspace", "text")
689
+ self._ensure_approval_column(connection, "normalized_identity_key", "text")
689
690
  self._ensure_approval_column(connection, "fallback_cli_command", "text")
690
691
  self._ensure_attachment_column(connection, "lease_id", "text not null default ''")
691
692
  self._ensure_attachment_column(connection, "lease_expires_at", "text")
@@ -6,6 +6,11 @@ import json
6
6
  import sqlite3
7
7
 
8
8
  from .models import GuardApprovalRequest
9
+ from .runtime.action_identity import normalize_command_identity
10
+
11
+
12
+ def _normalized_identity_key(launch_target: str | None) -> str:
13
+ return normalize_command_identity(launch_target or "")
9
14
 
10
15
 
11
16
  def approval_schema_statement() -> str:
@@ -25,6 +30,7 @@ def approval_schema_statement() -> str:
25
30
  config_path text not null,
26
31
  workspace text,
27
32
  launch_target text,
33
+ normalized_identity_key text,
28
34
  transport text,
29
35
  risk_summary text,
30
36
  risk_signals_json text not null default '[]',
@@ -50,16 +56,37 @@ def approval_schema_statement() -> str:
50
56
 
51
57
 
52
58
  def add_approval_request(connection: sqlite3.Connection, request: GuardApprovalRequest, now: str) -> str:
59
+ identity_key = _normalized_identity_key(request.launch_target)
53
60
  existing = connection.execute(
54
61
  """
55
62
  select request_id
56
63
  from approval_requests
57
- where harness = ? and artifact_id = ? and status = 'pending'
64
+ where harness = ?
65
+ and artifact_id = ?
66
+ and workspace IS ?
67
+ and normalized_identity_key = ?
68
+ and status = 'pending'
58
69
  order by created_at desc
59
70
  limit 1
60
71
  """,
61
- (request.harness, request.artifact_id),
72
+ (request.harness, request.artifact_id, request.workspace, identity_key),
62
73
  ).fetchone()
74
+ if existing is None:
75
+ existing = connection.execute(
76
+ """
77
+ select request_id
78
+ from approval_requests
79
+ where harness = ?
80
+ and artifact_id = ?
81
+ and workspace IS ?
82
+ and launch_target IS ?
83
+ and normalized_identity_key IS NULL
84
+ and status = 'pending'
85
+ order by created_at desc
86
+ limit 1
87
+ """,
88
+ (request.harness, request.artifact_id, request.workspace, request.launch_target),
89
+ ).fetchone()
63
90
  request_id = str(existing["request_id"]) if existing is not None else request.request_id
64
91
  if existing is not None:
65
92
  review_command = _rewrite_review_command(request.review_command, request_id)
@@ -69,7 +96,7 @@ def add_approval_request(connection: sqlite3.Connection, request: GuardApprovalR
69
96
  update approval_requests
70
97
  set artifact_name = ?, artifact_type = ?, artifact_hash = ?, publisher = ?, policy_action = ?,
71
98
  recommended_scope = ?, changed_fields_json = ?, source_scope = ?, config_path = ?, workspace = ?,
72
- launch_target = ?, transport = ?, risk_summary = ?, risk_signals_json = ?,
99
+ launch_target = ?, normalized_identity_key = ?, transport = ?, risk_summary = ?, risk_signals_json = ?,
73
100
  artifact_label = ?, source_label = ?, trigger_summary = ?, why_now = ?, launch_summary = ?,
74
101
  risk_headline = ?, action_envelope_json = ?, decision_v2_json = ?, fallback_cli_command = ?,
75
102
  review_command = ?, approval_url = ?, created_at = ?
@@ -87,6 +114,7 @@ def add_approval_request(connection: sqlite3.Connection, request: GuardApprovalR
87
114
  request.config_path,
88
115
  request.workspace,
89
116
  request.launch_target,
117
+ _normalized_identity_key(request.launch_target),
90
118
  request.transport,
91
119
  request.risk_summary,
92
120
  json.dumps(list(request.risk_signals)),
@@ -115,12 +143,14 @@ def add_approval_request(connection: sqlite3.Connection, request: GuardApprovalR
115
143
  insert into approval_requests (
116
144
  request_id, harness, artifact_id, artifact_name, artifact_type, artifact_hash, publisher, policy_action,
117
145
  recommended_scope, changed_fields_json, source_scope, config_path, workspace,
118
- launch_target, transport, risk_summary,
146
+ launch_target, normalized_identity_key, transport, risk_summary,
119
147
  risk_signals_json, artifact_label, source_label, trigger_summary, why_now, launch_summary, risk_headline,
120
148
  action_envelope_json, decision_v2_json, fallback_cli_command, review_command,
121
149
  approval_url, status, resolution_action, resolution_scope, reason, created_at, resolved_at
122
150
  )
123
- values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
151
+ values (
152
+ ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
153
+ )
124
154
  """,
125
155
  (
126
156
  request.request_id,
@@ -137,6 +167,7 @@ def add_approval_request(connection: sqlite3.Connection, request: GuardApprovalR
137
167
  request.config_path,
138
168
  request.workspace,
139
169
  request.launch_target,
170
+ _normalized_identity_key(request.launch_target),
140
171
  request.transport,
141
172
  request.risk_summary,
142
173
  json.dumps(list(request.risk_signals)),
@@ -1,3 +1,3 @@
1
1
  """Single source of truth for tool version."""
2
2
 
3
- __version__ = "2.0.133"
3
+ __version__ = "2.0.135"
@@ -0,0 +1,155 @@
1
+ """Tests for action identity normalization (T700-T718)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import pytest
6
+
7
+ action_identity_mod = pytest.importorskip(
8
+ "codex_plugin_scanner.guard.runtime.action_identity",
9
+ reason="action_identity module not yet implemented",
10
+ )
11
+
12
+ normalize_command_identity = action_identity_mod.normalize_command_identity
13
+ normalize_prompt_identity = action_identity_mod.normalize_prompt_identity
14
+ normalize_mcp_identity = action_identity_mod.normalize_mcp_identity
15
+
16
+
17
+ class TestCommandIdentityNormalization:
18
+ """T700-T705: Command identity normalizer."""
19
+
20
+ def test_same_command_with_different_request_ids_maps_to_same_identity(self) -> None:
21
+ """T703: Different approval request IDs in command must not change identity."""
22
+ cmd_a = "hol-guard approvals approve req-abc-001 --scope artifact"
23
+ cmd_b = "hol-guard approvals approve req-xyz-999 --scope artifact"
24
+ assert normalize_command_identity(cmd_a) == normalize_command_identity(cmd_b)
25
+
26
+ def test_ansi_codes_removed_before_normalization(self) -> None:
27
+ """T701: ANSI escape codes must be stripped before normalization."""
28
+ cmd_with_ansi = "\x1b[32mnode\x1b[0m server.js"
29
+ cmd_clean = "node server.js"
30
+ assert normalize_command_identity(cmd_with_ansi) == normalize_command_identity(cmd_clean)
31
+
32
+ def test_daemon_port_numbers_removed(self) -> None:
33
+ """T701: Ephemeral port numbers (like approval center ports) must not affect identity."""
34
+ cmd_a = "hol-guard approvals --port 6174"
35
+ cmd_b = "hol-guard approvals --port 7890"
36
+ assert normalize_command_identity(cmd_a) == normalize_command_identity(cmd_b)
37
+
38
+ def test_timestamps_removed(self) -> None:
39
+ """T701: Timestamps in commands must not affect identity."""
40
+ cmd_a = "backup.sh --at 2026-01-01T00:00:00Z"
41
+ cmd_b = "backup.sh --at 2026-06-15T12:30:00Z"
42
+ assert normalize_command_identity(cmd_a) == normalize_command_identity(cmd_b)
43
+
44
+ def test_different_network_hosts_produce_different_identity(self) -> None:
45
+ """T704: Commands targeting different network hosts must have different identity."""
46
+ cmd_internal = "curl http://internal.corp/api/data"
47
+ cmd_external = "curl http://evil.example.com/api/data"
48
+ assert normalize_command_identity(cmd_internal) != normalize_command_identity(cmd_external)
49
+
50
+ def test_different_secret_paths_produce_different_identity(self) -> None:
51
+ """T705: Commands targeting different secret paths must have different identity."""
52
+ cmd_npmrc = "cat /Users/me/.npmrc"
53
+ cmd_env = "cat /Users/me/.env"
54
+ assert normalize_command_identity(cmd_npmrc) != normalize_command_identity(cmd_env)
55
+
56
+ def test_meaningful_command_preserved(self) -> None:
57
+ """T702: Core command and meaningful args must be preserved in normalized identity."""
58
+ id_a = normalize_command_identity("node server.js --port 3000")
59
+ id_b = normalize_command_identity("python server.py --port 3000")
60
+ assert id_a != id_b, "Different commands must produce different identities"
61
+
62
+ def test_whitespace_normalized(self) -> None:
63
+ """T701: Extra whitespace must not affect identity."""
64
+ cmd_a = "node server.js --port 3000"
65
+ cmd_b = "node server.js --port 3000"
66
+ assert normalize_command_identity(cmd_a) == normalize_command_identity(cmd_b)
67
+
68
+
69
+ class TestPromptIdentityNormalization:
70
+ """T706-T708: Prompt identity normalizer."""
71
+
72
+ def test_repeated_read_npmrc_prompt_maps_to_same_identity(self) -> None:
73
+ """T707: Same prompt repeated with minor formatting differences maps to same identity."""
74
+ prompt_a = "Read the **`.npmrc`** file and tell me the registry config."
75
+ prompt_b = "Read the `.npmrc` file and tell me the registry config."
76
+ assert normalize_prompt_identity(prompt_a) == normalize_prompt_identity(prompt_b)
77
+
78
+ def test_npmrc_vs_env_prompt_maps_to_different_identity(self) -> None:
79
+ """T708: Prompts targeting different secrets must have different identity."""
80
+ prompt_npmrc = "Read the .npmrc file."
81
+ prompt_env = "Read the .env file."
82
+ assert normalize_prompt_identity(prompt_npmrc) != normalize_prompt_identity(prompt_env)
83
+
84
+ def test_model_formatting_tokens_removed(self) -> None:
85
+ """T706: Transient model formatting (bold, markdown etc.) must not affect identity."""
86
+ prompt_formatted = "**Read** the `.npmrc` file and extract the _auth token_."
87
+ prompt_plain = "Read the .npmrc file and extract the auth token."
88
+ assert normalize_prompt_identity(prompt_formatted) == normalize_prompt_identity(prompt_plain)
89
+
90
+ def test_underscores_in_identifiers_preserved(self) -> None:
91
+ """T706b: Underscores inside identifiers must not be stripped."""
92
+ prompt_key = "Send OPENAI_API_KEY to the server."
93
+ prompt_file = "Read my_secret_file from disk."
94
+ normalized_key = normalize_prompt_identity(prompt_key)
95
+ normalized_file = normalize_prompt_identity(prompt_file)
96
+ assert "openai_api_key" in normalized_key, "Internal underscores in env var names must be preserved"
97
+ assert "my_secret_file" in normalized_file, "Internal underscores in file names must be preserved"
98
+
99
+
100
+ class TestMcpIdentityNormalization:
101
+ """T709-T711: MCP identity normalizer."""
102
+
103
+ def test_same_tool_and_target_maps_to_same_identity(self) -> None:
104
+ """T710: Same MCP server, tool, and target must produce same identity."""
105
+ call_a = {
106
+ "server_id": "github-mcp",
107
+ "tool_name": "read_file",
108
+ "arguments": {"path": "/Users/me/.npmrc"},
109
+ }
110
+ call_b = {
111
+ "server_id": "github-mcp",
112
+ "tool_name": "read_file",
113
+ "arguments": {"path": "/Users/me/.npmrc"},
114
+ }
115
+ assert normalize_mcp_identity(call_a) == normalize_mcp_identity(call_b)
116
+
117
+ def test_different_mcp_target_produces_different_identity(self) -> None:
118
+ """T710: Same MCP tool with different target must produce different identity."""
119
+ call_npmrc = {
120
+ "server_id": "github-mcp",
121
+ "tool_name": "read_file",
122
+ "arguments": {"path": "/Users/me/.npmrc"},
123
+ }
124
+ call_env = {
125
+ "server_id": "github-mcp",
126
+ "tool_name": "read_file",
127
+ "arguments": {"path": "/Users/me/.env"},
128
+ }
129
+ assert normalize_mcp_identity(call_npmrc) != normalize_mcp_identity(call_env)
130
+
131
+ def test_mcp_schema_change_produces_different_identity(self) -> None:
132
+ """T711: Change in MCP tool schema hash must invalidate previous approval identity."""
133
+ call_v1 = {
134
+ "server_id": "custom-mcp",
135
+ "tool_name": "execute",
136
+ "arguments": {"cmd": "ls"},
137
+ "schema_hash": "v1-abc123",
138
+ }
139
+ call_v2 = {
140
+ "server_id": "custom-mcp",
141
+ "tool_name": "execute",
142
+ "arguments": {"cmd": "ls"},
143
+ "schema_hash": "v2-def456",
144
+ }
145
+ assert normalize_mcp_identity(call_v1) != normalize_mcp_identity(call_v2)
146
+
147
+ def test_mcp_identity_is_deterministic(self) -> None:
148
+ """T709: normalize_mcp_identity must return same hash on repeated calls."""
149
+ call = {
150
+ "server_id": "test-mcp",
151
+ "tool_name": "read_file",
152
+ "arguments": {"path": "/tmp/test"},
153
+ "schema_hash": "hash-001",
154
+ }
155
+ assert normalize_mcp_identity(call) == normalize_mcp_identity(call)
@@ -0,0 +1,219 @@
1
+ """Phase 25 — approval store dedup by normalized action identity and workspace.
2
+
3
+ T719: store collapses duplicate pending requests by normalized action identity + workspace.
4
+ T720: duplicate pending requests update one row instead of creating many rows.
5
+ T721: different workspaces still get separate approval requests.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import sqlite3
11
+ import uuid
12
+
13
+ from codex_plugin_scanner.guard.models import GuardApprovalRequest
14
+ from codex_plugin_scanner.guard.store_approvals import (
15
+ add_approval_request,
16
+ approval_index_statements,
17
+ approval_schema_statement,
18
+ count_approval_requests,
19
+ list_approval_requests,
20
+ )
21
+
22
+
23
+ def _make_conn() -> sqlite3.Connection:
24
+ conn = sqlite3.connect(":memory:")
25
+ conn.row_factory = sqlite3.Row
26
+ conn.execute("pragma journal_mode=wal")
27
+ conn.execute(approval_schema_statement())
28
+ for stmt in approval_index_statements():
29
+ conn.execute(stmt)
30
+ return conn
31
+
32
+
33
+ def _make_request(
34
+ *,
35
+ harness: str = "codex",
36
+ workspace: str | None = "ws-a",
37
+ artifact_id: str | None = None,
38
+ launch_target: str | None = None,
39
+ ) -> GuardApprovalRequest:
40
+ aid = artifact_id or f"codex:project:tool-{uuid.uuid4().hex[:8]}"
41
+ rid = str(uuid.uuid4())
42
+ return GuardApprovalRequest(
43
+ request_id=rid,
44
+ harness=harness,
45
+ artifact_id=aid,
46
+ artifact_name="tool",
47
+ artifact_type="mcp_server",
48
+ artifact_hash="abc123",
49
+ publisher=None,
50
+ policy_action="require-reapproval",
51
+ recommended_scope="session",
52
+ changed_fields=frozenset(["args"]),
53
+ source_scope="project",
54
+ config_path="/repo/config.toml",
55
+ workspace=workspace,
56
+ launch_target=launch_target,
57
+ transport="stdio",
58
+ risk_summary="risk",
59
+ risk_signals=[],
60
+ artifact_label=None,
61
+ source_label=None,
62
+ trigger_summary=None,
63
+ why_now=None,
64
+ launch_summary=None,
65
+ risk_headline=None,
66
+ action_envelope_json=None,
67
+ decision_v2_json=None,
68
+ fallback_cli_command=None,
69
+ review_command=f"hol-guard review {rid}",
70
+ approval_url=f"http://localhost:4455/approve/{rid}",
71
+ )
72
+
73
+
74
+ class TestDuplicatePendingRequestCollapse:
75
+ """T719-T720: Duplicate pending requests collapse to one row."""
76
+
77
+ def test_second_identical_request_updates_existing_row(self) -> None:
78
+ """T720: A second pending request for the same artifact+workspace+launch_target
79
+ must update the existing row and not create a new one."""
80
+ conn = _make_conn()
81
+ artifact_id = "codex:project:tool-abc"
82
+ req1 = _make_request(artifact_id=artifact_id, workspace="ws-a", launch_target="run tool --flag")
83
+ req2 = _make_request(artifact_id=artifact_id, workspace="ws-a", launch_target="run tool --flag")
84
+
85
+ id1 = add_approval_request(conn, req1, "2026-01-01T00:00:00Z")
86
+ id2 = add_approval_request(conn, req2, "2026-01-01T00:01:00Z")
87
+
88
+ assert id1 == id2, "Second identical request must reuse the existing request_id"
89
+ total = count_approval_requests(conn, status="pending")
90
+ assert total == 1, f"Expected 1 pending row, got {total}"
91
+
92
+ def test_transient_variation_collapses_to_same_row(self) -> None:
93
+ """T719: Transient variation (UUID in args) must not create a new row."""
94
+ conn = _make_conn()
95
+ artifact_id = "codex:project:tool-xyz"
96
+ req1 = _make_request(
97
+ artifact_id=artifact_id,
98
+ workspace="ws-b",
99
+ launch_target="run tool --request-id req-aaaaaaaaaaaa --flag",
100
+ )
101
+ req2 = _make_request(
102
+ artifact_id=artifact_id,
103
+ workspace="ws-b",
104
+ launch_target="run tool --request-id req-bbbbbbbbbbbb --flag",
105
+ )
106
+
107
+ id1 = add_approval_request(conn, req1, "2026-01-01T00:00:00Z")
108
+ id2 = add_approval_request(conn, req2, "2026-01-01T00:01:00Z")
109
+
110
+ assert id1 == id2, "Same command with different transient request IDs must collapse to one row"
111
+ total = count_approval_requests(conn, status="pending")
112
+ assert total == 1, f"Expected 1 pending row after transient variation, got {total}"
113
+
114
+
115
+ class TestDifferentWorkspacesGetSeparateRows:
116
+ """T721: Different workspaces must produce separate approval request rows."""
117
+
118
+ def test_same_artifact_different_workspaces_creates_two_rows(self) -> None:
119
+ """T721: Same artifact in different workspaces must each queue its own approval."""
120
+ conn = _make_conn()
121
+ artifact_id = "codex:project:tool-shared"
122
+ req_ws_a = _make_request(artifact_id=artifact_id, workspace="ws-a", launch_target="run tool")
123
+ req_ws_b = _make_request(artifact_id=artifact_id, workspace="ws-b", launch_target="run tool")
124
+
125
+ id_a = add_approval_request(conn, req_ws_a, "2026-01-01T00:00:00Z")
126
+ id_b = add_approval_request(conn, req_ws_b, "2026-01-01T00:01:00Z")
127
+
128
+ assert id_a != id_b, "Different workspaces must get separate request IDs"
129
+ total = count_approval_requests(conn, status="pending")
130
+ assert total == 2, f"Expected 2 pending rows for 2 workspaces, got {total}"
131
+
132
+ def test_null_and_named_workspace_get_separate_rows(self) -> None:
133
+ """T721b: Null workspace and a named workspace must be treated as different."""
134
+ conn = _make_conn()
135
+ artifact_id = "codex:project:tool-null-ws"
136
+ req_null = _make_request(artifact_id=artifact_id, workspace=None, launch_target="run tool")
137
+ req_named = _make_request(artifact_id=artifact_id, workspace="ws-c", launch_target="run tool")
138
+
139
+ id_null = add_approval_request(conn, req_null, "2026-01-01T00:00:00Z")
140
+ id_named = add_approval_request(conn, req_named, "2026-01-01T00:01:00Z")
141
+
142
+ assert id_null != id_named, "Null workspace and named workspace must get separate request IDs"
143
+ pending = list_approval_requests(conn, status="pending")
144
+ assert len(pending) == 2, f"Expected 2 pending rows, got {len(pending)}"
145
+
146
+ def test_different_launch_targets_create_separate_rows(self) -> None:
147
+ """T719b: Different commands for same artifact+workspace must queue separate approvals."""
148
+ conn = _make_conn()
149
+ artifact_id = "codex:project:tool-multi"
150
+ req_ls = _make_request(artifact_id=artifact_id, workspace="ws-a", launch_target="run ls /repo")
151
+ req_rm = _make_request(artifact_id=artifact_id, workspace="ws-a", launch_target="run rm /tmp/file")
152
+
153
+ id_ls = add_approval_request(conn, req_ls, "2026-01-01T00:00:00Z")
154
+ id_rm = add_approval_request(conn, req_rm, "2026-01-01T00:01:00Z")
155
+
156
+ assert id_ls != id_rm, "Different commands must not collapse into one approval row"
157
+ total = count_approval_requests(conn, status="pending")
158
+ assert total == 2, f"Expected 2 pending rows for different commands, got {total}"
159
+
160
+
161
+ class TestLegacyNullIdentityKeyUpgradePath:
162
+ """Regression: existing rows with NULL normalized_identity_key must still be deduped."""
163
+
164
+ def test_legacy_null_row_is_updated_not_duplicated(self) -> None:
165
+ """After upgrade, a pending row with NULL identity key must be reused for the same
166
+ artifact+workspace instead of inserting a duplicate row."""
167
+ conn = _make_conn()
168
+ artifact_id = "codex:project:tool-legacy"
169
+ req_legacy = _make_request(artifact_id=artifact_id, workspace="ws-a", launch_target="run tool")
170
+ first_id = add_approval_request(conn, req_legacy, "2026-01-01T00:00:00Z")
171
+
172
+ conn.execute(
173
+ "update approval_requests set normalized_identity_key = NULL where request_id = ?",
174
+ (first_id,),
175
+ )
176
+
177
+ req_new = _make_request(artifact_id=artifact_id, workspace="ws-a", launch_target="run tool")
178
+ second_id = add_approval_request(conn, req_new, "2026-01-01T00:01:00Z")
179
+
180
+ assert first_id == second_id, "Legacy row with NULL identity key must be reused, not duplicated"
181
+ total = count_approval_requests(conn, status="pending")
182
+ assert total == 1, f"Expected 1 pending row after deduping legacy null row, got {total}"
183
+
184
+ def test_legacy_null_row_different_command_not_collapsed(self) -> None:
185
+ """Legacy NULL rows for different commands must NOT be collapsed even during upgrade path."""
186
+ conn = _make_conn()
187
+ artifact_id = "codex:project:tool-multi"
188
+ req_a = _make_request(artifact_id=artifact_id, workspace="ws-a", launch_target="run cmd-a")
189
+ id_a = add_approval_request(conn, req_a, "2026-01-01T00:00:00Z")
190
+ conn.execute(
191
+ "update approval_requests set normalized_identity_key = NULL where request_id = ?",
192
+ (id_a,),
193
+ )
194
+
195
+ req_b = _make_request(artifact_id=artifact_id, workspace="ws-a", launch_target="run cmd-b")
196
+ id_b = add_approval_request(conn, req_b, "2026-01-01T00:01:00Z")
197
+
198
+ assert id_a != id_b, "Different commands must each get their own pending row even when legacy row is NULL"
199
+ total = count_approval_requests(conn, status="pending")
200
+ assert total == 2, f"Expected 2 pending rows for different commands, got {total}"
201
+
202
+ def test_legacy_null_launch_target_row_is_deduped(self) -> None:
203
+ """Legacy rows with NULL launch_target AND NULL identity key must be deduped for the same artifact."""
204
+ conn = _make_conn()
205
+ artifact_id = "codex:project:tool-null-target"
206
+ req_null = _make_request(artifact_id=artifact_id, workspace="ws-a", launch_target=None)
207
+ first_id = add_approval_request(conn, req_null, "2026-01-01T00:00:00Z")
208
+
209
+ conn.execute(
210
+ "update approval_requests set normalized_identity_key = NULL where request_id = ?",
211
+ (first_id,),
212
+ )
213
+
214
+ req_retry = _make_request(artifact_id=artifact_id, workspace="ws-a", launch_target=None)
215
+ second_id = add_approval_request(conn, req_retry, "2026-01-01T00:01:00Z")
216
+
217
+ assert first_id == second_id, "NULL-launch-target legacy row must be reused for the same artifact"
218
+ total = count_approval_requests(conn, status="pending")
219
+ assert total == 1, f"Expected 1 pending row for NULL launch_target dedup, got {total}"