plugin-scanner 2.0.106__tar.gz → 2.0.108__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 (351) hide show
  1. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/PKG-INFO +1 -1
  2. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/pyproject.toml +1 -1
  3. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/pyproject.toml.bak +1 -1
  4. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/adapters/mcp_servers.py +85 -0
  5. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/capabilities.py +19 -0
  6. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/cli/commands.py +64 -12
  7. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/mcp_tool_calls.py +93 -15
  8. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/proxy/runtime_mcp.py +11 -0
  9. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/runtime/secret_sensitivity.py +124 -2
  10. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/version.py +1 -1
  11. plugin_scanner-2.0.108/tests/conftest.py +15 -0
  12. plugin_scanner-2.0.108/tests/test_guard_access_graph.py +395 -0
  13. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_guard_cli.py +37 -0
  14. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_guard_risk.py +56 -1
  15. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_guard_runtime.py +171 -3
  16. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_guard_runtime_detectors.py +3 -0
  17. plugin_scanner-2.0.106/tests/conftest.py +0 -9
  18. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/.clusterfuzzlite/Dockerfile +0 -0
  19. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/.clusterfuzzlite/build.sh +0 -0
  20. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/.clusterfuzzlite/project.yaml +0 -0
  21. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/.clusterfuzzlite/requirements-atheris.txt +0 -0
  22. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/.dockerignore +0 -0
  23. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/.github/CODEOWNERS +0 -0
  24. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/.github/ISSUE_TEMPLATE/bug-report.yml +0 -0
  25. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  26. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
  27. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/.github/dependabot.yml +0 -0
  28. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/.github/workflows/ci.yml +0 -0
  29. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/.github/workflows/codeql.yml +0 -0
  30. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/.github/workflows/dependabot-uv-lock.yml +0 -0
  31. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/.github/workflows/fuzz.yml +0 -0
  32. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/.github/workflows/harness-smoke.yml +0 -0
  33. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/.github/workflows/publish.yml +0 -0
  34. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/.github/workflows/scorecard.yml +0 -0
  35. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/.gitignore +0 -0
  36. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/.pre-commit-hooks.yaml +0 -0
  37. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/CONTRIBUTING.md +0 -0
  38. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/Dockerfile +0 -0
  39. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/LICENSE +0 -0
  40. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/README.md +0 -0
  41. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/SECURITY.md +0 -0
  42. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/dashboard/index.html +0 -0
  43. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/dashboard/package.json +0 -0
  44. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/dashboard/pnpm-lock.yaml +0 -0
  45. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/dashboard/public/apple-touch-icon.png +0 -0
  46. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/dashboard/public/brand/Logo_Icon_Dark.png +0 -0
  47. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/dashboard/public/brand/Logo_Whole.png +0 -0
  48. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/dashboard/public/favicon-16x16.png +0 -0
  49. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/dashboard/public/favicon-32x32.png +0 -0
  50. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/dashboard/public/favicon.ico +0 -0
  51. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/dashboard/src/app.tsx +0 -0
  52. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/dashboard/src/approval-center-layout.tsx +0 -0
  53. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/dashboard/src/approval-center-primitives.tsx +0 -0
  54. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/dashboard/src/approval-center-utils.ts +0 -0
  55. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/dashboard/src/fleet-workspace.tsx +0 -0
  56. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/dashboard/src/guard-api.test.ts +0 -0
  57. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/dashboard/src/guard-api.ts +0 -0
  58. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/dashboard/src/guard-demo.ts +0 -0
  59. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/dashboard/src/guard-types.ts +0 -0
  60. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/dashboard/src/main.tsx +0 -0
  61. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/dashboard/src/receipts-workspace.tsx +0 -0
  62. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/dashboard/src/runtime-overview.tsx +0 -0
  63. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/dashboard/src/settings-workspace.tsx +0 -0
  64. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/dashboard/src/styles.css +0 -0
  65. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/dashboard/src/vite-env.d.ts +0 -0
  66. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/dashboard/tsconfig.json +0 -0
  67. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/dashboard/vite.config.ts +0 -0
  68. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/docker-requirements.txt +0 -0
  69. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/docs/guard/approval-audit.md +0 -0
  70. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/docs/guard/architecture.md +0 -0
  71. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/docs/guard/get-started.md +0 -0
  72. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/docs/guard/harness-support.md +0 -0
  73. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/docs/guard/local-vs-cloud.md +0 -0
  74. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/docs/guard/testing-matrix.md +0 -0
  75. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/docs/trust/mcp-trust-draft.md +0 -0
  76. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/docs/trust/plugin-trust-draft.md +0 -0
  77. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/docs/trust/skill-trust-local.md +0 -0
  78. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/fuzzers/manifest_fuzzer.py +0 -0
  79. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/requirements.txt +0 -0
  80. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/schemas/plugin-quality.v1.json +0 -0
  81. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/schemas/scan-result.v1.json +0 -0
  82. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/schemas/verify-result.v1.json +0 -0
  83. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/__init__.py +0 -0
  84. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/action_runner.py +0 -0
  85. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/argparse_utils.py +0 -0
  86. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/checks/__init__.py +0 -0
  87. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/checks/best_practices.py +0 -0
  88. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/checks/claude.py +0 -0
  89. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/checks/code_quality.py +0 -0
  90. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/checks/ecosystem_common.py +0 -0
  91. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/checks/gemini.py +0 -0
  92. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/checks/manifest.py +0 -0
  93. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/checks/manifest_support.py +0 -0
  94. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/checks/marketplace.py +0 -0
  95. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/checks/mcp_security.py +0 -0
  96. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/checks/opencode.py +0 -0
  97. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/checks/operational_security.py +0 -0
  98. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/checks/security.py +0 -0
  99. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/checks/skill_security.py +0 -0
  100. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/cli.py +0 -0
  101. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/cli_ui.py +0 -0
  102. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/config.py +0 -0
  103. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/ecosystems/__init__.py +0 -0
  104. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/ecosystems/base.py +0 -0
  105. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/ecosystems/claude.py +0 -0
  106. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/ecosystems/codex.py +0 -0
  107. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/ecosystems/detect.py +0 -0
  108. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/ecosystems/gemini.py +0 -0
  109. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/ecosystems/opencode.py +0 -0
  110. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/ecosystems/registry.py +0 -0
  111. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/ecosystems/types.py +0 -0
  112. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/github_reporting.py +0 -0
  113. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/__init__.py +0 -0
  114. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/adapters/__init__.py +0 -0
  115. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/adapters/antigravity.py +0 -0
  116. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/adapters/base.py +0 -0
  117. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/adapters/claude_code.py +0 -0
  118. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/adapters/cloud_identity.py +0 -0
  119. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/adapters/codex.py +0 -0
  120. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/adapters/copilot.py +0 -0
  121. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/adapters/cursor.py +0 -0
  122. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/adapters/gemini.py +0 -0
  123. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/adapters/hermes.py +0 -0
  124. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/adapters/openclaw.py +0 -0
  125. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/adapters/openclaw_config.py +0 -0
  126. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/adapters/openclaw_support.py +0 -0
  127. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/adapters/opencode.py +0 -0
  128. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/adapters/opencode_artifacts.py +0 -0
  129. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/advisory_model.py +0 -0
  130. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/approvals.py +0 -0
  131. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/bridge/__init__.py +0 -0
  132. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/cli/__init__.py +0 -0
  133. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/cli/approval_commands.py +0 -0
  134. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/cli/bootstrap.py +0 -0
  135. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/cli/connect_flow.py +0 -0
  136. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/cli/install_commands.py +0 -0
  137. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/cli/product.py +0 -0
  138. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/cli/prompt.py +0 -0
  139. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/cli/render.py +0 -0
  140. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/cli/update_commands.py +0 -0
  141. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/codex_config.py +0 -0
  142. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/config.py +0 -0
  143. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/consumer/__init__.py +0 -0
  144. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/consumer/service.py +0 -0
  145. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/daemon/__init__.py +0 -0
  146. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/daemon/client.py +0 -0
  147. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/daemon/manager.py +0 -0
  148. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/daemon/server.py +0 -0
  149. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/daemon/static/apple-touch-icon.png +0 -0
  150. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/daemon/static/assets/guard-dashboard.js +0 -0
  151. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/daemon/static/assets/index.css +0 -0
  152. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Icon_Dark.png +0 -0
  153. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Whole.png +0 -0
  154. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/daemon/static/favicon-16x16.png +0 -0
  155. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/daemon/static/favicon-32x32.png +0 -0
  156. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/daemon/static/favicon.ico +0 -0
  157. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/daemon/static/index.html +0 -0
  158. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/edge_events.py +0 -0
  159. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/incident.py +0 -0
  160. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/launcher.py +0 -0
  161. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/models.py +0 -0
  162. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/policy/__init__.py +0 -0
  163. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/policy/engine.py +0 -0
  164. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/protect.py +0 -0
  165. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/proxy/__init__.py +0 -0
  166. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/proxy/remote.py +0 -0
  167. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/proxy/stdio.py +0 -0
  168. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/receipts/__init__.py +0 -0
  169. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/receipts/manager.py +0 -0
  170. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/redaction.py +0 -0
  171. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/risk.py +0 -0
  172. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/runtime/__init__.py +0 -0
  173. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/runtime/actions.py +0 -0
  174. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/runtime/decisions.py +0 -0
  175. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/runtime/detectors.py +0 -0
  176. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/runtime/runner.py +0 -0
  177. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/runtime/secret_file_requests.py +0 -0
  178. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/runtime/signals.py +0 -0
  179. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/runtime/surface_server.py +0 -0
  180. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/schemas/__init__.py +0 -0
  181. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/schemas/consumer_mode.py +0 -0
  182. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/schemas/guard_event_v1.py +0 -0
  183. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/schemas/surface_server.py +0 -0
  184. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/shims.py +0 -0
  185. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/store.py +0 -0
  186. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/store_approvals.py +0 -0
  187. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/store_connect.py +0 -0
  188. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/guard/types.py +0 -0
  189. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/integrations/__init__.py +0 -0
  190. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/integrations/cisco_mcp_scanner.py +0 -0
  191. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/integrations/cisco_skill_scanner.py +0 -0
  192. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/lint_fixes.py +0 -0
  193. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/marketplace_support.py +0 -0
  194. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/models.py +0 -0
  195. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/path_support.py +0 -0
  196. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/policy.py +0 -0
  197. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/quality_artifact.py +0 -0
  198. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/repo_detect.py +0 -0
  199. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/reporting.py +0 -0
  200. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/rules/__init__.py +0 -0
  201. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/rules/registry.py +0 -0
  202. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/rules/specs.py +0 -0
  203. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/scanner.py +0 -0
  204. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/submission.py +0 -0
  205. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/suppressions.py +0 -0
  206. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/trust_domain_scoring.py +0 -0
  207. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/trust_helpers.py +0 -0
  208. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/trust_mcp_scoring.py +0 -0
  209. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/trust_models.py +0 -0
  210. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/trust_plugin_scoring.py +0 -0
  211. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/trust_scoring.py +0 -0
  212. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/trust_skill_scoring.py +0 -0
  213. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/trust_specs.py +0 -0
  214. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/src/codex_plugin_scanner/verification.py +0 -0
  215. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/__init__.py +0 -0
  216. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/__init__.py +0 -0
  217. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/bad-plugin/.codex-plugin/plugin.json +0 -0
  218. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/bad-plugin/.mcp.json +0 -0
  219. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/bad-plugin/secrets.js +0 -0
  220. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/claude-plugin-good/.claude-plugin/plugin.json +0 -0
  221. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/claude-plugin-good/LICENSE +0 -0
  222. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/claude-plugin-good/README.md +0 -0
  223. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/claude-plugin-good/SECURITY.md +0 -0
  224. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/claude-plugin-good/hooks/hooks.json +0 -0
  225. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/claude-plugin-good/skills/example/SKILL.md +0 -0
  226. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/code-quality-bad/evil.js +0 -0
  227. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/code-quality-bad/inject.js +0 -0
  228. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/gemini-extension-good/GEMINI.md +0 -0
  229. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/gemini-extension-good/LICENSE +0 -0
  230. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/gemini-extension-good/README.md +0 -0
  231. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/gemini-extension-good/SECURITY.md +0 -0
  232. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/gemini-extension-good/commands/hello.toml +0 -0
  233. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/gemini-extension-good/gemini-extension.json +0 -0
  234. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/good-plugin/.codex-plugin/plugin.json +0 -0
  235. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/good-plugin/.codexignore +0 -0
  236. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/good-plugin/LICENSE +0 -0
  237. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/good-plugin/README.md +0 -0
  238. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/good-plugin/SECURITY.md +0 -0
  239. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/good-plugin/assets/icon.svg +0 -0
  240. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/good-plugin/assets/logo.svg +0 -0
  241. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/good-plugin/assets/screenshot.svg +0 -0
  242. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/good-plugin/skills/example/SKILL.md +0 -0
  243. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/guard-codex-malicious-mcp/.codex/config.toml +0 -0
  244. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/hermes-plugin-evil/config.yaml +0 -0
  245. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/hermes-plugin-evil/mcp_servers.json +0 -0
  246. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/hermes-plugin-evil/skills/security/malicious/SKILL.md +0 -0
  247. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/SKILL.md +0 -0
  248. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/references/api-setup.md +0 -0
  249. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/scripts/deploy.sh +0 -0
  250. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/hermes-plugin-evil/skills/utils/benign/SKILL.md +0 -0
  251. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/malformed-json/.codex-plugin/plugin.json +0 -0
  252. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/malicious-skill-plugin/.codex-plugin/plugin.json +0 -0
  253. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/malicious-skill-plugin/.codexignore +0 -0
  254. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/malicious-skill-plugin/LICENSE +0 -0
  255. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/malicious-skill-plugin/README.md +0 -0
  256. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/malicious-skill-plugin/SECURITY.md +0 -0
  257. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/malicious-skill-plugin/skills/leaky-skill/SKILL.md +0 -0
  258. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/mcp-canary-server.py +0 -0
  259. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/minimal-plugin/.codex-plugin/plugin.json +0 -0
  260. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/missing-fields/.codex-plugin/plugin.json +0 -0
  261. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/mit-license/LICENSE +0 -0
  262. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/multi-ecosystem-repo/codex-plugin/.codex-plugin/plugin.json +0 -0
  263. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/multi-ecosystem-repo/codex-plugin/LICENSE +0 -0
  264. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/multi-ecosystem-repo/codex-plugin/README.md +0 -0
  265. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/multi-ecosystem-repo/codex-plugin/SECURITY.md +0 -0
  266. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/multi-ecosystem-repo/gemini-ext/README.md +0 -0
  267. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/multi-ecosystem-repo/gemini-ext/gemini-extension.json +0 -0
  268. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/multi-plugin-repo/.agents/plugins/marketplace.json +0 -0
  269. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codex-plugin/plugin.json +0 -0
  270. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codexignore +0 -0
  271. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/LICENSE +0 -0
  272. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/README.md +0 -0
  273. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/SECURITY.md +0 -0
  274. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/skills/example/SKILL.md +0 -0
  275. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/.codex-plugin/plugin.json +0 -0
  276. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/skills/example/SKILL.md +0 -0
  277. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/no-version/.codex-plugin/plugin.json +0 -0
  278. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/opencode-good/.opencode/commands/hello.md +0 -0
  279. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/opencode-good/.opencode/plugins/example.ts +0 -0
  280. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/opencode-good/LICENSE +0 -0
  281. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/opencode-good/README.md +0 -0
  282. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/opencode-good/SECURITY.md +0 -0
  283. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/opencode-good/opencode.jsonc +0 -0
  284. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/skills-missing-dir/.codex-plugin/plugin.json +0 -0
  285. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/skills-no-frontmatter/.codex-plugin/plugin.json +0 -0
  286. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/skills-no-frontmatter/skills/bad-skill/SKILL.md +0 -0
  287. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/with-marketplace/.codex-plugin/plugin.json +0 -0
  288. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/with-marketplace/marketplace-broken.json +0 -0
  289. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/fixtures/with-marketplace/marketplace.json +0 -0
  290. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test-trust-scoring.py +0 -0
  291. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test-trust-specs.py +0 -0
  292. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_action_runner.py +0 -0
  293. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_best_practices.py +0 -0
  294. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_cisco_install_surfaces.py +0 -0
  295. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_cli.py +0 -0
  296. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_code_quality.py +0 -0
  297. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_config.py +0 -0
  298. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_coverage_remaining.py +0 -0
  299. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_ecosystems.py +0 -0
  300. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_edge_cases.py +0 -0
  301. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_final_coverage.py +0 -0
  302. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_guard_approvals.py +0 -0
  303. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_guard_bootstrap.py +0 -0
  304. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_guard_capabilities.py +0 -0
  305. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_guard_claude_adapter.py +0 -0
  306. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_guard_codex_e2e.py +0 -0
  307. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_guard_codex_install.py +0 -0
  308. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_guard_codex_proxy.py +0 -0
  309. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_guard_config_paths.py +0 -0
  310. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_guard_connect_flow.py +0 -0
  311. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_guard_consumer_mode.py +0 -0
  312. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_guard_copilot_adapter.py +0 -0
  313. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_guard_copilot_proxy.py +0 -0
  314. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_guard_daemon_manager.py +0 -0
  315. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_guard_event_schema_v1.py +0 -0
  316. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_guard_events.py +0 -0
  317. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_guard_launch_env.py +0 -0
  318. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_guard_opencode_proxy.py +0 -0
  319. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_guard_product_flow.py +0 -0
  320. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_guard_protect.py +0 -0
  321. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_guard_render.py +0 -0
  322. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_guard_runtime_action_harnesses.py +0 -0
  323. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_guard_runtime_actions.py +0 -0
  324. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_guard_runtime_decisions.py +0 -0
  325. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_guard_runtime_signals.py +0 -0
  326. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_guard_store_migrations.py +0 -0
  327. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_guard_surface_server.py +0 -0
  328. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_guard_verdicts.py +0 -0
  329. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_hermes_adapter.py +0 -0
  330. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_integration.py +0 -0
  331. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_lint_fixes.py +0 -0
  332. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_live_cisco_smoke.py +0 -0
  333. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_manifest.py +0 -0
  334. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_marketplace.py +0 -0
  335. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_mcp_security.py +0 -0
  336. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_openclaw_adapter.py +0 -0
  337. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_operational_security.py +0 -0
  338. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_policy.py +0 -0
  339. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_quality_artifact.py +0 -0
  340. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_rule_registry.py +0 -0
  341. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_scanner.py +0 -0
  342. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_schema_contracts.py +0 -0
  343. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_security.py +0 -0
  344. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_security_ops.py +0 -0
  345. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_skill_security.py +0 -0
  346. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_submission.py +0 -0
  347. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_trust_scoring.py +0 -0
  348. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_trust_specs.py +0 -0
  349. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_verification.py +0 -0
  350. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/tests/test_versioning.py +0 -0
  351. {plugin_scanner-2.0.106 → plugin_scanner-2.0.108}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plugin-scanner
3
- Version: 2.0.106
3
+ Version: 2.0.108
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.106"
7
+ version = "2.0.108"
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.106"
7
+ version = "2.0.108"
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"
@@ -2,7 +2,10 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import json
5
6
  from dataclasses import dataclass
7
+ from hashlib import sha256
8
+ from pathlib import PurePath
6
9
 
7
10
  from ..models import GuardArtifact, HarnessDetection
8
11
 
@@ -31,6 +34,23 @@ _GUARD_PROXY_COMMANDS = frozenset(
31
34
  }
32
35
  )
33
36
 
37
+ _STABLE_SLASH_FLAG_TOKENS = frozenset(
38
+ {
39
+ "/?",
40
+ "/debug",
41
+ "/dry-run",
42
+ "/h",
43
+ "/help",
44
+ "/quiet",
45
+ "/read-only",
46
+ "/readonly",
47
+ "/ro",
48
+ "/safe",
49
+ "/trace",
50
+ "/verbose",
51
+ }
52
+ )
53
+
34
54
 
35
55
  def managed_stdio_servers(detection: HarnessDetection) -> tuple[ManagedMcpServer, ...]:
36
56
  """Extract local stdio MCP servers from a harness detection payload."""
@@ -76,6 +96,8 @@ def proxy_cli_args(
76
96
  guard_home,
77
97
  "--server-name",
78
98
  server.name,
99
+ "--server-id",
100
+ stable_mcp_server_identifier(server),
79
101
  "--source-scope",
80
102
  server.source_scope,
81
103
  "--config-path",
@@ -94,6 +116,24 @@ def proxy_cli_args(
94
116
  return args
95
117
 
96
118
 
119
+ def stable_mcp_server_identifier(server: ManagedMcpServer) -> str:
120
+ """Build a Cloud-stable MCP server ID without local config path material."""
121
+
122
+ harness = server.harness.strip().lower()
123
+ source_scope = server.source_scope.strip().lower()
124
+ server_name = _stable_server_name(server.name)
125
+ payload = {
126
+ "harness": harness,
127
+ "source_scope": source_scope,
128
+ "name": server_name,
129
+ "command": _stable_command_name(server.command),
130
+ "args": [_stable_arg_token(value) for value in server.args],
131
+ "transport": server.transport,
132
+ }
133
+ digest = sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest()[:20]
134
+ return f"mcp_server:{harness}:{source_scope}:{server_name}:{digest}"
135
+
136
+
97
137
  def _managed_stdio_server(artifact: GuardArtifact) -> ManagedMcpServer | None:
98
138
  if artifact.artifact_type != "mcp_server":
99
139
  return None
@@ -119,6 +159,50 @@ def _managed_stdio_server(artifact: GuardArtifact) -> ManagedMcpServer | None:
119
159
  )
120
160
 
121
161
 
162
+ def _stable_arg_token(value: str) -> str:
163
+ key, separator, item = value.partition("=")
164
+ if separator and (_looks_like_path_assignment(key, item) or _looks_like_path_token(item)):
165
+ return f"{key}=<path>"
166
+ if _looks_like_path_token(value):
167
+ return "<path>"
168
+ return value
169
+
170
+
171
+ def _stable_server_name(value: str) -> str:
172
+ return value.strip().lower() or "unnamed"
173
+
174
+
175
+ def _stable_command_name(value: str) -> str:
176
+ return PurePath(value.replace("\\", "/")).name.lower()
177
+
178
+
179
+ def _looks_like_path_assignment(key: str, value: str) -> bool:
180
+ normalized_key = key.strip().lstrip("-/").lower().replace("_", "-")
181
+ path_keys = {
182
+ "cache",
183
+ "config",
184
+ "cwd",
185
+ "dir",
186
+ "directory",
187
+ "file",
188
+ "folder",
189
+ "path",
190
+ "root",
191
+ "workspace",
192
+ "workdir",
193
+ }
194
+ return normalized_key in path_keys and value.strip().replace("\\", "/").startswith("/")
195
+
196
+
197
+ def _looks_like_path_token(value: str) -> bool:
198
+ normalized = value.strip().replace("\\", "/")
199
+ if normalized.startswith(("~/", "./", "../")):
200
+ return True
201
+ if normalized.startswith("/"):
202
+ return "/" in normalized[1:] or normalized.lower() not in _STABLE_SLASH_FLAG_TOKENS
203
+ return len(normalized) >= 3 and normalized[0].isalpha() and normalized[1:3] == ":/"
204
+
205
+
122
206
  def _string_env(value: object) -> dict[str, str]:
123
207
  if not isinstance(value, dict):
124
208
  return {}
@@ -149,4 +233,5 @@ __all__ = [
149
233
  "managed_stdio_servers",
150
234
  "proxy_cli_args",
151
235
  "skipped_stdio_server_names",
236
+ "stable_mcp_server_identifier",
152
237
  ]
@@ -196,6 +196,25 @@ def severity_from_deltas(deltas: tuple[CapabilityDelta, ...]) -> int:
196
196
  return max(delta.severity for delta in deltas)
197
197
 
198
198
 
199
+ def normalized_capability_categories(capabilities: CapabilitySet) -> tuple[str, ...]:
200
+ """Return broad Cloud access graph categories for skills and MCP tools."""
201
+
202
+ categories: set[str] = set()
203
+ if capabilities.network_hosts or capabilities.network_schemes:
204
+ categories.add("network")
205
+ if capabilities.filesystem_paths:
206
+ categories.add("filesystem")
207
+ if capabilities.secret_classes:
208
+ categories.add("secret")
209
+ if capabilities.subprocess_invocation or capabilities.interpreters or capabilities.shell_wrappers:
210
+ categories.add("execution")
211
+ if capabilities.publisher:
212
+ categories.add("publisher")
213
+ if capabilities.transport != "local":
214
+ categories.add("transport")
215
+ return tuple(sorted(categories))
216
+
217
+
199
218
  def _first_seen_deltas(after: CapabilitySet) -> list[CapabilityDelta]:
200
219
  deltas: list[CapabilityDelta] = []
201
220
  if after.network_hosts:
@@ -97,6 +97,7 @@ from ..runtime.secret_file_requests import (
97
97
  extract_sensitive_tool_action_request,
98
98
  is_explicitly_benign_tool_action_request,
99
99
  )
100
+ from ..runtime.secret_sensitivity import SecretContentMatch, classify_secret_content
100
101
  from ..runtime.surface_server import GuardSurfaceRuntime
101
102
  from ..store import GuardStore
102
103
  from .approval_commands import add_approval_parser, run_approval_command
@@ -539,6 +540,7 @@ def _configure_guard_parser(guard_parser: argparse.ArgumentParser) -> None:
539
540
  codex_proxy_parser = guard_subparsers.add_parser("codex-mcp-proxy", help=argparse.SUPPRESS)
540
541
  _add_guard_common_args(codex_proxy_parser)
541
542
  codex_proxy_parser.add_argument("--server-name", required=True)
543
+ codex_proxy_parser.add_argument("--server-id")
542
544
  codex_proxy_parser.add_argument("--source-scope", default="project")
543
545
  codex_proxy_parser.add_argument("--config-path", required=True)
544
546
  codex_proxy_parser.add_argument("--transport", default="stdio")
@@ -548,6 +550,7 @@ def _configure_guard_parser(guard_parser: argparse.ArgumentParser) -> None:
548
550
  opencode_proxy_parser = guard_subparsers.add_parser("opencode-mcp-proxy", help=argparse.SUPPRESS)
549
551
  _add_guard_common_args(opencode_proxy_parser)
550
552
  opencode_proxy_parser.add_argument("--server-name", required=True)
553
+ opencode_proxy_parser.add_argument("--server-id")
551
554
  opencode_proxy_parser.add_argument("--source-scope", default="project")
552
555
  opencode_proxy_parser.add_argument("--config-path", required=True)
553
556
  opencode_proxy_parser.add_argument("--transport", default="local")
@@ -557,6 +560,7 @@ def _configure_guard_parser(guard_parser: argparse.ArgumentParser) -> None:
557
560
  copilot_proxy_parser = guard_subparsers.add_parser("copilot-mcp-proxy", help=argparse.SUPPRESS)
558
561
  _add_guard_common_args(copilot_proxy_parser)
559
562
  copilot_proxy_parser.add_argument("--server-name", required=True)
563
+ copilot_proxy_parser.add_argument("--server-id")
560
564
  copilot_proxy_parser.add_argument("--source-scope", default="project")
561
565
  copilot_proxy_parser.add_argument("--config-path", required=True)
562
566
  copilot_proxy_parser.add_argument("--transport", default="stdio")
@@ -807,6 +811,7 @@ def run_guard_command(
807
811
  source_scope=args.source_scope,
808
812
  config_path=args.config_path,
809
813
  transport=args.transport,
814
+ server_id=args.server_id,
810
815
  )
811
816
  return proxy.serve()
812
817
 
@@ -820,6 +825,7 @@ def run_guard_command(
820
825
  source_scope=args.source_scope,
821
826
  config_path=args.config_path,
822
827
  transport=args.transport,
828
+ server_id=args.server_id,
823
829
  )
824
830
  return proxy.serve()
825
831
 
@@ -833,6 +839,7 @@ def run_guard_command(
833
839
  source_scope=args.source_scope,
834
840
  config_path=args.config_path,
835
841
  transport=args.transport,
842
+ server_id=args.server_id,
836
843
  )
837
844
  return proxy.serve()
838
845
 
@@ -1253,6 +1260,7 @@ def run_guard_command(
1253
1260
  decision_source="pre-tool-hook",
1254
1261
  now=now,
1255
1262
  signals=decision.signals,
1263
+ risk_categories=decision.risk_categories,
1256
1264
  remember=False,
1257
1265
  )
1258
1266
  if _should_emit_copilot_hook_response(args):
@@ -1267,6 +1275,7 @@ def run_guard_command(
1267
1275
  decision_source="pre-tool-hook",
1268
1276
  now=now,
1269
1277
  signals=decision.signals,
1278
+ risk_categories=decision.risk_categories,
1270
1279
  )
1271
1280
  if _should_emit_copilot_hook_response(args):
1272
1281
  _emit_copilot_hook_response(
@@ -1345,6 +1354,7 @@ def run_guard_command(
1345
1354
  decision_source=decision.source,
1346
1355
  now=now,
1347
1356
  signals=decision.signals,
1357
+ risk_categories=decision.risk_categories,
1348
1358
  remember=False,
1349
1359
  )
1350
1360
  if _should_emit_copilot_hook_response(args):
@@ -1359,6 +1369,7 @@ def run_guard_command(
1359
1369
  decision_source="permission-request-hook",
1360
1370
  now=now,
1361
1371
  signals=decision.signals,
1372
+ risk_categories=decision.risk_categories,
1362
1373
  )
1363
1374
  approval_center_url = ensure_guard_daemon(guard_home)
1364
1375
  approval_flow = get_adapter(args.harness).approval_flow(managed_install=managed_install)
@@ -2010,18 +2021,19 @@ def _should_emit_native_hook_json_response(
2010
2021
 
2011
2022
 
2012
2023
  def _should_emit_native_hook_exit_block(args: argparse.Namespace, *, event_name: str, policy_action: str) -> bool:
2013
- codex_runtime_marker = (
2014
- os.environ.get("CODEX_HOME", "").strip() or os.environ.get("CODEX_MANAGED_BY_BUN", "").strip()
2015
- )
2016
2024
  return (
2017
2025
  args.harness == "codex"
2018
2026
  and event_name == "PreToolUse"
2019
2027
  and policy_action in {"block", "sandbox-required", "require-reapproval"}
2020
2028
  and not getattr(args, "json", False)
2021
- and bool(codex_runtime_marker)
2029
+ and _is_codex_native_runtime()
2022
2030
  )
2023
2031
 
2024
2032
 
2033
+ def _is_codex_native_runtime() -> bool:
2034
+ return bool(os.environ.get("CODEX_HOME", "").strip() or os.environ.get("CODEX_MANAGED_BY_BUN", "").strip())
2035
+
2036
+
2025
2037
  def _codex_browser_approval_decision(
2026
2038
  *,
2027
2039
  args: argparse.Namespace,
@@ -2040,6 +2052,8 @@ def _codex_browser_approval_decision(
2040
2052
  return None
2041
2053
  if policy_action not in {"block", "sandbox-required", "require-reapproval"}:
2042
2054
  return None
2055
+ if event_name == "PreToolUse" and not _is_codex_native_runtime():
2056
+ return None
2043
2057
  approval_requests = response_payload.get("approval_requests")
2044
2058
  if not isinstance(approval_requests, list):
2045
2059
  return None
@@ -3949,10 +3963,6 @@ def _hook_runtime_artifact(
3949
3963
  )
3950
3964
 
3951
3965
 
3952
- _CODEX_SECRET_OUTPUT_PATTERN = re.compile(
3953
- r"(?i)(?:fake[_-]?credential|fake[_-]?secret|"
3954
- r"(?:api[_-]?key|auth[_-]?token|credential|npm[_-]?token|private[_-]?key|secret|token|password)\s*[:=])"
3955
- )
3956
3966
  _CODEX_PROMPT_SECRET_KEY_MARKERS = ("TOKEN", "SECRET", "PASSWORD", "PASS", "API_KEY", "API-KEY", "AUTH", "CREDENTIAL")
3957
3967
  _CODEX_TOOL_RESPONSE_MAX_DEPTH = 5
3958
3968
  _CODEX_TOOL_RESPONSE_TEXT_LIMIT = 20000
@@ -3967,13 +3977,19 @@ def _codex_post_tool_output_artifact(
3967
3977
  cwd: Path | None,
3968
3978
  ) -> GuardArtifact | None:
3969
3979
  response_text = _collect_codex_tool_response_text(payload.get("tool_response"))
3970
- if not response_text or _CODEX_SECRET_OUTPUT_PATTERN.search(response_text) is None:
3980
+ content_matches = classify_secret_content(response_text)
3981
+ if not content_matches:
3971
3982
  return None
3972
3983
  tool_name = _coalesce_string(payload.get("tool_name"), "Bash")
3973
3984
  command_text = _codex_post_tool_command_text(payload)
3974
3985
  if not command_text:
3975
3986
  command_text = tool_name
3976
- if _codex_command_is_read_only_source_inspection(command_text, cwd=cwd):
3987
+ if _codex_source_inspection_can_skip_secret_output(
3988
+ command_text=command_text,
3989
+ response_text=response_text,
3990
+ content_matches=content_matches,
3991
+ cwd=cwd,
3992
+ ):
3977
3993
  return None
3978
3994
  fingerprint = hashlib.sha256(
3979
3995
  json.dumps(
@@ -4096,6 +4112,10 @@ _CODEX_SOURCE_SEARCH_EXTENSIONS = frozenset(
4096
4112
  ".yml",
4097
4113
  }
4098
4114
  )
4115
+ _CODEX_BENIGN_SOURCE_DOTFILES = frozenset({".nvmrc"})
4116
+ _CODEX_BENIGN_SECRET_FIXTURE_ASSIGNMENT_PATTERN = re.compile(
4117
+ r"(?i)\s*fake[_-]?(?:credential|secret|token)\s*[:=]\s*(?:\"[^\r\n\"]+\"|'[^\r\n']+'|[^\s\"',}]+)\s*"
4118
+ )
4099
4119
  _CODEX_SENSITIVE_SEARCH_BASENAMES = frozenset(
4100
4120
  {
4101
4121
  ".aws",
@@ -4114,6 +4134,35 @@ _CODEX_SENSITIVE_SEARCH_BASENAMES = frozenset(
4114
4134
  _CODEX_SED_PRINT_SCRIPT_PATTERN = re.compile(r"^\s*(?:\$|\d+)?(?:\s*,\s*(?:\$|\d+))?p\s*$")
4115
4135
 
4116
4136
 
4137
+ def _codex_source_inspection_can_skip_secret_output(
4138
+ *,
4139
+ command_text: str,
4140
+ response_text: str,
4141
+ content_matches: tuple[SecretContentMatch, ...],
4142
+ cwd: Path | None,
4143
+ ) -> bool:
4144
+ if not _codex_command_is_read_only_source_inspection(command_text, cwd=cwd):
4145
+ return False
4146
+ if any(match.sensitivity != "medium" for match in content_matches):
4147
+ return False
4148
+ if _codex_command_references_benign_source_dotfile(command_text):
4149
+ return _codex_output_is_only_benign_secret_fixture(response_text)
4150
+ return True
4151
+
4152
+
4153
+ def _codex_output_is_only_benign_secret_fixture(response_text: str) -> bool:
4154
+ lines = [line for line in response_text.splitlines() if line.strip()]
4155
+ return bool(lines) and all(_CODEX_BENIGN_SECRET_FIXTURE_ASSIGNMENT_PATTERN.fullmatch(line) for line in lines)
4156
+
4157
+
4158
+ def _codex_command_references_benign_source_dotfile(command_text: str) -> bool:
4159
+ try:
4160
+ parts = shlex.split(command_text)
4161
+ except ValueError:
4162
+ return False
4163
+ return any(Path(part).name.lower() in _CODEX_BENIGN_SOURCE_DOTFILES for part in parts)
4164
+
4165
+
4117
4166
  def _codex_command_is_read_only_source_inspection(command_text: str, *, cwd: Path | None) -> bool:
4118
4167
  command = command_text.strip()
4119
4168
  if not command:
@@ -4631,13 +4680,16 @@ def _codex_search_target_is_source_like(target: str, *, cwd: Path | None) -> boo
4631
4680
  lowered_parts = [part.lower() for part in parts]
4632
4681
  if any(part in _CODEX_SENSITIVE_SEARCH_BASENAMES for part in lowered_parts):
4633
4682
  return False
4634
- if any(part.startswith(".") for part in parts):
4683
+ hidden_parts = [part for part in lowered_parts if part.startswith(".")]
4684
+ if hidden_parts and not all(part in _CODEX_BENIGN_SOURCE_DOTFILES for part in hidden_parts):
4635
4685
  return False
4636
4686
  normalized = "/".join(parts)
4637
4687
  if normalized in {prefix.rstrip("/") for prefix in _CODEX_SOURCE_SEARCH_PREFIXES}:
4638
4688
  return True
4639
4689
  if any(normalized.startswith(prefix) for prefix in _CODEX_SOURCE_SEARCH_PREFIXES):
4640
4690
  return True
4691
+ if Path(stripped).name.lower() in _CODEX_BENIGN_SOURCE_DOTFILES:
4692
+ return True
4641
4693
  return Path(stripped).suffix.lower() in _CODEX_SOURCE_SEARCH_EXTENSIONS
4642
4694
 
4643
4695
 
@@ -4732,7 +4784,7 @@ def _codex_prompt_credential_file_artifact(
4732
4784
  content = handle.read(_PROMPT_CONTENT_SCAN_MAX_BYTES).decode("utf-8", errors="ignore")
4733
4785
  except OSError:
4734
4786
  continue
4735
- if _CODEX_SECRET_OUTPUT_PATTERN.search(content) is None:
4787
+ if not classify_secret_content(content):
4736
4788
  continue
4737
4789
  normalized_path = str(path)
4738
4790
  fingerprint = hashlib.sha256(
@@ -22,6 +22,7 @@ class ToolCallDecision:
22
22
  source: str
23
23
  signals: tuple[str, ...]
24
24
  summary: str
25
+ risk_categories: tuple[str, ...] = ()
25
26
 
26
27
 
27
28
  def build_tool_call_artifact(
@@ -32,9 +33,12 @@ def build_tool_call_artifact(
32
33
  source_scope: str,
33
34
  config_path: str,
34
35
  transport: str,
36
+ server_id: str | None = None,
35
37
  server_fingerprint: object | None = None,
36
38
  ) -> GuardArtifact:
37
39
  metadata = {"server_name": server_name}
40
+ if server_id is not None:
41
+ metadata["server_id"] = server_id
38
42
  if server_fingerprint is not None:
39
43
  metadata["server_fingerprint"] = server_fingerprint
40
44
  return GuardArtifact(
@@ -82,20 +86,24 @@ def evaluate_tool_call(
82
86
  override = config.resolve_action_override(artifact.harness, artifact.artifact_id, artifact.publisher)
83
87
  action = _coerce_guard_action(override) if isinstance(override, str) else None
84
88
  if action is not None:
89
+ risk_categories = tool_call_risk_categories(artifact, arguments)
85
90
  return ToolCallDecision(
86
91
  action=action,
87
92
  source="policy",
88
93
  signals=tool_call_risk_signals(artifact, arguments),
89
94
  summary="Local Guard policy matched this exact tool call.",
95
+ risk_categories=risk_categories,
90
96
  )
91
97
 
92
98
  signals = tool_call_risk_signals(artifact, arguments)
99
+ risk_categories = tool_call_risk_categories(artifact, arguments)
93
100
  if len(signals) == 0:
94
101
  return ToolCallDecision(
95
102
  action="allow",
96
103
  source="heuristic",
97
104
  signals=(),
98
105
  summary="Guard did not detect a high-risk signal in this tool call.",
106
+ risk_categories=(),
99
107
  )
100
108
  if config.mode == "prompt":
101
109
  return ToolCallDecision(
@@ -103,37 +111,95 @@ def evaluate_tool_call(
103
111
  source="heuristic",
104
112
  signals=signals,
105
113
  summary=tool_call_risk_summary(artifact, arguments),
114
+ risk_categories=risk_categories,
106
115
  )
107
116
  return ToolCallDecision(
108
117
  action="block",
109
118
  source="heuristic",
110
119
  signals=signals,
111
120
  summary=tool_call_risk_summary(artifact, arguments),
121
+ risk_categories=risk_categories,
112
122
  )
113
123
 
114
124
 
115
125
  def tool_call_risk_signals(artifact: GuardArtifact, arguments: object) -> tuple[str, ...]:
126
+ signals_by_category = {
127
+ "destructive_mutation": "tool name implies destructive file or system changes",
128
+ "command_execution": "tool name implies shell or command execution",
129
+ "outbound_network": "call arguments imply outbound network activity",
130
+ "secret_access": "call arguments mention sensitive local files or secrets",
131
+ "privileged_system_mutation": "call arguments imply privileged system mutation",
132
+ }
133
+ return tuple(signals_by_category[category] for category in tool_call_risk_categories(artifact, arguments))
134
+
135
+
136
+ def tool_call_risk_categories(artifact: GuardArtifact, arguments: object) -> tuple[str, ...]:
137
+ """Return normalized Cloud risk categories for one MCP tool call."""
138
+
139
+ categories = _tool_call_risk_category_set(artifact, arguments)
140
+ order = (
141
+ "command_execution",
142
+ "destructive_mutation",
143
+ "outbound_network",
144
+ "privileged_system_mutation",
145
+ "secret_access",
146
+ )
147
+ return tuple(category for category in order if category in categories)
148
+
149
+
150
+ def _tool_call_risk_category_set(artifact: GuardArtifact, arguments: object) -> set[str]:
116
151
  tool_name = PurePath(artifact.command or artifact.name).name
117
- serialized_arguments = json.dumps(arguments, sort_keys=True).lower() if arguments is not None else ""
118
- combined = f"{artifact.name.lower()} {serialized_arguments}"
152
+ serialized_arguments = _serialized_tool_arguments(arguments)
153
+ combined = _risk_match_text(f"{artifact.name} {serialized_arguments}")
119
154
  tool_name_tokens = set(_tool_name_tokens(tool_name))
120
- signals: list[str] = []
155
+ categories: set[str] = set()
121
156
 
122
157
  if len(tool_name_tokens.intersection({"delete", "remove", "rm", "destroy", "erase"})) > 0:
123
- signals.append("tool name implies destructive file or system changes")
158
+ categories.add("destructive_mutation")
124
159
  if len(tool_name_tokens.intersection({"shell", "bash", "exec", "execute", "command", "powershell"})) > 0:
125
- signals.append("tool name implies shell or command execution")
126
- if any(token in combined for token in ("http://", "https://", "curl", "wget", "fetch", "axios", "requests")):
127
- signals.append("call arguments imply outbound network activity")
128
- if any(
129
- token in combined
130
- for token in (".env", ".ssh", "id_rsa", "credentials", ".npmrc", ".pypirc", "token", "secret", "passwd")
160
+ categories.add("command_execution")
161
+ if _matches_any(
162
+ combined,
163
+ (
164
+ r"https?://",
165
+ _token_pattern("curl", "wget", "fetch", "axios", "requests"),
166
+ ),
167
+ ):
168
+ categories.add("outbound_network")
169
+ if _matches_any(
170
+ combined,
171
+ (
172
+ r"(?<![a-z0-9_-])\.env(?![a-z0-9_-])",
173
+ r"(?<![a-z0-9_-])\.ssh(?![a-z0-9_-])",
174
+ r"(?<![a-z0-9])(id[_-]?rsa|credentials|token|secret|passwd)(?![a-z0-9])",
175
+ r"(?<![a-z0-9_-])\.(npmrc|pypirc)(?![a-z0-9_-])",
176
+ ),
131
177
  ):
132
- signals.append("call arguments mention sensitive local files or secrets")
133
- if any(token in combined for token in ("sudo", "chmod", "chown", "launchctl", "systemctl")):
134
- signals.append("call arguments imply privileged system mutation")
178
+ categories.add("secret_access")
179
+ if _matches_any(
180
+ combined,
181
+ (_token_pattern("sudo", "chmod", "chown", "launchctl", "systemctl"),),
182
+ ):
183
+ categories.add("privileged_system_mutation")
184
+ return categories
185
+
135
186
 
136
- return tuple(_dedupe(signals))
187
+ def _serialized_tool_arguments(arguments: object) -> str:
188
+ if arguments is None:
189
+ return ""
190
+ try:
191
+ return json.dumps(arguments, sort_keys=True, default=str)
192
+ except (TypeError, ValueError):
193
+ return str(arguments)
194
+
195
+
196
+ def _matches_any(value: str, patterns: tuple[str, ...]) -> bool:
197
+ return any(re.search(pattern, value) is not None for pattern in patterns)
198
+
199
+
200
+ def _token_pattern(*tokens: str) -> str:
201
+ alternatives = "|".join(re.escape(token) for token in tokens)
202
+ return rf"(?<![a-z0-9])({alternatives})(?![a-z0-9])"
137
203
 
138
204
 
139
205
  def tool_call_risk_summary(artifact: GuardArtifact, arguments: object) -> str:
@@ -154,6 +220,7 @@ def allow_tool_call(
154
220
  now: str,
155
221
  signals: tuple[str, ...],
156
222
  remember: bool,
223
+ risk_categories: tuple[str, ...] = (),
157
224
  ) -> GuardReceipt:
158
225
  if remember:
159
226
  store.upsert_policy(
@@ -196,6 +263,7 @@ def allow_tool_call(
196
263
  "artifact_id": artifact.artifact_id,
197
264
  "artifact_hash": artifact_hash,
198
265
  "decision_source": decision_source,
266
+ "risk_categories": list(risk_categories),
199
267
  "signals": list(signals),
200
268
  },
201
269
  now,
@@ -211,6 +279,7 @@ def block_tool_call(
211
279
  decision_source: str,
212
280
  now: str,
213
281
  signals: tuple[str, ...],
282
+ risk_categories: tuple[str, ...] = (),
214
283
  ) -> GuardReceipt:
215
284
  store.record_inventory_artifact(
216
285
  artifact=artifact,
@@ -239,6 +308,7 @@ def block_tool_call(
239
308
  "artifact_id": artifact.artifact_id,
240
309
  "artifact_hash": artifact_hash,
241
310
  "decision_source": decision_source,
311
+ "risk_categories": list(risk_categories),
242
312
  "signals": list(signals),
243
313
  },
244
314
  now,
@@ -258,10 +328,18 @@ def _dedupe(values: list[str]) -> list[str]:
258
328
 
259
329
 
260
330
  def _tool_name_tokens(tool_name: str) -> tuple[str, ...]:
261
- camel_normalized = re.sub(r"([a-z0-9])([A-Z])", r"\1 \2", tool_name)
331
+ camel_normalized = _camel_token_normalized(tool_name)
262
332
  return tuple(token for token in re.findall(r"[a-z0-9]+", camel_normalized.lower()) if token)
263
333
 
264
334
 
335
+ def _risk_match_text(value: str) -> str:
336
+ return _camel_token_normalized(value).lower()
337
+
338
+
339
+ def _camel_token_normalized(value: str) -> str:
340
+ return re.sub(r"([a-z0-9])([A-Z])", r"\1 \2", value)
341
+
342
+
265
343
  def _coerce_guard_action(value: str) -> GuardAction | None:
266
344
  for action in GUARD_ACTION_VALUES:
267
345
  if value == action: