plugin-scanner 2.0.112__tar.gz → 2.0.113__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 (357) hide show
  1. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/PKG-INFO +1 -1
  2. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/pyproject.toml +1 -1
  3. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/pyproject.toml.bak +1 -1
  4. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/runtime/data_flow.py +26 -2
  5. plugin_scanner-2.0.113/src/codex_plugin_scanner/guard/runtime/data_flow_rules.py +472 -0
  6. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/runtime/detectors.py +20 -2
  7. plugin_scanner-2.0.113/src/codex_plugin_scanner/guard/runtime/secret_sources.py +99 -0
  8. plugin_scanner-2.0.113/src/codex_plugin_scanner/guard/runtime/shell_commands.py +202 -0
  9. plugin_scanner-2.0.113/src/codex_plugin_scanner/guard/runtime/temp_files.py +57 -0
  10. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/version.py +1 -1
  11. plugin_scanner-2.0.113/tests/test_guard_data_flow.py +331 -0
  12. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_runtime_detectors.py +25 -7
  13. plugin_scanner-2.0.112/tests/test_guard_data_flow.py +0 -117
  14. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.clusterfuzzlite/Dockerfile +0 -0
  15. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.clusterfuzzlite/build.sh +0 -0
  16. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.clusterfuzzlite/project.yaml +0 -0
  17. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.clusterfuzzlite/requirements-atheris.txt +0 -0
  18. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.dockerignore +0 -0
  19. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.github/CODEOWNERS +0 -0
  20. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.github/ISSUE_TEMPLATE/bug-report.yml +0 -0
  21. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  22. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
  23. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.github/dependabot.yml +0 -0
  24. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.github/workflows/ci.yml +0 -0
  25. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.github/workflows/codeql.yml +0 -0
  26. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.github/workflows/dependabot-uv-lock.yml +0 -0
  27. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.github/workflows/fuzz.yml +0 -0
  28. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.github/workflows/harness-smoke.yml +0 -0
  29. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.github/workflows/publish.yml +0 -0
  30. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.github/workflows/scorecard.yml +0 -0
  31. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.gitignore +0 -0
  32. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.pre-commit-hooks.yaml +0 -0
  33. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/CONTRIBUTING.md +0 -0
  34. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/Dockerfile +0 -0
  35. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/LICENSE +0 -0
  36. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/README.md +0 -0
  37. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/SECURITY.md +0 -0
  38. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/index.html +0 -0
  39. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/package.json +0 -0
  40. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/pnpm-lock.yaml +0 -0
  41. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/public/apple-touch-icon.png +0 -0
  42. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/public/brand/Logo_Icon_Dark.png +0 -0
  43. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/public/brand/Logo_Whole.png +0 -0
  44. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/public/favicon-16x16.png +0 -0
  45. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/public/favicon-32x32.png +0 -0
  46. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/public/favicon.ico +0 -0
  47. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/src/app.tsx +0 -0
  48. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/src/approval-center-layout.tsx +0 -0
  49. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/src/approval-center-primitives.tsx +0 -0
  50. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/src/approval-center-utils.ts +0 -0
  51. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/src/fleet-workspace.tsx +0 -0
  52. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/src/guard-api.test.ts +0 -0
  53. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/src/guard-api.ts +0 -0
  54. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/src/guard-demo.ts +0 -0
  55. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/src/guard-types.ts +0 -0
  56. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/src/main.tsx +0 -0
  57. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/src/receipts-workspace.tsx +0 -0
  58. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/src/runtime-overview.tsx +0 -0
  59. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/src/settings-workspace.tsx +0 -0
  60. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/src/styles.css +0 -0
  61. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/src/vite-env.d.ts +0 -0
  62. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/tsconfig.json +0 -0
  63. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/vite.config.ts +0 -0
  64. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/docker-requirements.txt +0 -0
  65. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/docs/guard/approval-audit.md +0 -0
  66. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/docs/guard/architecture.md +0 -0
  67. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/docs/guard/get-started.md +0 -0
  68. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/docs/guard/harness-support.md +0 -0
  69. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/docs/guard/local-vs-cloud.md +0 -0
  70. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/docs/guard/testing-matrix.md +0 -0
  71. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/docs/trust/mcp-trust-draft.md +0 -0
  72. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/docs/trust/plugin-trust-draft.md +0 -0
  73. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/docs/trust/skill-trust-local.md +0 -0
  74. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/fuzzers/manifest_fuzzer.py +0 -0
  75. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/requirements.txt +0 -0
  76. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/schemas/plugin-quality.v1.json +0 -0
  77. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/schemas/scan-result.v1.json +0 -0
  78. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/schemas/verify-result.v1.json +0 -0
  79. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/__init__.py +0 -0
  80. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/action_runner.py +0 -0
  81. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/argparse_utils.py +0 -0
  82. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/checks/__init__.py +0 -0
  83. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/checks/best_practices.py +0 -0
  84. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/checks/claude.py +0 -0
  85. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/checks/code_quality.py +0 -0
  86. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/checks/ecosystem_common.py +0 -0
  87. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/checks/gemini.py +0 -0
  88. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/checks/manifest.py +0 -0
  89. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/checks/manifest_support.py +0 -0
  90. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/checks/marketplace.py +0 -0
  91. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/checks/mcp_security.py +0 -0
  92. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/checks/opencode.py +0 -0
  93. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/checks/operational_security.py +0 -0
  94. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/checks/security.py +0 -0
  95. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/checks/skill_security.py +0 -0
  96. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/cli.py +0 -0
  97. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/cli_ui.py +0 -0
  98. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/config.py +0 -0
  99. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/ecosystems/__init__.py +0 -0
  100. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/ecosystems/base.py +0 -0
  101. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/ecosystems/claude.py +0 -0
  102. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/ecosystems/codex.py +0 -0
  103. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/ecosystems/detect.py +0 -0
  104. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/ecosystems/gemini.py +0 -0
  105. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/ecosystems/opencode.py +0 -0
  106. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/ecosystems/registry.py +0 -0
  107. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/ecosystems/types.py +0 -0
  108. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/github_reporting.py +0 -0
  109. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/__init__.py +0 -0
  110. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/__init__.py +0 -0
  111. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/antigravity.py +0 -0
  112. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/base.py +0 -0
  113. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/claude_code.py +0 -0
  114. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/cloud_identity.py +0 -0
  115. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/codex.py +0 -0
  116. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/copilot.py +0 -0
  117. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/cursor.py +0 -0
  118. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/gemini.py +0 -0
  119. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/hermes.py +0 -0
  120. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/mcp_servers.py +0 -0
  121. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/openclaw.py +0 -0
  122. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/openclaw_config.py +0 -0
  123. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/openclaw_support.py +0 -0
  124. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/opencode.py +0 -0
  125. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/opencode_artifacts.py +0 -0
  126. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/advisory_model.py +0 -0
  127. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/approvals.py +0 -0
  128. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/bridge/__init__.py +0 -0
  129. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/capabilities.py +0 -0
  130. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/cli/__init__.py +0 -0
  131. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/cli/approval_commands.py +0 -0
  132. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/cli/bootstrap.py +0 -0
  133. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/cli/commands.py +0 -0
  134. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/cli/connect_flow.py +0 -0
  135. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/cli/install_commands.py +0 -0
  136. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/cli/product.py +0 -0
  137. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/cli/prompt.py +0 -0
  138. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/cli/render.py +0 -0
  139. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/cli/update_commands.py +0 -0
  140. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/codex_config.py +0 -0
  141. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/config.py +0 -0
  142. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/consumer/__init__.py +0 -0
  143. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/consumer/service.py +0 -0
  144. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/daemon/__init__.py +0 -0
  145. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/daemon/client.py +0 -0
  146. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/daemon/manager.py +0 -0
  147. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/daemon/server.py +0 -0
  148. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/daemon/static/apple-touch-icon.png +0 -0
  149. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/daemon/static/assets/guard-dashboard.js +0 -0
  150. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/daemon/static/assets/index.css +0 -0
  151. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Icon_Dark.png +0 -0
  152. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Whole.png +0 -0
  153. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/daemon/static/favicon-16x16.png +0 -0
  154. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/daemon/static/favicon-32x32.png +0 -0
  155. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/daemon/static/favicon.ico +0 -0
  156. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/daemon/static/index.html +0 -0
  157. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/edge_events.py +0 -0
  158. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/incident.py +0 -0
  159. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/launcher.py +0 -0
  160. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/mcp_tool_calls.py +0 -0
  161. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/models.py +0 -0
  162. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/policy/__init__.py +0 -0
  163. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/policy/engine.py +0 -0
  164. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/protect.py +0 -0
  165. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/proxy/__init__.py +0 -0
  166. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/proxy/remote.py +0 -0
  167. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/proxy/runtime_mcp.py +0 -0
  168. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/proxy/stdio.py +0 -0
  169. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/receipts/__init__.py +0 -0
  170. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/receipts/manager.py +0 -0
  171. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/redaction.py +0 -0
  172. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/risk.py +0 -0
  173. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/runtime/__init__.py +0 -0
  174. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/runtime/actions.py +0 -0
  175. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/runtime/decisions.py +0 -0
  176. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/runtime/runner.py +0 -0
  177. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/runtime/secret_file_requests.py +0 -0
  178. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/runtime/secret_sensitivity.py +0 -0
  179. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/runtime/signals.py +0 -0
  180. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/runtime/surface_server.py +0 -0
  181. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/schemas/__init__.py +0 -0
  182. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/schemas/consumer_mode.py +0 -0
  183. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/schemas/guard_event_v1.py +0 -0
  184. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/schemas/surface_server.py +0 -0
  185. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/shims.py +0 -0
  186. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/store.py +0 -0
  187. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/store_approvals.py +0 -0
  188. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/store_connect.py +0 -0
  189. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/types.py +0 -0
  190. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/integrations/__init__.py +0 -0
  191. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/integrations/cisco_mcp_scanner.py +0 -0
  192. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/integrations/cisco_skill_scanner.py +0 -0
  193. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/lint_fixes.py +0 -0
  194. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/marketplace_support.py +0 -0
  195. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/models.py +0 -0
  196. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/path_support.py +0 -0
  197. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/policy.py +0 -0
  198. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/quality_artifact.py +0 -0
  199. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/repo_detect.py +0 -0
  200. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/reporting.py +0 -0
  201. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/rules/__init__.py +0 -0
  202. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/rules/registry.py +0 -0
  203. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/rules/specs.py +0 -0
  204. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/scanner.py +0 -0
  205. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/submission.py +0 -0
  206. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/suppressions.py +0 -0
  207. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/trust_domain_scoring.py +0 -0
  208. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/trust_helpers.py +0 -0
  209. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/trust_mcp_scoring.py +0 -0
  210. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/trust_models.py +0 -0
  211. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/trust_plugin_scoring.py +0 -0
  212. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/trust_scoring.py +0 -0
  213. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/trust_skill_scoring.py +0 -0
  214. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/trust_specs.py +0 -0
  215. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/verification.py +0 -0
  216. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/__init__.py +0 -0
  217. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/conftest.py +0 -0
  218. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/__init__.py +0 -0
  219. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/bad-plugin/.codex-plugin/plugin.json +0 -0
  220. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/bad-plugin/.mcp.json +0 -0
  221. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/bad-plugin/secrets.js +0 -0
  222. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/claude-plugin-good/.claude-plugin/plugin.json +0 -0
  223. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/claude-plugin-good/LICENSE +0 -0
  224. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/claude-plugin-good/README.md +0 -0
  225. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/claude-plugin-good/SECURITY.md +0 -0
  226. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/claude-plugin-good/hooks/hooks.json +0 -0
  227. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/claude-plugin-good/skills/example/SKILL.md +0 -0
  228. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/code-quality-bad/evil.js +0 -0
  229. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/code-quality-bad/inject.js +0 -0
  230. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/gemini-extension-good/GEMINI.md +0 -0
  231. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/gemini-extension-good/LICENSE +0 -0
  232. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/gemini-extension-good/README.md +0 -0
  233. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/gemini-extension-good/SECURITY.md +0 -0
  234. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/gemini-extension-good/commands/hello.toml +0 -0
  235. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/gemini-extension-good/gemini-extension.json +0 -0
  236. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/good-plugin/.codex-plugin/plugin.json +0 -0
  237. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/good-plugin/.codexignore +0 -0
  238. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/good-plugin/LICENSE +0 -0
  239. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/good-plugin/README.md +0 -0
  240. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/good-plugin/SECURITY.md +0 -0
  241. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/good-plugin/assets/icon.svg +0 -0
  242. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/good-plugin/assets/logo.svg +0 -0
  243. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/good-plugin/assets/screenshot.svg +0 -0
  244. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/good-plugin/skills/example/SKILL.md +0 -0
  245. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/guard-codex-malicious-mcp/.codex/config.toml +0 -0
  246. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/hermes-plugin-evil/config.yaml +0 -0
  247. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/hermes-plugin-evil/mcp_servers.json +0 -0
  248. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/hermes-plugin-evil/skills/security/malicious/SKILL.md +0 -0
  249. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/SKILL.md +0 -0
  250. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/references/api-setup.md +0 -0
  251. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/scripts/deploy.sh +0 -0
  252. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/hermes-plugin-evil/skills/utils/benign/SKILL.md +0 -0
  253. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/malformed-json/.codex-plugin/plugin.json +0 -0
  254. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/malicious-skill-plugin/.codex-plugin/plugin.json +0 -0
  255. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/malicious-skill-plugin/.codexignore +0 -0
  256. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/malicious-skill-plugin/LICENSE +0 -0
  257. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/malicious-skill-plugin/README.md +0 -0
  258. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/malicious-skill-plugin/SECURITY.md +0 -0
  259. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/malicious-skill-plugin/skills/leaky-skill/SKILL.md +0 -0
  260. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/mcp-canary-server.py +0 -0
  261. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/minimal-plugin/.codex-plugin/plugin.json +0 -0
  262. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/missing-fields/.codex-plugin/plugin.json +0 -0
  263. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/mit-license/LICENSE +0 -0
  264. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/multi-ecosystem-repo/codex-plugin/.codex-plugin/plugin.json +0 -0
  265. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/multi-ecosystem-repo/codex-plugin/LICENSE +0 -0
  266. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/multi-ecosystem-repo/codex-plugin/README.md +0 -0
  267. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/multi-ecosystem-repo/codex-plugin/SECURITY.md +0 -0
  268. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/multi-ecosystem-repo/gemini-ext/README.md +0 -0
  269. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/multi-ecosystem-repo/gemini-ext/gemini-extension.json +0 -0
  270. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/multi-plugin-repo/.agents/plugins/marketplace.json +0 -0
  271. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codex-plugin/plugin.json +0 -0
  272. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codexignore +0 -0
  273. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/LICENSE +0 -0
  274. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/README.md +0 -0
  275. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/SECURITY.md +0 -0
  276. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/skills/example/SKILL.md +0 -0
  277. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/.codex-plugin/plugin.json +0 -0
  278. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/skills/example/SKILL.md +0 -0
  279. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/no-version/.codex-plugin/plugin.json +0 -0
  280. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/opencode-good/.opencode/commands/hello.md +0 -0
  281. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/opencode-good/.opencode/plugins/example.ts +0 -0
  282. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/opencode-good/LICENSE +0 -0
  283. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/opencode-good/README.md +0 -0
  284. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/opencode-good/SECURITY.md +0 -0
  285. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/opencode-good/opencode.jsonc +0 -0
  286. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/skills-missing-dir/.codex-plugin/plugin.json +0 -0
  287. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/skills-no-frontmatter/.codex-plugin/plugin.json +0 -0
  288. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/skills-no-frontmatter/skills/bad-skill/SKILL.md +0 -0
  289. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/with-marketplace/.codex-plugin/plugin.json +0 -0
  290. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/with-marketplace/marketplace-broken.json +0 -0
  291. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/with-marketplace/marketplace.json +0 -0
  292. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test-trust-scoring.py +0 -0
  293. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test-trust-specs.py +0 -0
  294. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_action_runner.py +0 -0
  295. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_best_practices.py +0 -0
  296. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_cisco_install_surfaces.py +0 -0
  297. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_cli.py +0 -0
  298. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_code_quality.py +0 -0
  299. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_config.py +0 -0
  300. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_coverage_remaining.py +0 -0
  301. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_ecosystems.py +0 -0
  302. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_edge_cases.py +0 -0
  303. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_final_coverage.py +0 -0
  304. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_access_graph.py +0 -0
  305. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_approvals.py +0 -0
  306. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_bootstrap.py +0 -0
  307. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_capabilities.py +0 -0
  308. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_claude_adapter.py +0 -0
  309. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_cli.py +0 -0
  310. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_codex_e2e.py +0 -0
  311. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_codex_install.py +0 -0
  312. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_codex_proxy.py +0 -0
  313. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_config_paths.py +0 -0
  314. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_connect_flow.py +0 -0
  315. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_consumer_mode.py +0 -0
  316. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_copilot_adapter.py +0 -0
  317. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_copilot_proxy.py +0 -0
  318. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_daemon_manager.py +0 -0
  319. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_event_schema_v1.py +0 -0
  320. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_events.py +0 -0
  321. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_launch_env.py +0 -0
  322. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_opencode_proxy.py +0 -0
  323. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_product_flow.py +0 -0
  324. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_protect.py +0 -0
  325. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_render.py +0 -0
  326. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_risk.py +0 -0
  327. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_runtime.py +0 -0
  328. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_runtime_action_harnesses.py +0 -0
  329. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_runtime_actions.py +0 -0
  330. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_runtime_decisions.py +0 -0
  331. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_runtime_signals.py +0 -0
  332. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_store_migrations.py +0 -0
  333. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_surface_server.py +0 -0
  334. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_verdicts.py +0 -0
  335. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_hermes_adapter.py +0 -0
  336. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_integration.py +0 -0
  337. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_lint_fixes.py +0 -0
  338. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_live_cisco_smoke.py +0 -0
  339. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_manifest.py +0 -0
  340. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_marketplace.py +0 -0
  341. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_mcp_security.py +0 -0
  342. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_openclaw_adapter.py +0 -0
  343. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_operational_security.py +0 -0
  344. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_policy.py +0 -0
  345. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_quality_artifact.py +0 -0
  346. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_rule_registry.py +0 -0
  347. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_scanner.py +0 -0
  348. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_schema_contracts.py +0 -0
  349. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_security.py +0 -0
  350. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_security_ops.py +0 -0
  351. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_skill_security.py +0 -0
  352. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_submission.py +0 -0
  353. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_trust_scoring.py +0 -0
  354. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_trust_specs.py +0 -0
  355. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_verification.py +0 -0
  356. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_versioning.py +0 -0
  357. {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plugin-scanner
3
- Version: 2.0.112
3
+ Version: 2.0.113
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.112"
7
+ version = "2.0.113"
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.112"
7
+ version = "2.0.113"
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"
@@ -207,6 +207,12 @@ def extract_pipes(command: str) -> tuple[ShellPipe, ...]:
207
207
  return tuple(pipes)
208
208
 
209
209
 
210
+ def extract_command_segments(command: str) -> tuple[str, ...]:
211
+ """Return top-level shell command segments split on command separators."""
212
+
213
+ return _split_top_level_commands(command)
214
+
215
+
210
216
  def extract_http_methods(command: str) -> tuple[str, ...]:
211
217
  """Return explicit or strongly implied HTTP methods referenced by shell text."""
212
218
 
@@ -226,6 +232,15 @@ def extract_urls(command: str) -> tuple[str, ...]:
226
232
  return _dedupe(url for url in urls if url)
227
233
 
228
234
 
235
+ def extract_url_ranges(command: str) -> tuple[tuple[int, int], ...]:
236
+ """Return HTTP(S) URL character ranges in shell text."""
237
+
238
+ return tuple(
239
+ (match.start(), match.start() + len(_strip_url_suffix(match.group(0))))
240
+ for match in _URL_PATTERN.finditer(command)
241
+ )
242
+
243
+
229
244
  def _append_http_method(methods: list[str], method: str) -> None:
230
245
  normalized = method.upper()
231
246
  if normalized in _HTTP_METHODS:
@@ -264,18 +279,27 @@ def _split_top_level_commands(command: str) -> tuple[str, ...]:
264
279
  if next_index != index + 1:
265
280
  index = next_index
266
281
  continue
267
- if state.is_top_level and command[index] == ";":
282
+ if state.is_top_level and command[index] in {";", "\n"}:
268
283
  _append_segment(parts, command[start:index])
269
284
  start = index + 1
270
285
  elif state.is_top_level and (command.startswith("&&", index) or command.startswith("||", index)):
271
286
  _append_segment(parts, command[start:index])
272
287
  start = index + 2
273
288
  index += 1
289
+ elif state.is_top_level and command[index] == "&" and _is_background_separator(command, index):
290
+ _append_segment(parts, command[start:index])
291
+ start = index + 1
274
292
  index += 1
275
293
  _append_segment(parts, command[start:])
276
294
  return tuple(parts)
277
295
 
278
296
 
297
+ def _is_background_separator(command: str, index: int) -> bool:
298
+ previous_char = command[index - 1] if index > 0 else ""
299
+ next_char = command[index + 1] if index + 1 < len(command) else ""
300
+ return previous_char not in {">", "<", "|"} and next_char not in {"&", ">"}
301
+
302
+
279
303
  def _split_top_level_pipes(command: str) -> tuple[str, ...]:
280
304
  parts: list[str] = []
281
305
  start = 0
@@ -291,7 +315,7 @@ def _split_top_level_pipes(command: str) -> tuple[str, ...]:
291
315
  next_is_pipe = index + 1 < len(command) and command[index + 1] == "|"
292
316
  if not previous_is_pipe and not next_is_pipe:
293
317
  _append_segment(parts, command[start:index])
294
- start = index + 1
318
+ start = index + 2 if index + 1 < len(command) and command[index + 1] == "&" else index + 1
295
319
  index += 1
296
320
  _append_segment(parts, command[start:])
297
321
  return tuple(parts)
@@ -0,0 +1,472 @@
1
+ """Data-flow exfiltration rules for Guard runtime shell actions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from collections.abc import Sequence
7
+ from pathlib import Path
8
+ from urllib.parse import urlparse
9
+
10
+ from codex_plugin_scanner.guard.runtime.actions import GuardActionEnvelope
11
+ from codex_plugin_scanner.guard.runtime.data_flow import (
12
+ ShellPipe,
13
+ extract_command_segments,
14
+ extract_command_substitutions,
15
+ extract_pipes,
16
+ extract_urls,
17
+ )
18
+ from codex_plugin_scanner.guard.runtime.secret_sensitivity import SecretPathMatch, classify_secret_path
19
+ from codex_plugin_scanner.guard.runtime.secret_sources import secret_path_matches_in_command, strip_shell_token
20
+ from codex_plugin_scanner.guard.runtime.shell_commands import (
21
+ command_execution_segments,
22
+ command_tokens_after_env_assignments,
23
+ git_remote_add_url_tokens,
24
+ is_scp_remote_target,
25
+ npm_publish_index,
26
+ npm_publish_is_dry_run,
27
+ scp_operands,
28
+ segment_executes_command,
29
+ )
30
+ from codex_plugin_scanner.guard.runtime.signals import RiskSignalCategory, RiskSignalV2
31
+ from codex_plugin_scanner.guard.runtime.temp_files import chmod_temp_targets, temp_write_targets
32
+
33
+ _CURL_DATA_FILE_PATTERN = re.compile(
34
+ r"(?s)(?:^|[\s;&|])(?i:curl|curl\.exe)\b.*?"
35
+ r"(?:(?:--data(?:-binary|-raw|-urlencode)?|-d)\s*@|--upload-file(?:=|\s+)|-T\s*)"
36
+ r"(?P<path>\"[^\"]+\"|'[^']+'|[^\s;&|]+)"
37
+ )
38
+ _CURL_DATA_STDIN_PATTERN = re.compile(
39
+ r"(?s)(?:^|[\s;&|])(?i:curl|curl\.exe)\b[^\r\n;&|]*?"
40
+ r"(?:(?:--data(?:-binary|-raw|-urlencode)?|-d)\s*@-|(?:--form|-F)(?:=|\s*)[^\s;&|]*@[.-](?=$|[\s;&|])|"
41
+ r"--upload-file(?:=|\s+)[.-](?=$|[\s;&|])|-T\s*[.-](?=$|[\s;&|]))"
42
+ )
43
+ _CURL_DATA_VALUE_PATTERN = re.compile(
44
+ r"(?s)(?:^|[\s;&|])(?i:curl|curl\.exe)\b[^\r\n;&|]*?"
45
+ r"(?:--data(?:-binary|-raw|-urlencode)?|-d)(?:=|\s*)"
46
+ r"(?P<value>\"[^\"]+\"|'[^']+'|[^\s;&|]+)"
47
+ )
48
+ _PYTHON_SECRET_POST_PATTERN = re.compile(
49
+ r"(?is)\bpython(?:3)?\b.*?-c\s+.*?"
50
+ r"(?:requests\.post|urllib\.request|http\.client).*?open\(['\"](?P<path>[^'\"]+)['\"]"
51
+ )
52
+ _NODE_SECRET_FETCH_PATTERN = re.compile(
53
+ r"(?is)\bnode\b.*?-e\s+.*?(?:fetch|axios\.post|https\.request|http\.request).*?"
54
+ r"readFileSync\(['\"](?P<path>[^'\"]+)['\"]"
55
+ )
56
+ _DNS_LONG_LABEL_PATTERN = re.compile(
57
+ r"(?i)(?:^|[\s;&|])(?:dig|nslookup|host)\s+"
58
+ r"(?P<host>(?:[A-Za-z0-9_-]+\.)*[A-Za-z0-9_-]{48,}(?:\.[A-Za-z0-9_-]+)*)"
59
+ )
60
+ _SCP_PATTERN = re.compile(r"(?is)(?:^|[\s;&|])scp\b(?P<body>[^\r\n;&|]+)")
61
+ _TOKEN_SOURCE_PATTERN = re.compile(r"(?i)\b(?:NPM_TOKEN|NODE_AUTH_TOKEN|_authToken|npm[_-]?token)\b")
62
+ _CLIPBOARD_COMMANDS = frozenset({"pbcopy", "xclip", "xsel", "wl-copy", "clip", "clip.exe"})
63
+ _WEBHOOK_HOST_PATTERN = re.compile(
64
+ r"webhook\.site|hooks\.slack\.com|discord\.com|pastebin\.com|gist\.github\.com|transfer\.sh|requestbin"
65
+ )
66
+
67
+
68
+ def detect_data_flow_exfiltration(
69
+ action: GuardActionEnvelope,
70
+ *,
71
+ workspace: Path | None,
72
+ ) -> tuple[RiskSignalV2, ...]:
73
+ if action.action_type != "shell_command" or action.command is None:
74
+ return ()
75
+ command = action.command
76
+ findings: list[RiskSignalV2] = []
77
+ secret_matches = _secret_path_matches_in_command(command, workspace=workspace)
78
+ pipes = extract_pipes(command)
79
+ if _has_secret_pipe_to_http_upload(pipes, command, workspace=workspace):
80
+ findings.append(
81
+ _data_flow_signal(
82
+ "secret-pipe-http",
83
+ "Shell pipeline sends a local secret to a network host",
84
+ "This command pipes a local secret into an HTTP upload.",
85
+ "secret path and HTTP upload appear in the same pipe chain",
86
+ category="network",
87
+ )
88
+ )
89
+ if _curl_uploads_secret_file(command, workspace=workspace):
90
+ findings.append(
91
+ _data_flow_signal(
92
+ "curl-data-file",
93
+ "Curl uploads a local secret file",
94
+ "This command sends a local secret file as curl request data.",
95
+ "curl data flag references a sensitive local path",
96
+ category="network",
97
+ )
98
+ )
99
+ if _python_posts_secret(command, workspace=workspace):
100
+ findings.append(
101
+ _data_flow_signal(
102
+ "python-secret-post",
103
+ "Python posts a local secret",
104
+ "This Python snippet reads a local secret and posts it to a network host.",
105
+ "python -c combines a sensitive file read with an HTTP post",
106
+ category="network",
107
+ )
108
+ )
109
+ if _node_fetches_secret(command, workspace=workspace):
110
+ findings.append(
111
+ _data_flow_signal(
112
+ "node-secret-fetch",
113
+ "Node sends a local secret",
114
+ "This Node snippet reads a local secret and sends it to a network host.",
115
+ "node -e combines fs.readFileSync on a sensitive path with fetch/request",
116
+ category="network",
117
+ )
118
+ )
119
+ if _encoded_secret_send(command, secret_matches, workspace=workspace):
120
+ findings.append(
121
+ _data_flow_signal(
122
+ "encoded-secret-send",
123
+ "Encoded local secret is sent to a network host",
124
+ "This command encodes a local secret before sending it to a network host.",
125
+ "base64 appears between a sensitive path and HTTP upload",
126
+ category="network",
127
+ )
128
+ )
129
+ if _has_dns_exfil_hostname(command):
130
+ findings.append(
131
+ _data_flow_signal(
132
+ "dns-exfil",
133
+ "DNS query looks like encoded exfiltration",
134
+ "This command sends an unusually long encoded-looking DNS label.",
135
+ "DNS tool is called with a long encoded-looking label",
136
+ category="network",
137
+ )
138
+ )
139
+ if _has_webhook_exfil(command, workspace=workspace):
140
+ findings.append(
141
+ _data_flow_signal(
142
+ "webhook-sink",
143
+ "Local secret is sent to a public collection endpoint",
144
+ "This command targets a paste, gist, transfer, or webhook endpoint with local secret data.",
145
+ "known collection host appears with local secret source",
146
+ category="network",
147
+ )
148
+ )
149
+ if _scp_sends_secret(command, workspace=workspace):
150
+ findings.append(
151
+ _data_flow_signal(
152
+ "scp-secret",
153
+ "SCP sends a local secret file",
154
+ "This command copies a local secret file to a remote host.",
155
+ "scp command references a sensitive local source",
156
+ category="network",
157
+ )
158
+ )
159
+ if _git_remote_adds_token_url(command):
160
+ findings.append(
161
+ _data_flow_signal(
162
+ "git-remote-token",
163
+ "Git remote URL contains an access token",
164
+ "This command stores a token-bearing URL in git remote configuration.",
165
+ "git remote add URL includes credentials before host",
166
+ category="secret",
167
+ )
168
+ )
169
+ if _npm_publish_with_token_source(command, workspace=workspace):
170
+ findings.append(
171
+ _data_flow_signal(
172
+ "npm-publish-token-source",
173
+ "NPM publish uses local token material",
174
+ "This command publishes a package while local npm token material is in scope.",
175
+ "npm publish appears with npm token source evidence",
176
+ category="network",
177
+ )
178
+ )
179
+ if _clipboard_receives_secret(pipes, command, workspace=workspace):
180
+ findings.append(
181
+ _data_flow_signal(
182
+ "clipboard-secret",
183
+ "Clipboard receives a local secret",
184
+ "This command copies local secret contents into the clipboard.",
185
+ "clipboard command receives sensitive source through a pipe",
186
+ category="secret",
187
+ )
188
+ )
189
+ if _world_readable_temp_secret(command, workspace=workspace):
190
+ findings.append(
191
+ _data_flow_signal(
192
+ "world-readable-temp-secret",
193
+ "Local secret is written to a world-readable temp file",
194
+ "This command writes local secret contents to a world-readable temp file.",
195
+ "sensitive source is redirected to /tmp and chmod makes it world-readable",
196
+ category="secret",
197
+ )
198
+ )
199
+ return tuple(_dedupe_signals(findings))
200
+
201
+
202
+ def _secret_path_matches_in_command(command: str, *, workspace: Path | None) -> tuple[SecretPathMatch, ...]:
203
+ return secret_path_matches_in_command(command, workspace=workspace, extra_paths=_curl_data_file_paths(command))
204
+
205
+
206
+ def _curl_data_file_paths(command: str) -> tuple[str, ...]:
207
+ paths: list[str] = []
208
+ for segment in command_execution_segments(command):
209
+ if not segment_executes_command(segment, {"curl", "curl.exe"}):
210
+ continue
211
+ for match in _CURL_DATA_FILE_PATTERN.finditer(segment):
212
+ paths.append(strip_shell_token(match.group("path")))
213
+ return tuple(paths)
214
+
215
+
216
+ def _has_secret_pipe_to_http_upload(pipes: Sequence[ShellPipe], command: str, *, workspace: Path | None) -> bool:
217
+ if not pipes or not _has_http_upload(command):
218
+ return False
219
+ return any(
220
+ extract_pipes(segment)
221
+ and _secret_path_matches_in_command(segment, workspace=workspace)
222
+ and _contains_http_upload_sink(segment)
223
+ for segment in extract_command_segments(command)
224
+ )
225
+
226
+
227
+ def _has_http_upload(command: str) -> bool:
228
+ return any(
229
+ segment_executes_command(segment, {"curl", "curl.exe"}) and _CURL_DATA_STDIN_PATTERN.search(segment)
230
+ for segment in command_execution_segments(command)
231
+ )
232
+
233
+
234
+ def _contains_http_upload_sink(command: str) -> bool:
235
+ return bool(extract_urls(command)) and _has_http_upload(command)
236
+
237
+
238
+ def _curl_uploads_secret_file(command: str, *, workspace: Path | None) -> bool:
239
+ return any(
240
+ extract_urls(segment)
241
+ and any(classify_secret_path(path, cwd=workspace) is not None for path in _curl_data_file_paths(segment))
242
+ for segment in extract_command_segments(command)
243
+ ) or _curl_data_substitution_reads_secret(command, workspace=workspace)
244
+
245
+
246
+ def _curl_data_substitution_reads_secret(command: str, *, workspace: Path | None) -> bool:
247
+ for segment in command_execution_segments(command):
248
+ if not segment_executes_command(segment, {"curl", "curl.exe"}):
249
+ continue
250
+ if not extract_urls(segment):
251
+ continue
252
+ for match in _CURL_DATA_VALUE_PATTERN.finditer(segment):
253
+ value = match.group("value")
254
+ if not any(
255
+ _secret_path_matches_in_command(substitution, workspace=workspace)
256
+ for substitution in extract_command_substitutions(value)
257
+ ):
258
+ continue
259
+ return True
260
+ return False
261
+
262
+
263
+ def _python_posts_secret(command: str, *, workspace: Path | None) -> bool:
264
+ for segment in extract_command_segments(command):
265
+ if (
266
+ segment_executes_command(segment, {"python", "python3"})
267
+ and extract_urls(segment)
268
+ and any(
269
+ classify_secret_path(match.group("path"), cwd=workspace) is not None
270
+ for match in _PYTHON_SECRET_POST_PATTERN.finditer(segment)
271
+ )
272
+ ):
273
+ return True
274
+ return False
275
+
276
+
277
+ def _node_fetches_secret(command: str, *, workspace: Path | None) -> bool:
278
+ for segment in extract_command_segments(command):
279
+ if (
280
+ segment_executes_command(segment, {"node"})
281
+ and extract_urls(segment)
282
+ and any(
283
+ classify_secret_path(match.group("path"), cwd=workspace) is not None
284
+ for match in _NODE_SECRET_FETCH_PATTERN.finditer(segment)
285
+ )
286
+ ):
287
+ return True
288
+ return False
289
+
290
+
291
+ def _encoded_secret_send(command: str, secret_matches: Sequence[SecretPathMatch], *, workspace: Path | None) -> bool:
292
+ if not secret_matches:
293
+ return False
294
+ return any(
295
+ _secret_path_matches_in_command(segment, workspace=workspace)
296
+ and "base64" in segment.lower()
297
+ and _has_http_upload(segment)
298
+ and bool(extract_urls(segment))
299
+ for segment in extract_command_segments(command)
300
+ )
301
+
302
+
303
+ def _has_dns_exfil_hostname(command: str) -> bool:
304
+ return any(
305
+ segment_executes_command(segment, {"dig", "nslookup", "host"})
306
+ and any(_has_long_encoded_label(token) for token in _dns_query_tokens(segment))
307
+ for segment in extract_command_segments(command)
308
+ )
309
+
310
+
311
+ def _dns_query_tokens(segment: str) -> tuple[str, ...]:
312
+ tokens = command_tokens_after_env_assignments(segment)
313
+ return tuple(token.strip("'\"") for token in tokens[1:] if "." in token and not token.startswith(("-", "+", "@")))
314
+
315
+
316
+ def _has_long_encoded_label(host: str) -> bool:
317
+ return any(len(label) >= 48 for label in host.split("."))
318
+
319
+
320
+ def _has_webhook_sink(urls: Sequence[str]) -> bool:
321
+ for url in urls:
322
+ host = (urlparse(url).hostname or "").lower()
323
+ if _WEBHOOK_HOST_PATTERN.search(host):
324
+ return True
325
+ return False
326
+
327
+
328
+ def _has_webhook_exfil(command: str, *, workspace: Path | None) -> bool:
329
+ for segment in extract_command_segments(command):
330
+ if not _has_webhook_sink(extract_urls(segment)):
331
+ continue
332
+ if _secret_path_matches_in_command(segment, workspace=workspace):
333
+ return True
334
+ if _curl_uploads_secret_file(segment, workspace=workspace):
335
+ return True
336
+ if _has_secret_pipe_to_http_upload(extract_pipes(segment), segment, workspace=workspace):
337
+ return True
338
+ return False
339
+
340
+
341
+ def _scp_sends_secret(command: str, *, workspace: Path | None) -> bool:
342
+ for segment in extract_command_segments(command):
343
+ if not segment_executes_command(segment, {"scp"}):
344
+ continue
345
+ match = _SCP_PATTERN.search(segment)
346
+ if match is None:
347
+ continue
348
+ operands = scp_operands(match.group("body"))
349
+ if len(operands) < 2:
350
+ continue
351
+ target = operands[-1]
352
+ sources = operands[:-1]
353
+ if not is_scp_remote_target(target):
354
+ continue
355
+ if any(not is_scp_remote_target(source) and classify_secret_path(source, cwd=workspace) for source in sources):
356
+ return True
357
+ return False
358
+
359
+
360
+ def _git_remote_adds_token_url(command: str) -> bool:
361
+ for segment in extract_command_segments(command):
362
+ tokens = command_tokens_after_env_assignments(segment)
363
+ url_tokens = git_remote_add_url_tokens(tokens)
364
+ if not url_tokens:
365
+ continue
366
+ for url in extract_urls(" ".join(url_tokens)):
367
+ parsed = urlparse(url)
368
+ if parsed.username and _looks_like_token(parsed.username):
369
+ return True
370
+ if parsed.password and _looks_like_token(parsed.password):
371
+ return True
372
+ return False
373
+
374
+
375
+ def _looks_like_token(value: str) -> bool:
376
+ lowered = value.lower()
377
+ return lowered.startswith(("ghp_", "github_pat_", "glpat-", "x-access-token")) or len(value) >= 24
378
+
379
+
380
+ def _npm_publish_with_token_source(command: str, *, workspace: Path | None) -> bool:
381
+ for segment in extract_command_segments(command):
382
+ tokens = command_tokens_after_env_assignments(segment)
383
+ publish_index = npm_publish_index(tokens)
384
+ if publish_index is None:
385
+ continue
386
+ if npm_publish_is_dry_run(tokens, publish_index):
387
+ continue
388
+ if _TOKEN_SOURCE_PATTERN.search(segment):
389
+ return True
390
+ if _has_npm_secret_match(_secret_path_matches_in_command(segment, workspace=workspace)):
391
+ return True
392
+ return False
393
+
394
+
395
+ def _has_npm_secret_match(secret_matches: Sequence[SecretPathMatch]) -> bool:
396
+ return any("npm" in match.family.lower() or ".npmrc" in match.requested_path.lower() for match in secret_matches)
397
+
398
+
399
+ def _clipboard_receives_secret(pipes: Sequence[ShellPipe], command: str, *, workspace: Path | None) -> bool:
400
+ if not pipes:
401
+ return False
402
+ return any(
403
+ (segment_pipes := extract_pipes(segment))
404
+ and _secret_path_matches_in_command(segment, workspace=workspace)
405
+ and any(segment_executes_command(pipe.right, _CLIPBOARD_COMMANDS) for pipe in segment_pipes)
406
+ for segment in extract_command_segments(command)
407
+ )
408
+
409
+
410
+ def _world_readable_temp_secret(command: str, *, workspace: Path | None) -> bool:
411
+ write_targets = {
412
+ target
413
+ for segment in extract_command_segments(command)
414
+ if _secret_path_matches_in_command(segment, workspace=workspace)
415
+ for target in temp_write_targets(segment)
416
+ }
417
+ if not write_targets:
418
+ return False
419
+ return any(
420
+ target in write_targets and _mode_makes_world_readable(mode) for target, mode in chmod_temp_targets(command)
421
+ )
422
+
423
+
424
+ def _mode_makes_world_readable(mode: str) -> bool:
425
+ normalized = mode.lower()
426
+ if normalized.isdigit():
427
+ return normalized[-1] in {"4", "5", "6", "7"}
428
+ for clause in normalized.split(","):
429
+ if "+r" in clause:
430
+ who = clause.split("+", 1)[0]
431
+ if not who or "a" in who or "o" in who:
432
+ return True
433
+ if "=" in clause:
434
+ who, permissions = clause.split("=", 1)
435
+ if "r" in permissions and (not who or "a" in who or "o" in who):
436
+ return True
437
+ return False
438
+
439
+
440
+ def _data_flow_signal(
441
+ signal_key: str,
442
+ title: str,
443
+ plain_reason: str,
444
+ technical_detail: str,
445
+ *,
446
+ category: RiskSignalCategory,
447
+ ) -> RiskSignalV2:
448
+ return RiskSignalV2(
449
+ signal_id=f"data-flow:{signal_key}",
450
+ category=category,
451
+ severity="critical",
452
+ confidence="strong",
453
+ detector="data_flow.exfiltration",
454
+ title=title,
455
+ plain_reason=plain_reason,
456
+ technical_detail=technical_detail,
457
+ evidence_ref="command",
458
+ redaction_level="summary",
459
+ false_positive_hint="Allow only if this exact command intentionally moves non-sensitive local data.",
460
+ advisory_id=None,
461
+ )
462
+
463
+
464
+ def _dedupe_signals(signals: Sequence[RiskSignalV2]) -> tuple[RiskSignalV2, ...]:
465
+ seen: set[str] = set()
466
+ result: list[RiskSignalV2] = []
467
+ for signal in signals:
468
+ if signal.signal_id in seen:
469
+ continue
470
+ seen.add(signal.signal_id)
471
+ result.append(signal)
472
+ return tuple(result)
@@ -10,6 +10,7 @@ from typing import Literal, Protocol
10
10
 
11
11
  from codex_plugin_scanner.guard.config import GuardConfig
12
12
  from codex_plugin_scanner.guard.runtime.actions import GuardActionEnvelope
13
+ from codex_plugin_scanner.guard.runtime.data_flow_rules import detect_data_flow_exfiltration
13
14
  from codex_plugin_scanner.guard.runtime.secret_sensitivity import SecretPathMatch, classify_secret_path
14
15
  from codex_plugin_scanner.guard.runtime.signals import RiskSignalCategory, RiskSignalV2
15
16
 
@@ -120,7 +121,7 @@ class DetectorRegistry:
120
121
  if elapsed_ms > timeout_ms:
121
122
  telemetry.append(_telemetry(detector, "timeout", elapsed_ms=elapsed_ms))
122
123
  continue
123
- signals.extend(detector_signals)
124
+ signals.extend(_filter_signals(detector_signals, category_filter))
124
125
  telemetry.append(_telemetry(detector, "ok", elapsed_ms=elapsed_ms))
125
126
  return DetectorRunResult(signals=tuple(signals), telemetry=tuple(telemetry))
126
127
 
@@ -136,8 +137,16 @@ class SecretPathDetector:
136
137
  return tuple(_secret_path_signal(match, index=index) for index, match in enumerate(matches))
137
138
 
138
139
 
140
+ class DataFlowExfiltrationDetector:
141
+ detector_id = "data_flow.exfiltration"
142
+ categories: tuple[RiskSignalCategory, ...] = ("secret", "network")
143
+
144
+ def detect(self, action: GuardActionEnvelope, context: DetectorContext) -> tuple[RiskSignalV2, ...]:
145
+ return detect_data_flow_exfiltration(action, workspace=context.workspace)
146
+
147
+
139
148
  def register_default_detectors() -> tuple[GuardDetector, ...]:
140
- return (SecretPathDetector(),)
149
+ return (DataFlowExfiltrationDetector(), SecretPathDetector())
141
150
 
142
151
 
143
152
  def _secret_path_signal(match: SecretPathMatch, *, index: int) -> RiskSignalV2:
@@ -170,6 +179,15 @@ def _elapsed_ms(started_at: float, finished_at: float) -> int:
170
179
  return max(0, round((finished_at - started_at) * 1000))
171
180
 
172
181
 
182
+ def _filter_signals(
183
+ signals: Sequence[RiskSignalV2],
184
+ category_filter: frozenset[RiskSignalCategory] | None,
185
+ ) -> tuple[RiskSignalV2, ...]:
186
+ if category_filter is None:
187
+ return tuple(signals)
188
+ return tuple(signal for signal in signals if signal.category in category_filter)
189
+
190
+
173
191
  def _slug(value: str) -> str:
174
192
  return "-".join(part for part in value.lower().replace(".", " ").replace("/", " ").split() if part)
175
193