plugin-scanner 2.0.123__tar.gz → 2.0.125__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 (380) hide show
  1. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/PKG-INFO +1 -1
  2. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/pyproject.toml +1 -1
  3. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/pyproject.toml.bak +1 -1
  4. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/cli/render.py +37 -1
  5. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/config.py +11 -0
  6. plugin_scanner-2.0.125/src/codex_plugin_scanner/guard/runtime/sandbox.py +349 -0
  7. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/store_approvals.py +145 -10
  8. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/version.py +1 -1
  9. plugin_scanner-2.0.125/tests/test_guard_approval_store_scale.py +510 -0
  10. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_config_paths.py +31 -0
  11. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_render.py +25 -0
  12. plugin_scanner-2.0.125/tests/test_guard_sandbox.py +232 -0
  13. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/.clusterfuzzlite/Dockerfile +0 -0
  14. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/.clusterfuzzlite/build.sh +0 -0
  15. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/.clusterfuzzlite/project.yaml +0 -0
  16. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/.clusterfuzzlite/requirements-atheris.txt +0 -0
  17. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/.dockerignore +0 -0
  18. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/.github/CODEOWNERS +0 -0
  19. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/.github/ISSUE_TEMPLATE/bug-report.yml +0 -0
  20. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  21. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
  22. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/.github/dependabot.yml +0 -0
  23. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/.github/workflows/ci.yml +0 -0
  24. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/.github/workflows/codeql.yml +0 -0
  25. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/.github/workflows/dependabot-uv-lock.yml +0 -0
  26. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/.github/workflows/fuzz.yml +0 -0
  27. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/.github/workflows/harness-smoke.yml +0 -0
  28. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/.github/workflows/publish.yml +0 -0
  29. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/.github/workflows/scorecard.yml +0 -0
  30. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/.gitignore +0 -0
  31. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/.pre-commit-hooks.yaml +0 -0
  32. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/CONTRIBUTING.md +0 -0
  33. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/Dockerfile +0 -0
  34. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/LICENSE +0 -0
  35. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/README.md +0 -0
  36. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/SECURITY.md +0 -0
  37. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/dashboard/index.html +0 -0
  38. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/dashboard/package.json +0 -0
  39. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/dashboard/pnpm-lock.yaml +0 -0
  40. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/dashboard/public/apple-touch-icon.png +0 -0
  41. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/dashboard/public/brand/Logo_Icon_Dark.png +0 -0
  42. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/dashboard/public/brand/Logo_Whole.png +0 -0
  43. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/dashboard/public/favicon-16x16.png +0 -0
  44. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/dashboard/public/favicon-32x32.png +0 -0
  45. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/dashboard/public/favicon.ico +0 -0
  46. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/dashboard/src/app.tsx +0 -0
  47. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/dashboard/src/approval-center-layout.tsx +0 -0
  48. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/dashboard/src/approval-center-primitives.tsx +0 -0
  49. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/dashboard/src/approval-center-utils.ts +0 -0
  50. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/dashboard/src/data-flow-evidence-card.tsx +0 -0
  51. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/dashboard/src/fleet-workspace.tsx +0 -0
  52. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/dashboard/src/guard-api.test.ts +0 -0
  53. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/dashboard/src/guard-api.ts +0 -0
  54. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/dashboard/src/guard-demo.ts +0 -0
  55. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/dashboard/src/guard-types.ts +0 -0
  56. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/dashboard/src/main.tsx +0 -0
  57. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/dashboard/src/receipts-workspace.tsx +0 -0
  58. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/dashboard/src/runtime-overview.tsx +0 -0
  59. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/dashboard/src/settings-workspace.tsx +0 -0
  60. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/dashboard/src/styles.css +0 -0
  61. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/dashboard/src/vite-env.d.ts +0 -0
  62. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/dashboard/tsconfig.json +0 -0
  63. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/dashboard/vite.config.ts +0 -0
  64. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/docker-requirements.txt +0 -0
  65. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/docs/guard/approval-audit.md +0 -0
  66. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/docs/guard/architecture.md +0 -0
  67. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/docs/guard/get-started.md +0 -0
  68. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/docs/guard/harness-support.md +0 -0
  69. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/docs/guard/local-vs-cloud.md +0 -0
  70. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/docs/guard/testing-matrix.md +0 -0
  71. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/docs/trust/mcp-trust-draft.md +0 -0
  72. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/docs/trust/plugin-trust-draft.md +0 -0
  73. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/docs/trust/skill-trust-local.md +0 -0
  74. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/fuzzers/manifest_fuzzer.py +0 -0
  75. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/requirements.txt +0 -0
  76. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/schemas/plugin-quality.v1.json +0 -0
  77. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/schemas/scan-result.v1.json +0 -0
  78. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/schemas/verify-result.v1.json +0 -0
  79. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/__init__.py +0 -0
  80. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/action_runner.py +0 -0
  81. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/argparse_utils.py +0 -0
  82. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/checks/__init__.py +0 -0
  83. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/checks/best_practices.py +0 -0
  84. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/checks/claude.py +0 -0
  85. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/checks/code_quality.py +0 -0
  86. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/checks/ecosystem_common.py +0 -0
  87. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/checks/gemini.py +0 -0
  88. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/checks/manifest.py +0 -0
  89. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/checks/manifest_support.py +0 -0
  90. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/checks/marketplace.py +0 -0
  91. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/checks/mcp_security.py +0 -0
  92. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/checks/opencode.py +0 -0
  93. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/checks/operational_security.py +0 -0
  94. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/checks/security.py +0 -0
  95. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/checks/skill_security.py +0 -0
  96. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/cli.py +0 -0
  97. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/cli_ui.py +0 -0
  98. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/config.py +0 -0
  99. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/ecosystems/__init__.py +0 -0
  100. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/ecosystems/base.py +0 -0
  101. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/ecosystems/claude.py +0 -0
  102. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/ecosystems/codex.py +0 -0
  103. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/ecosystems/detect.py +0 -0
  104. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/ecosystems/gemini.py +0 -0
  105. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/ecosystems/opencode.py +0 -0
  106. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/ecosystems/registry.py +0 -0
  107. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/ecosystems/types.py +0 -0
  108. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/github_reporting.py +0 -0
  109. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/__init__.py +0 -0
  110. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/access_graph_events.py +0 -0
  111. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/adapters/__init__.py +0 -0
  112. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/adapters/antigravity.py +0 -0
  113. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/adapters/base.py +0 -0
  114. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/adapters/claude_code.py +0 -0
  115. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/adapters/cloud_identity.py +0 -0
  116. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/adapters/codex.py +0 -0
  117. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/adapters/copilot.py +0 -0
  118. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/adapters/cursor.py +0 -0
  119. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/adapters/gemini.py +0 -0
  120. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/adapters/hermes.py +0 -0
  121. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/adapters/mcp_servers.py +0 -0
  122. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/adapters/openclaw.py +0 -0
  123. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/adapters/openclaw_config.py +0 -0
  124. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/adapters/openclaw_support.py +0 -0
  125. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/adapters/opencode.py +0 -0
  126. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/adapters/opencode_artifacts.py +0 -0
  127. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/advisory_model.py +0 -0
  128. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/approvals.py +0 -0
  129. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/bridge/__init__.py +0 -0
  130. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/capabilities.py +0 -0
  131. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/cli/__init__.py +0 -0
  132. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/cli/approval_commands.py +0 -0
  133. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/cli/bootstrap.py +0 -0
  134. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/cli/commands.py +0 -0
  135. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/cli/connect_flow.py +0 -0
  136. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/cli/install_commands.py +0 -0
  137. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/cli/product.py +0 -0
  138. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/cli/prompt.py +0 -0
  139. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/cli/update_commands.py +0 -0
  140. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/codex_config.py +0 -0
  141. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/consumer/__init__.py +0 -0
  142. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/consumer/service.py +0 -0
  143. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/daemon/__init__.py +0 -0
  144. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/daemon/client.py +0 -0
  145. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/daemon/manager.py +0 -0
  146. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/daemon/server.py +0 -0
  147. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/daemon/static/apple-touch-icon.png +0 -0
  148. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/daemon/static/assets/guard-dashboard.js +0 -0
  149. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/daemon/static/assets/index.css +0 -0
  150. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Icon_Dark.png +0 -0
  151. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Whole.png +0 -0
  152. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/daemon/static/favicon-16x16.png +0 -0
  153. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/daemon/static/favicon-32x32.png +0 -0
  154. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/daemon/static/favicon.ico +0 -0
  155. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/daemon/static/index.html +0 -0
  156. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/edge_events.py +0 -0
  157. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/incident.py +0 -0
  158. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/launcher.py +0 -0
  159. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/mcp_tool_calls.py +0 -0
  160. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/models.py +0 -0
  161. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/policy/__init__.py +0 -0
  162. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/policy/engine.py +0 -0
  163. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/protect.py +0 -0
  164. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/proxy/__init__.py +0 -0
  165. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/proxy/remote.py +0 -0
  166. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/proxy/runtime_mcp.py +0 -0
  167. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/proxy/stdio.py +0 -0
  168. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/receipts/__init__.py +0 -0
  169. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/receipts/manager.py +0 -0
  170. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/redaction.py +0 -0
  171. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/risk.py +0 -0
  172. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/runtime/__init__.py +0 -0
  173. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/runtime/actions.py +0 -0
  174. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/runtime/data_flow.py +0 -0
  175. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/runtime/data_flow_rules.py +0 -0
  176. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/runtime/data_flow_variables.py +0 -0
  177. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/runtime/decisions.py +0 -0
  178. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/runtime/detectors.py +0 -0
  179. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/runtime/mcp_protection.py +0 -0
  180. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/runtime/prompt_injection.py +0 -0
  181. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/runtime/runner.py +0 -0
  182. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/runtime/safe_decode.py +0 -0
  183. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/runtime/secret_file_requests.py +0 -0
  184. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/runtime/secret_sensitivity.py +0 -0
  185. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/runtime/secret_sources.py +0 -0
  186. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/runtime/shell_commands.py +0 -0
  187. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/runtime/signals.py +0 -0
  188. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/runtime/skill_protection.py +0 -0
  189. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/runtime/supply_chain.py +0 -0
  190. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/runtime/surface_server.py +0 -0
  191. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/runtime/temp_files.py +0 -0
  192. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/schemas/__init__.py +0 -0
  193. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/schemas/consumer_mode.py +0 -0
  194. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/schemas/guard_event_v1.py +0 -0
  195. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/schemas/surface_server.py +0 -0
  196. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/shims.py +0 -0
  197. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/store.py +0 -0
  198. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/store_connect.py +0 -0
  199. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/guard/types.py +0 -0
  200. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/integrations/__init__.py +0 -0
  201. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/integrations/cisco_mcp_scanner.py +0 -0
  202. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/integrations/cisco_skill_scanner.py +0 -0
  203. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/lint_fixes.py +0 -0
  204. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/marketplace_support.py +0 -0
  205. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/models.py +0 -0
  206. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/path_support.py +0 -0
  207. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/policy.py +0 -0
  208. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/quality_artifact.py +0 -0
  209. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/repo_detect.py +0 -0
  210. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/reporting.py +0 -0
  211. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/rules/__init__.py +0 -0
  212. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/rules/registry.py +0 -0
  213. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/rules/specs.py +0 -0
  214. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/scanner.py +0 -0
  215. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/submission.py +0 -0
  216. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/suppressions.py +0 -0
  217. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/trust_domain_scoring.py +0 -0
  218. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/trust_helpers.py +0 -0
  219. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/trust_mcp_scoring.py +0 -0
  220. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/trust_models.py +0 -0
  221. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/trust_plugin_scoring.py +0 -0
  222. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/trust_scoring.py +0 -0
  223. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/trust_skill_scoring.py +0 -0
  224. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/trust_specs.py +0 -0
  225. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/src/codex_plugin_scanner/verification.py +0 -0
  226. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/__init__.py +0 -0
  227. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/conftest.py +0 -0
  228. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/__init__.py +0 -0
  229. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/bad-plugin/.codex-plugin/plugin.json +0 -0
  230. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/bad-plugin/.mcp.json +0 -0
  231. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/bad-plugin/secrets.js +0 -0
  232. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/claude-plugin-good/.claude-plugin/plugin.json +0 -0
  233. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/claude-plugin-good/LICENSE +0 -0
  234. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/claude-plugin-good/README.md +0 -0
  235. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/claude-plugin-good/SECURITY.md +0 -0
  236. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/claude-plugin-good/hooks/hooks.json +0 -0
  237. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/claude-plugin-good/skills/example/SKILL.md +0 -0
  238. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/code-quality-bad/evil.js +0 -0
  239. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/code-quality-bad/inject.js +0 -0
  240. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/gemini-extension-good/GEMINI.md +0 -0
  241. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/gemini-extension-good/LICENSE +0 -0
  242. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/gemini-extension-good/README.md +0 -0
  243. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/gemini-extension-good/SECURITY.md +0 -0
  244. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/gemini-extension-good/commands/hello.toml +0 -0
  245. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/gemini-extension-good/gemini-extension.json +0 -0
  246. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/good-plugin/.codex-plugin/plugin.json +0 -0
  247. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/good-plugin/.codexignore +0 -0
  248. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/good-plugin/LICENSE +0 -0
  249. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/good-plugin/README.md +0 -0
  250. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/good-plugin/SECURITY.md +0 -0
  251. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/good-plugin/assets/icon.svg +0 -0
  252. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/good-plugin/assets/logo.svg +0 -0
  253. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/good-plugin/assets/screenshot.svg +0 -0
  254. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/good-plugin/skills/example/SKILL.md +0 -0
  255. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/guard-codex-malicious-mcp/.codex/config.toml +0 -0
  256. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/hermes-plugin-evil/config.yaml +0 -0
  257. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/hermes-plugin-evil/mcp_servers.json +0 -0
  258. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/hermes-plugin-evil/skills/security/malicious/SKILL.md +0 -0
  259. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/SKILL.md +0 -0
  260. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/references/api-setup.md +0 -0
  261. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/scripts/deploy.sh +0 -0
  262. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/hermes-plugin-evil/skills/utils/benign/SKILL.md +0 -0
  263. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/malformed-json/.codex-plugin/plugin.json +0 -0
  264. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/malicious-skill-plugin/.codex-plugin/plugin.json +0 -0
  265. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/malicious-skill-plugin/.codexignore +0 -0
  266. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/malicious-skill-plugin/LICENSE +0 -0
  267. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/malicious-skill-plugin/README.md +0 -0
  268. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/malicious-skill-plugin/SECURITY.md +0 -0
  269. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/malicious-skill-plugin/skills/leaky-skill/SKILL.md +0 -0
  270. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/mcp-canary-server.py +0 -0
  271. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/minimal-plugin/.codex-plugin/plugin.json +0 -0
  272. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/missing-fields/.codex-plugin/plugin.json +0 -0
  273. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/mit-license/LICENSE +0 -0
  274. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/multi-ecosystem-repo/codex-plugin/.codex-plugin/plugin.json +0 -0
  275. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/multi-ecosystem-repo/codex-plugin/LICENSE +0 -0
  276. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/multi-ecosystem-repo/codex-plugin/README.md +0 -0
  277. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/multi-ecosystem-repo/codex-plugin/SECURITY.md +0 -0
  278. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/multi-ecosystem-repo/gemini-ext/README.md +0 -0
  279. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/multi-ecosystem-repo/gemini-ext/gemini-extension.json +0 -0
  280. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/multi-plugin-repo/.agents/plugins/marketplace.json +0 -0
  281. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codex-plugin/plugin.json +0 -0
  282. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codexignore +0 -0
  283. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/LICENSE +0 -0
  284. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/README.md +0 -0
  285. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/SECURITY.md +0 -0
  286. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/skills/example/SKILL.md +0 -0
  287. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/.codex-plugin/plugin.json +0 -0
  288. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/skills/example/SKILL.md +0 -0
  289. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/no-version/.codex-plugin/plugin.json +0 -0
  290. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/opencode-good/.opencode/commands/hello.md +0 -0
  291. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/opencode-good/.opencode/plugins/example.ts +0 -0
  292. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/opencode-good/LICENSE +0 -0
  293. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/opencode-good/README.md +0 -0
  294. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/opencode-good/SECURITY.md +0 -0
  295. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/opencode-good/opencode.jsonc +0 -0
  296. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/skills-missing-dir/.codex-plugin/plugin.json +0 -0
  297. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/skills-no-frontmatter/.codex-plugin/plugin.json +0 -0
  298. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/skills-no-frontmatter/skills/bad-skill/SKILL.md +0 -0
  299. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/supply-chain/benign-npm-package.json +0 -0
  300. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/supply-chain/benign-pnpm-package.json +0 -0
  301. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/supply-chain/benign-pyproject.toml +0 -0
  302. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/supply-chain/malicious-Dockerfile +0 -0
  303. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/supply-chain/malicious-action.yml +0 -0
  304. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/supply-chain/malicious-npm-package.json +0 -0
  305. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/supply-chain/malicious-setup.py +0 -0
  306. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/with-marketplace/.codex-plugin/plugin.json +0 -0
  307. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/with-marketplace/marketplace-broken.json +0 -0
  308. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/fixtures/with-marketplace/marketplace.json +0 -0
  309. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test-trust-scoring.py +0 -0
  310. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test-trust-specs.py +0 -0
  311. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_action_runner.py +0 -0
  312. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_best_practices.py +0 -0
  313. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_cisco_install_surfaces.py +0 -0
  314. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_cli.py +0 -0
  315. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_code_quality.py +0 -0
  316. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_config.py +0 -0
  317. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_coverage_remaining.py +0 -0
  318. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_ecosystems.py +0 -0
  319. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_edge_cases.py +0 -0
  320. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_final_coverage.py +0 -0
  321. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_access_graph.py +0 -0
  322. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_approvals.py +0 -0
  323. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_bootstrap.py +0 -0
  324. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_capabilities.py +0 -0
  325. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_claude_adapter.py +0 -0
  326. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_cli.py +0 -0
  327. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_cloud_local_sync.py +0 -0
  328. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_codex_e2e.py +0 -0
  329. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_codex_install.py +0 -0
  330. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_codex_proxy.py +0 -0
  331. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_connect_flow.py +0 -0
  332. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_consumer_mode.py +0 -0
  333. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_copilot_adapter.py +0 -0
  334. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_copilot_proxy.py +0 -0
  335. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_daemon_manager.py +0 -0
  336. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_data_flow.py +0 -0
  337. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_event_schema_v1.py +0 -0
  338. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_events.py +0 -0
  339. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_launch_env.py +0 -0
  340. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_mcp_protection.py +0 -0
  341. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_opencode_proxy.py +0 -0
  342. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_product_flow.py +0 -0
  343. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_prompt_injection.py +0 -0
  344. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_protect.py +0 -0
  345. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_risk.py +0 -0
  346. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_runtime.py +0 -0
  347. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_runtime_action_harnesses.py +0 -0
  348. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_runtime_actions.py +0 -0
  349. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_runtime_decisions.py +0 -0
  350. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_runtime_detectors.py +0 -0
  351. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_runtime_signals.py +0 -0
  352. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_safe_decode.py +0 -0
  353. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_skill_protection.py +0 -0
  354. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_store_migrations.py +0 -0
  355. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_supply_chain.py +0 -0
  356. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_surface_server.py +0 -0
  357. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_guard_verdicts.py +0 -0
  358. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_hermes_adapter.py +0 -0
  359. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_integration.py +0 -0
  360. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_lint_fixes.py +0 -0
  361. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_live_cisco_smoke.py +0 -0
  362. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_manifest.py +0 -0
  363. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_marketplace.py +0 -0
  364. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_mcp_security.py +0 -0
  365. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_openclaw_adapter.py +0 -0
  366. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_operational_security.py +0 -0
  367. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_policy.py +0 -0
  368. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_quality_artifact.py +0 -0
  369. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_rule_registry.py +0 -0
  370. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_scanner.py +0 -0
  371. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_schema_contracts.py +0 -0
  372. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_security.py +0 -0
  373. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_security_ops.py +0 -0
  374. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_skill_security.py +0 -0
  375. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_submission.py +0 -0
  376. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_trust_scoring.py +0 -0
  377. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_trust_specs.py +0 -0
  378. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_verification.py +0 -0
  379. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/tests/test_versioning.py +0 -0
  380. {plugin_scanner-2.0.123 → plugin_scanner-2.0.125}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plugin-scanner
3
- Version: 2.0.123
3
+ Version: 2.0.125
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.123"
7
+ version = "2.0.125"
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.123"
7
+ version = "2.0.125"
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"
@@ -576,13 +576,14 @@ def _render_managed_install(console: Console, payload: dict[str, object]) -> Non
576
576
  skill_scan = _coerce_dict_list(payload.get("skill_scan"))
577
577
  supply_chain_risks = _coerce_dict_list(payload.get("supply_chain_risks"))
578
578
  safe_decode_risks = _coerce_dict_list(payload.get("safe_decode_risks"))
579
+ sandbox_analysis = _coerce_dict_list(payload.get("sandbox_analysis"))
579
580
  managed_install = payload.get("managed_install")
580
581
  if isinstance(managed_install, dict):
581
582
  _render_single_managed_install(console, managed_install)
582
583
  else:
583
584
  managed_installs = _coerce_dict_list(payload.get("managed_installs"))
584
585
  if not managed_installs:
585
- if not skill_scan and not supply_chain_risks and not safe_decode_risks:
586
+ if not skill_scan and not supply_chain_risks and not safe_decode_risks and not sandbox_analysis:
586
587
  _render_fallback(console, payload)
587
588
  return
588
589
  else:
@@ -603,6 +604,8 @@ def _render_managed_install(console: Console, payload: dict[str, object]) -> Non
603
604
  _render_supply_chain_risk_results(console, supply_chain_risks)
604
605
  if safe_decode_risks:
605
606
  _render_safe_decode_results(console, safe_decode_risks)
607
+ if sandbox_analysis:
608
+ _render_sandbox_results(console, sandbox_analysis)
606
609
 
607
610
 
608
611
  def _render_supply_chain_risk_results(console: Console, supply_chain_risks: list[dict[str, object]]) -> None:
@@ -654,6 +657,39 @@ def _render_safe_decode_results(console: Console, safe_decode_risks: list[dict[s
654
657
  )
655
658
 
656
659
 
660
+ def _render_sandbox_results(console: Console, sandbox_analysis: list[dict[str, object]]) -> None:
661
+ table = Table(box=box.SIMPLE_HEAVY, show_header=True, expand=True)
662
+ table.add_column("Signal", overflow="fold")
663
+ table.add_column("Writes", justify="right", no_wrap=True)
664
+ table.add_column("Network", justify="right", no_wrap=True)
665
+ table.add_column("Processes", justify="right", no_wrap=True)
666
+ table.add_column("Timed out", no_wrap=True)
667
+ table.add_column("Exit code", no_wrap=True)
668
+ for entry in sandbox_analysis:
669
+ signals = _coerce_string_list(entry.get("signals_detected"))
670
+ signal_text = ", ".join(signals) if signals else "—"
671
+ writes = _coerce_string_list(entry.get("writes"))
672
+ network = _coerce_string_list(entry.get("network_attempts"))
673
+ processes = _coerce_string_list(entry.get("process_attempts"))
674
+ timed_out = bool(entry.get("timed_out"))
675
+ exit_code = entry.get("exit_code")
676
+ table.add_row(
677
+ signal_text,
678
+ str(len(writes)),
679
+ str(len(network)),
680
+ str(len(processes)),
681
+ "[red]yes[/red]" if timed_out else "no",
682
+ str(exit_code) if exit_code is not None else "—",
683
+ )
684
+ console.print(
685
+ Panel(
686
+ table,
687
+ title=f"[bold magenta]Sandbox analysis — {len(sandbox_analysis)} result(s)[/bold magenta]",
688
+ border_style="magenta",
689
+ )
690
+ )
691
+
692
+
657
693
  def _render_skill_scan_results(console: Console, skill_scan: list[dict[str, object]]) -> None:
658
694
  table = Table(box=box.SIMPLE_HEAVY, show_header=True, expand=True)
659
695
  table.add_column("Skill file", overflow="fold")
@@ -248,6 +248,7 @@ class GuardConfig:
248
248
  runtime_detector_timeout_ms: int = 50
249
249
  runtime_detector_debug_trace: bool = False
250
250
  runtime_detector_disabled_ids: tuple[str, ...] = ()
251
+ sandbox_analysis: str = "off"
251
252
  risk_actions: dict[str, GuardAction] | None = None
252
253
  harness_risk_actions: dict[str, dict[str, GuardAction]] | None = None
253
254
  harness_actions: dict[str, GuardAction] | None = None
@@ -328,6 +329,7 @@ def load_guard_config(guard_home: Path, workspace: Path | None = None) -> GuardC
328
329
  runtime_detector_timeout_ms=_coerce_loaded_positive_int(merged.get("runtime_detector_timeout_ms", 50), 50),
329
330
  runtime_detector_debug_trace=_coerce_loaded_bool(merged.get("runtime_detector_debug_trace", False)),
330
331
  runtime_detector_disabled_ids=_coerce_loaded_string_tuple(merged.get("runtime_detector_disabled_ids")),
332
+ sandbox_analysis=_coerce_sandbox_analysis(merged.get("sandbox_analysis", "off")),
331
333
  harness_actions=_coerce_action_map(merged.get("harnesses")),
332
334
  publisher_actions=_coerce_action_map(merged.get("publishers")),
333
335
  artifact_actions=_coerce_action_map(merged.get("artifacts")),
@@ -438,6 +440,15 @@ def _coerce_loaded_string_tuple(value: object) -> tuple[str, ...]:
438
440
  return tuple(item for item in value if isinstance(item, str) and item.strip())
439
441
 
440
442
 
443
+ _VALID_SANDBOX_MODES = {"off", "suspicious", "strict"}
444
+
445
+
446
+ def _coerce_sandbox_analysis(value: object) -> str:
447
+ if isinstance(value, str) and value in _VALID_SANDBOX_MODES:
448
+ return value
449
+ return "off"
450
+
451
+
441
452
  def _coerce_risk_action_payload(value: object) -> dict[str, GuardAction]:
442
453
  if not isinstance(value, dict):
443
454
  raise ValueError("Risk actions must be a table.")
@@ -0,0 +1,349 @@
1
+ """Static-first sandbox analysis for Guard runtime.
2
+
3
+ Provides dataclasses and a pure-Python sandbox runner that executes scripts
4
+ inside a temporary workspace with audited subprocess calls, hard resource
5
+ limits, and network-attempt detection. The sandbox never touches user secret
6
+ paths and always returns a result even when the script fails.
7
+
8
+ Supported static-first paths: shell, Node, Python, package scripts, MCP smoke.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import contextlib
14
+ import os
15
+ import re
16
+ import signal
17
+ import subprocess
18
+ import sys
19
+ import tempfile
20
+ import time
21
+ from dataclasses import dataclass, field
22
+ from pathlib import Path
23
+ from typing import Literal
24
+
25
+ _RESOURCE_AVAILABLE = sys.platform != "win32"
26
+ if _RESOURCE_AVAILABLE:
27
+ import resource
28
+
29
+ SandboxLanguage = Literal["shell", "node", "python", "package", "mcp_smoke"]
30
+ EnvPolicy = Literal["clean", "passthrough", "minimal"]
31
+ SandboxAnalysisMode = Literal["off", "suspicious", "strict"]
32
+
33
+ _DEFAULT_CPU_SECONDS: float = 5.0
34
+ _DEFAULT_MEMORY_BYTES: int = 512 * 1024 * 1024
35
+ _DEFAULT_MAX_PROCESSES: int = 32
36
+ _DEFAULT_TIMEOUT_SECONDS: float = 10.0
37
+
38
+ _SECRET_PATH_PATTERNS: tuple[re.Pattern[str], ...] = (
39
+ re.compile(r"\.ssh[/\\]", re.IGNORECASE),
40
+ re.compile(r"\.aws[/\\]", re.IGNORECASE),
41
+ re.compile(r"\.gnupg[/\\]", re.IGNORECASE),
42
+ re.compile(r"\.env\b", re.IGNORECASE),
43
+ re.compile(r"id_rsa|id_ed25519|id_ecdsa", re.IGNORECASE),
44
+ re.compile(r"credentials|secrets\.json|token\.json", re.IGNORECASE),
45
+ re.compile(r"keychain|keyring", re.IGNORECASE),
46
+ )
47
+
48
+ _NETWORK_SYSCALL_PATTERNS: tuple[re.Pattern[str], ...] = (
49
+ re.compile(r"\bcurl\b", re.IGNORECASE),
50
+ re.compile(r"\bwget\b", re.IGNORECASE),
51
+ re.compile(r"\bfetch\s*\(", re.IGNORECASE),
52
+ re.compile(r"\baxios\b", re.IGNORECASE),
53
+ re.compile(r"\brequests\.(get|post|put|delete|patch)\s*\(", re.IGNORECASE),
54
+ re.compile(r"http[s]?://", re.IGNORECASE),
55
+ re.compile(r"socket\.connect\s*\(", re.IGNORECASE),
56
+ )
57
+
58
+
59
+ @dataclass(frozen=True)
60
+ class SandboxRequest:
61
+ """Specification for a sandboxed script execution."""
62
+
63
+ language: SandboxLanguage
64
+ command: str
65
+ files: dict[str, str] = field(default_factory=dict)
66
+ cwd: str | None = None
67
+ env_policy: EnvPolicy = "clean"
68
+ cpu_seconds: float = _DEFAULT_CPU_SECONDS
69
+ memory_bytes: int = _DEFAULT_MEMORY_BYTES
70
+ max_processes: int = _DEFAULT_MAX_PROCESSES
71
+ timeout_seconds: float = _DEFAULT_TIMEOUT_SECONDS
72
+
73
+
74
+ @dataclass
75
+ class SandboxResult:
76
+ """Outcome of a sandboxed script execution."""
77
+
78
+ exit_code: int | None
79
+ stdout: str
80
+ stderr: str
81
+ timed_out: bool
82
+ writes: list[str] = field(default_factory=list)
83
+ network_attempts: list[str] = field(default_factory=list)
84
+ process_attempts: list[str] = field(default_factory=list)
85
+ secret_read_attempts: list[str] = field(default_factory=list)
86
+ signals_detected: list[str] = field(default_factory=list)
87
+ duration_ms: float = 0.0
88
+ failure_safe: bool = False
89
+
90
+ def to_dict(self) -> dict[str, object]:
91
+ return {
92
+ "exit_code": self.exit_code,
93
+ "stdout": self.stdout[:4096],
94
+ "stderr": self.stderr[:4096],
95
+ "timed_out": self.timed_out,
96
+ "writes": self.writes,
97
+ "network_attempts": self.network_attempts,
98
+ "process_attempts": self.process_attempts,
99
+ "secret_read_attempts": self.secret_read_attempts,
100
+ "signals_detected": self.signals_detected,
101
+ "duration_ms": round(self.duration_ms, 2),
102
+ "failure_safe": self.failure_safe,
103
+ }
104
+
105
+
106
+ def _is_secret_path(path: str) -> bool:
107
+ return any(pat.search(path) for pat in _SECRET_PATH_PATTERNS)
108
+
109
+
110
+ def _detect_network_attempts(text: str) -> list[str]:
111
+ found: list[str] = []
112
+ for pat in _NETWORK_SYSCALL_PATTERNS:
113
+ for m in pat.finditer(text):
114
+ snippet = text[max(0, m.start() - 10) : m.end() + 40].strip()
115
+ found.append(snippet[:80])
116
+ return found
117
+
118
+
119
+ def _build_env(policy: EnvPolicy) -> dict[str, str]:
120
+ if policy == "passthrough":
121
+ return dict(os.environ)
122
+ if policy == "minimal":
123
+ minimal = {}
124
+ for key in ("PATH", "HOME", "TMPDIR", "LANG", "LC_ALL", "TERM"):
125
+ val = os.environ.get(key)
126
+ if val:
127
+ minimal[key] = val
128
+ return minimal
129
+ return {
130
+ "PATH": os.environ.get("PATH", "/usr/local/bin:/usr/bin:/bin"),
131
+ "HOME": "/tmp",
132
+ "TMPDIR": "/tmp",
133
+ }
134
+
135
+
136
+ def _redact_env(env: dict[str, str]) -> dict[str, str]:
137
+ redact_keys = re.compile(
138
+ r"TOKEN|SECRET|KEY|PASSWORD|CREDENTIAL|AUTH|NPM_TOKEN|NODE_AUTH|AWS_",
139
+ re.IGNORECASE,
140
+ )
141
+ return {k: ("[REDACTED]" if redact_keys.search(k) else v) for k, v in env.items()}
142
+
143
+
144
+ def _apply_resource_limits(cpu_seconds: float, memory_bytes: int, max_processes: int) -> None:
145
+ if not _RESOURCE_AVAILABLE:
146
+ return
147
+ with contextlib.suppress(OSError, ValueError):
148
+ cpu_int = max(1, int(cpu_seconds))
149
+ resource.setrlimit(resource.RLIMIT_CPU, (cpu_int, cpu_int))
150
+ with contextlib.suppress(OSError, ValueError):
151
+ resource.setrlimit(resource.RLIMIT_DATA, (memory_bytes, memory_bytes))
152
+ with contextlib.suppress(OSError, ValueError):
153
+ soft, hard = resource.getrlimit(resource.RLIMIT_NPROC)
154
+ effective = min(max_processes, soft) if soft > 0 else max_processes
155
+ new_hard = hard if hard <= 0 else max(effective, hard)
156
+ resource.setrlimit(resource.RLIMIT_NPROC, (effective, new_hard))
157
+
158
+
159
+ def _write_sandbox_files(workspace: Path, files: dict[str, str]) -> None:
160
+ resolved_workspace = workspace.resolve()
161
+ for rel_path, content in files.items():
162
+ if _is_secret_path(rel_path):
163
+ continue
164
+ target = (workspace / rel_path).resolve()
165
+ try:
166
+ target.relative_to(resolved_workspace)
167
+ except ValueError:
168
+ continue
169
+ target.parent.mkdir(parents=True, exist_ok=True)
170
+ target.write_text(content, encoding="utf-8")
171
+
172
+
173
+ def _language_argv(language: SandboxLanguage, command: str, workspace: Path) -> list[str]:
174
+ if language == "shell":
175
+ return ["/bin/sh", "-c", command]
176
+ if language == "node":
177
+ return ["node", "-e", command]
178
+ if language == "python":
179
+ return ["python3", "-c", command]
180
+ if language == "package":
181
+ return ["/bin/sh", "-c", command]
182
+ if language == "mcp_smoke":
183
+ return ["/bin/sh", "-c", command]
184
+ return ["/bin/sh", "-c", command]
185
+
186
+
187
+ def _scan_writes(workspace: Path, before: set[str], before_mtimes: dict[str, float]) -> list[str]:
188
+ after_new: set[str] = set()
189
+ modified: list[str] = []
190
+ for p in workspace.rglob("*"):
191
+ if p.is_file():
192
+ rel = str(p.relative_to(workspace))
193
+ if rel not in before:
194
+ after_new.add(rel)
195
+ elif p.stat().st_mtime > before_mtimes.get(rel, 0):
196
+ modified.append(rel)
197
+ return sorted(after_new | set(modified))
198
+
199
+
200
+ def _audit_combined_output(stdout: str, stderr: str) -> tuple[list[str], list[str]]:
201
+ """Scan combined stdout+stderr for suspicious literal patterns.
202
+
203
+ This is best-effort heuristic detection of plaintext patterns. It does not
204
+ decode base64, escaped strings, or other obfuscated forms — those are
205
+ handled upstream by the safe-decode layer before the command reaches the sandbox.
206
+ """
207
+ combined = stdout + "\n" + stderr
208
+ network_attempts = _detect_network_attempts(combined)
209
+ process_attempts: list[str] = []
210
+ for pat in (re.compile(r"\bsubprocess\b"), re.compile(r"\bexecl\b|\bexecv\b|\bexecve\b")):
211
+ for m in pat.finditer(combined):
212
+ snippet = combined[max(0, m.start() - 5) : m.end() + 40].strip()
213
+ process_attempts.append(snippet[:80])
214
+ return network_attempts, process_attempts
215
+
216
+
217
+ def run_sandbox(request: SandboxRequest, *, analysis_mode: SandboxAnalysisMode = "suspicious") -> SandboxResult:
218
+ """Execute a script in a sandboxed temporary workspace.
219
+
220
+ Always returns a SandboxResult. If the sandbox itself fails (missing
221
+ interpreter, permissions issue), failure_safe=True is set and the
222
+ result contains the error detail.
223
+ """
224
+ if analysis_mode == "off":
225
+ return SandboxResult(
226
+ exit_code=None,
227
+ stdout="",
228
+ stderr="",
229
+ timed_out=False,
230
+ failure_safe=False,
231
+ )
232
+
233
+ network_pre = _detect_network_attempts(request.command)
234
+ if analysis_mode == "suspicious" and not network_pre:
235
+ static_result = _static_only_analysis(request)
236
+ if static_result is not None:
237
+ return static_result
238
+
239
+ with tempfile.TemporaryDirectory(prefix="guard-sandbox-") as tmpdir:
240
+ workspace = Path(tmpdir)
241
+ _write_sandbox_files(workspace, request.files)
242
+
243
+ before_files: set[str] = set()
244
+ before_mtimes: dict[str, float] = {}
245
+ for p in workspace.rglob("*"):
246
+ if p.is_file():
247
+ rel = str(p.relative_to(workspace))
248
+ before_files.add(rel)
249
+ before_mtimes[rel] = p.stat().st_mtime
250
+
251
+ env = _build_env(request.env_policy)
252
+ argv = _language_argv(request.language, request.command, workspace)
253
+
254
+ cpu_s = request.cpu_seconds
255
+ mem_b = request.memory_bytes
256
+ max_p = request.max_processes
257
+
258
+ def _preexec() -> None:
259
+ _apply_resource_limits(cpu_s, mem_b, max_p)
260
+
261
+ effective_cwd = str(Path(request.cwd).resolve()) if request.cwd else str(workspace)
262
+ start = time.monotonic()
263
+ try:
264
+ proc = subprocess.Popen(
265
+ argv,
266
+ cwd=effective_cwd,
267
+ env=env,
268
+ stdout=subprocess.PIPE,
269
+ stderr=subprocess.PIPE,
270
+ preexec_fn=_preexec,
271
+ start_new_session=True,
272
+ close_fds=True,
273
+ )
274
+ try:
275
+ raw_out, raw_err = proc.communicate(timeout=request.timeout_seconds)
276
+ timed_out = False
277
+ exit_code: int | None = proc.returncode
278
+ except subprocess.TimeoutExpired:
279
+ with contextlib.suppress(OSError):
280
+ os.killpg(os.getpgid(proc.pid), signal.SIGKILL)
281
+ raw_out, raw_err = proc.communicate()
282
+ timed_out = True
283
+ exit_code = None
284
+
285
+ duration_ms = (time.monotonic() - start) * 1000.0
286
+ stdout = raw_out.decode("utf-8", errors="replace")
287
+ stderr = raw_err.decode("utf-8", errors="replace")
288
+ writes = _scan_writes(workspace, before_files, before_mtimes)
289
+ network_attempts, process_attempts = _audit_combined_output(stdout, stderr)
290
+ network_attempts = list(dict.fromkeys(network_pre + network_attempts))
291
+
292
+ signals_detected: list[str] = []
293
+ if timed_out:
294
+ signals_detected.append("sandbox.timeout")
295
+ if network_attempts:
296
+ signals_detected.append("sandbox.network-attempt")
297
+ if writes:
298
+ signals_detected.append("sandbox.unexpected-write")
299
+
300
+ return SandboxResult(
301
+ exit_code=exit_code,
302
+ stdout=stdout,
303
+ stderr=stderr,
304
+ timed_out=timed_out,
305
+ writes=writes,
306
+ network_attempts=network_attempts,
307
+ process_attempts=process_attempts,
308
+ secret_read_attempts=[],
309
+ signals_detected=signals_detected,
310
+ duration_ms=duration_ms,
311
+ failure_safe=False,
312
+ )
313
+
314
+ except Exception as exc:
315
+ duration_ms = (time.monotonic() - start) * 1000.0
316
+ return SandboxResult(
317
+ exit_code=None,
318
+ stdout="",
319
+ stderr=f"sandbox-error: {exc}",
320
+ timed_out=False,
321
+ writes=[],
322
+ network_attempts=network_pre,
323
+ process_attempts=[],
324
+ secret_read_attempts=[],
325
+ signals_detected=["sandbox.execution-error"],
326
+ duration_ms=duration_ms,
327
+ failure_safe=True,
328
+ )
329
+
330
+
331
+ def _static_only_analysis(request: SandboxRequest) -> SandboxResult | None:
332
+ """Return a static result if the command contains no execution-worthy content."""
333
+ combined = request.command + "\n" + "\n".join(request.files.values())
334
+ network_attempts = _detect_network_attempts(combined)
335
+ if not network_attempts:
336
+ return SandboxResult(
337
+ exit_code=None,
338
+ stdout="",
339
+ stderr="",
340
+ timed_out=False,
341
+ writes=[],
342
+ network_attempts=[],
343
+ process_attempts=[],
344
+ secret_read_attempts=[],
345
+ signals_detected=[],
346
+ duration_ms=0.0,
347
+ failure_safe=False,
348
+ )
349
+ return None
@@ -175,6 +175,10 @@ def list_approval_requests(
175
175
  status: str | None = "pending",
176
176
  harness: str | None = None,
177
177
  limit: int | None = 50,
178
+ before_cursor: str | None = None,
179
+ search: str | None = None,
180
+ created_after: str | None = None,
181
+ created_before: str | None = None,
178
182
  ) -> list[dict[str, object]]:
179
183
  clauses = []
180
184
  params: list[object] = []
@@ -184,6 +188,24 @@ def list_approval_requests(
184
188
  if harness is not None:
185
189
  clauses.append("harness = ?")
186
190
  params.append(harness)
191
+ if before_cursor is not None:
192
+ clauses.append("created_at < ?")
193
+ params.append(before_cursor)
194
+ if created_after is not None:
195
+ clauses.append("created_at > ?")
196
+ params.append(created_after)
197
+ if created_before is not None:
198
+ clauses.append("created_at < ?")
199
+ params.append(created_before)
200
+ if search is not None:
201
+ search_clause = (
202
+ "(lower(artifact_name) like lower(?)"
203
+ " or lower(artifact_id) like lower(?)"
204
+ " or lower(coalesce(risk_summary, '')) like lower(?))"
205
+ )
206
+ clauses.append(search_clause)
207
+ pattern = f"%{search}%"
208
+ params.extend([pattern, pattern, pattern])
187
209
  where_clause = f"where {' and '.join(clauses)}" if clauses else ""
188
210
  query = f"""
189
211
  select request_id, harness, artifact_id, artifact_name, artifact_type, artifact_hash, publisher, policy_action,
@@ -243,14 +265,22 @@ def resolve_approval_request(
243
265
  )
244
266
 
245
267
 
246
- def count_approval_requests(connection: sqlite3.Connection, *, status: str | None = "pending") -> int:
247
- if status is None:
248
- row = connection.execute("select count(*) as total from approval_requests").fetchone()
249
- else:
250
- row = connection.execute(
251
- "select count(*) as total from approval_requests where status = ?",
252
- (status,),
253
- ).fetchone()
268
+ def count_approval_requests(
269
+ connection: sqlite3.Connection,
270
+ *,
271
+ status: str | None = "pending",
272
+ harness: str | None = None,
273
+ ) -> int:
274
+ clauses = []
275
+ params: list[object] = []
276
+ if status is not None:
277
+ clauses.append("status = ?")
278
+ params.append(status)
279
+ if harness is not None:
280
+ clauses.append("harness = ?")
281
+ params.append(harness)
282
+ where_clause = f"where {' and '.join(clauses)}" if clauses else ""
283
+ row = connection.execute(f"select count(*) as total from approval_requests {where_clause}", params).fetchone()
254
284
  return int(row["total"]) if row is not None else 0
255
285
 
256
286
 
@@ -265,14 +295,14 @@ def _row_to_payload(row: sqlite3.Row) -> dict[str, object]:
265
295
  "publisher": row["publisher"],
266
296
  "policy_action": str(row["policy_action"]),
267
297
  "recommended_scope": str(row["recommended_scope"]),
268
- "changed_fields": json.loads(str(row["changed_fields_json"])),
298
+ "changed_fields": _safe_json_list(row["changed_fields_json"]),
269
299
  "source_scope": str(row["source_scope"]),
270
300
  "config_path": str(row["config_path"]),
271
301
  "workspace": row["workspace"],
272
302
  "launch_target": row["launch_target"],
273
303
  "transport": row["transport"],
274
304
  "risk_summary": row["risk_summary"],
275
- "risk_signals": json.loads(str(row["risk_signals_json"])),
305
+ "risk_signals": _safe_json_list(row["risk_signals_json"]),
276
306
  "artifact_label": row["artifact_label"],
277
307
  "source_label": row["source_label"],
278
308
  "trigger_summary": row["trigger_summary"],
@@ -292,6 +322,111 @@ def _row_to_payload(row: sqlite3.Row) -> dict[str, object]:
292
322
  }
293
323
 
294
324
 
325
+ def _safe_json_list(value: object) -> list[object]:
326
+ if value is None:
327
+ return []
328
+ try:
329
+ parsed = json.loads(str(value))
330
+ except (json.JSONDecodeError, TypeError, ValueError):
331
+ return []
332
+ return list(parsed) if isinstance(parsed, list) else []
333
+
334
+
335
+ def approval_index_statements() -> list[str]:
336
+ return [
337
+ "create index if not exists idx_approval_status_created on approval_requests(status, created_at desc)",
338
+ "create index if not exists idx_approval_harness_status on approval_requests(harness, status)",
339
+ "create index if not exists idx_approval_artifact_hash on approval_requests(artifact_hash)",
340
+ "create index if not exists idx_approval_workspace_status on approval_requests(workspace, status)",
341
+ "create index if not exists idx_approval_policy_action on approval_requests(policy_action)",
342
+ "create index if not exists idx_approval_resolution on approval_requests(resolution_action, resolved_at)",
343
+ ]
344
+
345
+
346
+ def bulk_resolve_approval_requests(
347
+ connection: sqlite3.Connection,
348
+ request_ids: list[str],
349
+ *,
350
+ resolution_action: str,
351
+ resolution_scope: str,
352
+ reason: str | None,
353
+ resolved_at: str,
354
+ ) -> None:
355
+ if not request_ids:
356
+ return
357
+ placeholders = ",".join("?" for _ in request_ids)
358
+ connection.execute(
359
+ f"""
360
+ update approval_requests
361
+ set status = 'resolved',
362
+ resolution_action = ?,
363
+ resolution_scope = ?,
364
+ reason = ?,
365
+ resolved_at = ?
366
+ where request_id in ({placeholders})
367
+ and status = 'pending'
368
+ """,
369
+ [resolution_action, resolution_scope, reason, resolved_at, *request_ids],
370
+ )
371
+
372
+
373
+ def clear_approval_requests_by_harness(connection: sqlite3.Connection, harness: str) -> int:
374
+ cursor = connection.execute(
375
+ "delete from approval_requests where harness = ? and status = 'resolved'",
376
+ (harness,),
377
+ )
378
+ return cursor.rowcount
379
+
380
+
381
+ def clear_approval_requests_by_workspace(connection: sqlite3.Connection, workspace: str) -> int:
382
+ cursor = connection.execute(
383
+ "delete from approval_requests where workspace = ? and status = 'resolved'",
384
+ (workspace,),
385
+ )
386
+ return cursor.rowcount
387
+
388
+
389
+ def clear_approval_requests_by_scope(connection: sqlite3.Connection, source_scope: str) -> int:
390
+ cursor = connection.execute(
391
+ "delete from approval_requests where source_scope = ? and status = 'resolved'",
392
+ (source_scope,),
393
+ )
394
+ return cursor.rowcount
395
+
396
+
397
+ def clear_resolved_approval_requests_before(connection: sqlite3.Connection, before_timestamp: str) -> int:
398
+ cursor = connection.execute(
399
+ "delete from approval_requests where status = 'resolved' and resolved_at < ?",
400
+ (before_timestamp,),
401
+ )
402
+ return cursor.rowcount
403
+
404
+
405
+ def compact_approval_requests(connection: sqlite3.Connection) -> int:
406
+ rows = connection.execute(
407
+ """
408
+ select artifact_id, max(created_at) as latest_created
409
+ from approval_requests
410
+ where status = 'resolved'
411
+ group by artifact_id
412
+ having count(*) > 1
413
+ """
414
+ ).fetchall()
415
+ total_removed = 0
416
+ for row in rows:
417
+ cursor = connection.execute(
418
+ """
419
+ delete from approval_requests
420
+ where artifact_id = ?
421
+ and status = 'resolved'
422
+ and created_at < ?
423
+ """,
424
+ (row["artifact_id"], row["latest_created"]),
425
+ )
426
+ total_removed += cursor.rowcount
427
+ return total_removed
428
+
429
+
295
430
  def _optional_json_object(value: object) -> dict[str, object] | None:
296
431
  if value is None:
297
432
  return None
@@ -1,3 +1,3 @@
1
1
  """Single source of truth for tool version."""
2
2
 
3
- __version__ = "2.0.123"
3
+ __version__ = "2.0.125"