plugin-scanner 2.0.127__tar.gz → 2.0.128__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 (390) hide show
  1. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/PKG-INFO +1 -1
  2. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/pyproject.toml +1 -1
  3. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/pyproject.toml.bak +1 -1
  4. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/cli/commands.py +54 -5
  5. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/daemon/server.py +6 -4
  6. plugin_scanner-2.0.128/src/codex_plugin_scanner/guard/runtime/advisory_escalation.py +65 -0
  7. plugin_scanner-2.0.128/src/codex_plugin_scanner/guard/runtime/advisory_matchers.py +164 -0
  8. plugin_scanner-2.0.128/src/codex_plugin_scanner/guard/runtime/threat_intel.py +243 -0
  9. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/store.py +9 -0
  10. plugin_scanner-2.0.128/src/codex_plugin_scanner/guard/store_threat_intel.py +209 -0
  11. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/version.py +1 -1
  12. plugin_scanner-2.0.128/tests/test_guard_advisory_escalation.py +123 -0
  13. plugin_scanner-2.0.128/tests/test_guard_threat_intel.py +581 -0
  14. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.clusterfuzzlite/Dockerfile +0 -0
  15. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.clusterfuzzlite/build.sh +0 -0
  16. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.clusterfuzzlite/project.yaml +0 -0
  17. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.clusterfuzzlite/requirements-atheris.txt +0 -0
  18. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.dockerignore +0 -0
  19. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.github/CODEOWNERS +0 -0
  20. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.github/ISSUE_TEMPLATE/bug-report.yml +0 -0
  21. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  22. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
  23. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.github/dependabot.yml +0 -0
  24. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.github/workflows/ci.yml +0 -0
  25. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.github/workflows/codeql.yml +0 -0
  26. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.github/workflows/dependabot-uv-lock.yml +0 -0
  27. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.github/workflows/fuzz.yml +0 -0
  28. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.github/workflows/harness-smoke.yml +0 -0
  29. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.github/workflows/publish.yml +0 -0
  30. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.github/workflows/scorecard.yml +0 -0
  31. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.gitignore +0 -0
  32. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.pre-commit-hooks.yaml +0 -0
  33. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/CONTRIBUTING.md +0 -0
  34. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/Dockerfile +0 -0
  35. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/LICENSE +0 -0
  36. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/README.md +0 -0
  37. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/SECURITY.md +0 -0
  38. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/index.html +0 -0
  39. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/package.json +0 -0
  40. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/pnpm-lock.yaml +0 -0
  41. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/public/apple-touch-icon.png +0 -0
  42. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/public/brand/Logo_Icon_Dark.png +0 -0
  43. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/public/brand/Logo_Whole.png +0 -0
  44. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/public/favicon-16x16.png +0 -0
  45. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/public/favicon-32x32.png +0 -0
  46. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/public/favicon.ico +0 -0
  47. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/app.tsx +0 -0
  48. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/approval-center-layout.test.ts +0 -0
  49. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/approval-center-layout.tsx +0 -0
  50. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/approval-center-primitives.tsx +0 -0
  51. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/approval-center-review-cards.tsx +0 -0
  52. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/approval-center-utils.ts +0 -0
  53. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/data-flow-evidence-card.tsx +0 -0
  54. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/fleet-workspace.tsx +0 -0
  55. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/guard-api.test.ts +0 -0
  56. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/guard-api.ts +0 -0
  57. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/guard-demo.ts +0 -0
  58. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/guard-types.ts +0 -0
  59. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/main.tsx +0 -0
  60. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/receipts-workspace.tsx +0 -0
  61. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/runtime-overview.tsx +0 -0
  62. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/settings-workspace.tsx +0 -0
  63. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/styles.css +0 -0
  64. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/vite-env.d.ts +0 -0
  65. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/tsconfig.json +0 -0
  66. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/vite.config.ts +0 -0
  67. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/docker-requirements.txt +0 -0
  68. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/docs/guard/approval-audit.md +0 -0
  69. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/docs/guard/architecture.md +0 -0
  70. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/docs/guard/get-started.md +0 -0
  71. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/docs/guard/harness-support.md +0 -0
  72. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/docs/guard/local-vs-cloud.md +0 -0
  73. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/docs/guard/testing-matrix.md +0 -0
  74. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/docs/trust/mcp-trust-draft.md +0 -0
  75. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/docs/trust/plugin-trust-draft.md +0 -0
  76. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/docs/trust/skill-trust-local.md +0 -0
  77. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/fuzzers/manifest_fuzzer.py +0 -0
  78. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/requirements.txt +0 -0
  79. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/schemas/plugin-quality.v1.json +0 -0
  80. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/schemas/scan-result.v1.json +0 -0
  81. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/schemas/verify-result.v1.json +0 -0
  82. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/__init__.py +0 -0
  83. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/action_runner.py +0 -0
  84. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/argparse_utils.py +0 -0
  85. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/checks/__init__.py +0 -0
  86. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/checks/best_practices.py +0 -0
  87. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/checks/claude.py +0 -0
  88. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/checks/code_quality.py +0 -0
  89. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/checks/ecosystem_common.py +0 -0
  90. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/checks/gemini.py +0 -0
  91. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/checks/manifest.py +0 -0
  92. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/checks/manifest_support.py +0 -0
  93. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/checks/marketplace.py +0 -0
  94. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/checks/mcp_security.py +0 -0
  95. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/checks/opencode.py +0 -0
  96. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/checks/operational_security.py +0 -0
  97. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/checks/security.py +0 -0
  98. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/checks/skill_security.py +0 -0
  99. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/cli.py +0 -0
  100. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/cli_ui.py +0 -0
  101. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/config.py +0 -0
  102. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/ecosystems/__init__.py +0 -0
  103. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/ecosystems/base.py +0 -0
  104. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/ecosystems/claude.py +0 -0
  105. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/ecosystems/codex.py +0 -0
  106. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/ecosystems/detect.py +0 -0
  107. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/ecosystems/gemini.py +0 -0
  108. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/ecosystems/opencode.py +0 -0
  109. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/ecosystems/registry.py +0 -0
  110. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/ecosystems/types.py +0 -0
  111. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/github_reporting.py +0 -0
  112. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/__init__.py +0 -0
  113. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/access_graph_events.py +0 -0
  114. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/__init__.py +0 -0
  115. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/antigravity.py +0 -0
  116. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/base.py +0 -0
  117. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/claude_code.py +0 -0
  118. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/cloud_identity.py +0 -0
  119. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/codex.py +0 -0
  120. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/copilot.py +0 -0
  121. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/cursor.py +0 -0
  122. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/gemini.py +0 -0
  123. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/hermes.py +0 -0
  124. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/mcp_servers.py +0 -0
  125. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/openclaw.py +0 -0
  126. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/openclaw_config.py +0 -0
  127. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/openclaw_support.py +0 -0
  128. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/opencode.py +0 -0
  129. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/opencode_artifacts.py +0 -0
  130. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/advisory_model.py +0 -0
  131. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/approvals.py +0 -0
  132. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/bridge/__init__.py +0 -0
  133. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/capabilities.py +0 -0
  134. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/cli/__init__.py +0 -0
  135. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/cli/approval_commands.py +0 -0
  136. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/cli/bootstrap.py +0 -0
  137. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/cli/connect_flow.py +0 -0
  138. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/cli/install_commands.py +0 -0
  139. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/cli/product.py +0 -0
  140. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/cli/prompt.py +0 -0
  141. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/cli/render.py +0 -0
  142. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/cli/update_commands.py +0 -0
  143. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/codex_config.py +0 -0
  144. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/config.py +0 -0
  145. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/consumer/__init__.py +0 -0
  146. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/consumer/service.py +0 -0
  147. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/daemon/__init__.py +0 -0
  148. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/daemon/client.py +0 -0
  149. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/daemon/manager.py +0 -0
  150. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/daemon/static/apple-touch-icon.png +0 -0
  151. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/daemon/static/assets/guard-dashboard.js +0 -0
  152. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/daemon/static/assets/index.css +0 -0
  153. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Icon_Dark.png +0 -0
  154. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Whole.png +0 -0
  155. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/daemon/static/favicon-16x16.png +0 -0
  156. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/daemon/static/favicon-32x32.png +0 -0
  157. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/daemon/static/favicon.ico +0 -0
  158. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/daemon/static/index.html +0 -0
  159. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/edge_events.py +0 -0
  160. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/incident.py +0 -0
  161. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/launcher.py +0 -0
  162. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/mcp_tool_calls.py +0 -0
  163. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/models.py +0 -0
  164. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/policy/__init__.py +0 -0
  165. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/policy/engine.py +0 -0
  166. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/protect.py +0 -0
  167. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/proxy/__init__.py +0 -0
  168. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/proxy/remote.py +0 -0
  169. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/proxy/runtime_mcp.py +0 -0
  170. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/proxy/stdio.py +0 -0
  171. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/receipts/__init__.py +0 -0
  172. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/receipts/manager.py +0 -0
  173. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/redaction.py +0 -0
  174. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/risk.py +0 -0
  175. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/__init__.py +0 -0
  176. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/actions.py +0 -0
  177. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/data_flow.py +0 -0
  178. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/data_flow_rules.py +0 -0
  179. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/data_flow_variables.py +0 -0
  180. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/decisions.py +0 -0
  181. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/detectors.py +0 -0
  182. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/mcp_protection.py +0 -0
  183. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/prompt_injection.py +0 -0
  184. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/runner.py +0 -0
  185. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/safe_decode.py +0 -0
  186. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/sandbox.py +0 -0
  187. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/secret_file_requests.py +0 -0
  188. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/secret_sensitivity.py +0 -0
  189. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/secret_sources.py +0 -0
  190. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/shell_commands.py +0 -0
  191. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/signals.py +0 -0
  192. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/skill_protection.py +0 -0
  193. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/supply_chain.py +0 -0
  194. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/surface_server.py +0 -0
  195. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/temp_files.py +0 -0
  196. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/schemas/__init__.py +0 -0
  197. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/schemas/consumer_mode.py +0 -0
  198. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/schemas/guard_event_v1.py +0 -0
  199. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/schemas/surface_server.py +0 -0
  200. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/shims.py +0 -0
  201. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/store_approvals.py +0 -0
  202. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/store_connect.py +0 -0
  203. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/store_evidence.py +0 -0
  204. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/types.py +0 -0
  205. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/integrations/__init__.py +0 -0
  206. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/integrations/cisco_mcp_scanner.py +0 -0
  207. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/integrations/cisco_skill_scanner.py +0 -0
  208. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/lint_fixes.py +0 -0
  209. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/marketplace_support.py +0 -0
  210. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/models.py +0 -0
  211. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/path_support.py +0 -0
  212. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/policy.py +0 -0
  213. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/quality_artifact.py +0 -0
  214. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/repo_detect.py +0 -0
  215. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/reporting.py +0 -0
  216. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/rules/__init__.py +0 -0
  217. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/rules/registry.py +0 -0
  218. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/rules/specs.py +0 -0
  219. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/scanner.py +0 -0
  220. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/submission.py +0 -0
  221. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/suppressions.py +0 -0
  222. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/trust_domain_scoring.py +0 -0
  223. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/trust_helpers.py +0 -0
  224. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/trust_mcp_scoring.py +0 -0
  225. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/trust_models.py +0 -0
  226. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/trust_plugin_scoring.py +0 -0
  227. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/trust_scoring.py +0 -0
  228. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/trust_skill_scoring.py +0 -0
  229. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/trust_specs.py +0 -0
  230. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/verification.py +0 -0
  231. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/__init__.py +0 -0
  232. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/conftest.py +0 -0
  233. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/__init__.py +0 -0
  234. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/bad-plugin/.codex-plugin/plugin.json +0 -0
  235. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/bad-plugin/.mcp.json +0 -0
  236. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/bad-plugin/secrets.js +0 -0
  237. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/claude-plugin-good/.claude-plugin/plugin.json +0 -0
  238. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/claude-plugin-good/LICENSE +0 -0
  239. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/claude-plugin-good/README.md +0 -0
  240. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/claude-plugin-good/SECURITY.md +0 -0
  241. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/claude-plugin-good/hooks/hooks.json +0 -0
  242. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/claude-plugin-good/skills/example/SKILL.md +0 -0
  243. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/code-quality-bad/evil.js +0 -0
  244. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/code-quality-bad/inject.js +0 -0
  245. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/gemini-extension-good/GEMINI.md +0 -0
  246. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/gemini-extension-good/LICENSE +0 -0
  247. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/gemini-extension-good/README.md +0 -0
  248. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/gemini-extension-good/SECURITY.md +0 -0
  249. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/gemini-extension-good/commands/hello.toml +0 -0
  250. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/gemini-extension-good/gemini-extension.json +0 -0
  251. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/good-plugin/.codex-plugin/plugin.json +0 -0
  252. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/good-plugin/.codexignore +0 -0
  253. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/good-plugin/LICENSE +0 -0
  254. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/good-plugin/README.md +0 -0
  255. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/good-plugin/SECURITY.md +0 -0
  256. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/good-plugin/assets/icon.svg +0 -0
  257. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/good-plugin/assets/logo.svg +0 -0
  258. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/good-plugin/assets/screenshot.svg +0 -0
  259. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/good-plugin/skills/example/SKILL.md +0 -0
  260. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/guard-codex-malicious-mcp/.codex/config.toml +0 -0
  261. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/hermes-plugin-evil/config.yaml +0 -0
  262. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/hermes-plugin-evil/mcp_servers.json +0 -0
  263. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/hermes-plugin-evil/skills/security/malicious/SKILL.md +0 -0
  264. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/SKILL.md +0 -0
  265. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/references/api-setup.md +0 -0
  266. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/scripts/deploy.sh +0 -0
  267. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/hermes-plugin-evil/skills/utils/benign/SKILL.md +0 -0
  268. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/malformed-json/.codex-plugin/plugin.json +0 -0
  269. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/malicious-skill-plugin/.codex-plugin/plugin.json +0 -0
  270. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/malicious-skill-plugin/.codexignore +0 -0
  271. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/malicious-skill-plugin/LICENSE +0 -0
  272. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/malicious-skill-plugin/README.md +0 -0
  273. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/malicious-skill-plugin/SECURITY.md +0 -0
  274. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/malicious-skill-plugin/skills/leaky-skill/SKILL.md +0 -0
  275. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/mcp-canary-server.py +0 -0
  276. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/minimal-plugin/.codex-plugin/plugin.json +0 -0
  277. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/missing-fields/.codex-plugin/plugin.json +0 -0
  278. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/mit-license/LICENSE +0 -0
  279. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/multi-ecosystem-repo/codex-plugin/.codex-plugin/plugin.json +0 -0
  280. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/multi-ecosystem-repo/codex-plugin/LICENSE +0 -0
  281. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/multi-ecosystem-repo/codex-plugin/README.md +0 -0
  282. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/multi-ecosystem-repo/codex-plugin/SECURITY.md +0 -0
  283. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/multi-ecosystem-repo/gemini-ext/README.md +0 -0
  284. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/multi-ecosystem-repo/gemini-ext/gemini-extension.json +0 -0
  285. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/multi-plugin-repo/.agents/plugins/marketplace.json +0 -0
  286. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codex-plugin/plugin.json +0 -0
  287. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codexignore +0 -0
  288. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/LICENSE +0 -0
  289. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/README.md +0 -0
  290. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/SECURITY.md +0 -0
  291. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/skills/example/SKILL.md +0 -0
  292. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/.codex-plugin/plugin.json +0 -0
  293. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/skills/example/SKILL.md +0 -0
  294. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/no-version/.codex-plugin/plugin.json +0 -0
  295. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/opencode-good/.opencode/commands/hello.md +0 -0
  296. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/opencode-good/.opencode/plugins/example.ts +0 -0
  297. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/opencode-good/LICENSE +0 -0
  298. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/opencode-good/README.md +0 -0
  299. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/opencode-good/SECURITY.md +0 -0
  300. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/opencode-good/opencode.jsonc +0 -0
  301. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/skills-missing-dir/.codex-plugin/plugin.json +0 -0
  302. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/skills-no-frontmatter/.codex-plugin/plugin.json +0 -0
  303. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/skills-no-frontmatter/skills/bad-skill/SKILL.md +0 -0
  304. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/supply-chain/benign-npm-package.json +0 -0
  305. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/supply-chain/benign-pnpm-package.json +0 -0
  306. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/supply-chain/benign-pyproject.toml +0 -0
  307. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/supply-chain/malicious-Dockerfile +0 -0
  308. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/supply-chain/malicious-action.yml +0 -0
  309. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/supply-chain/malicious-npm-package.json +0 -0
  310. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/supply-chain/malicious-setup.py +0 -0
  311. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/with-marketplace/.codex-plugin/plugin.json +0 -0
  312. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/with-marketplace/marketplace-broken.json +0 -0
  313. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/with-marketplace/marketplace.json +0 -0
  314. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test-trust-scoring.py +0 -0
  315. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test-trust-specs.py +0 -0
  316. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_action_runner.py +0 -0
  317. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_best_practices.py +0 -0
  318. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_cisco_install_surfaces.py +0 -0
  319. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_cli.py +0 -0
  320. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_code_quality.py +0 -0
  321. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_config.py +0 -0
  322. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_coverage_remaining.py +0 -0
  323. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_ecosystems.py +0 -0
  324. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_edge_cases.py +0 -0
  325. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_final_coverage.py +0 -0
  326. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_access_graph.py +0 -0
  327. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_approval_store_scale.py +0 -0
  328. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_approvals.py +0 -0
  329. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_bootstrap.py +0 -0
  330. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_capabilities.py +0 -0
  331. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_claude_adapter.py +0 -0
  332. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_cli.py +0 -0
  333. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_cloud_local_sync.py +0 -0
  334. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_codex_e2e.py +0 -0
  335. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_codex_install.py +0 -0
  336. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_codex_proxy.py +0 -0
  337. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_config_paths.py +0 -0
  338. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_connect_flow.py +0 -0
  339. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_consumer_mode.py +0 -0
  340. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_copilot_adapter.py +0 -0
  341. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_copilot_proxy.py +0 -0
  342. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_daemon_manager.py +0 -0
  343. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_data_flow.py +0 -0
  344. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_event_schema_v1.py +0 -0
  345. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_events.py +0 -0
  346. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_evidence_store.py +0 -0
  347. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_launch_env.py +0 -0
  348. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_mcp_protection.py +0 -0
  349. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_opencode_proxy.py +0 -0
  350. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_product_flow.py +0 -0
  351. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_prompt_injection.py +0 -0
  352. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_protect.py +0 -0
  353. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_render.py +0 -0
  354. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_risk.py +0 -0
  355. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_runtime.py +0 -0
  356. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_runtime_action_harnesses.py +0 -0
  357. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_runtime_actions.py +0 -0
  358. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_runtime_decisions.py +0 -0
  359. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_runtime_detectors.py +0 -0
  360. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_runtime_signals.py +0 -0
  361. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_safe_decode.py +0 -0
  362. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_sandbox.py +0 -0
  363. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_skill_protection.py +0 -0
  364. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_store_migrations.py +0 -0
  365. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_supply_chain.py +0 -0
  366. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_surface_server.py +0 -0
  367. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_verdicts.py +0 -0
  368. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_hermes_adapter.py +0 -0
  369. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_integration.py +0 -0
  370. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_lint_fixes.py +0 -0
  371. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_live_cisco_smoke.py +0 -0
  372. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_manifest.py +0 -0
  373. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_marketplace.py +0 -0
  374. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_mcp_security.py +0 -0
  375. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_openclaw_adapter.py +0 -0
  376. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_operational_security.py +0 -0
  377. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_policy.py +0 -0
  378. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_quality_artifact.py +0 -0
  379. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_rule_registry.py +0 -0
  380. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_scanner.py +0 -0
  381. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_schema_contracts.py +0 -0
  382. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_security.py +0 -0
  383. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_security_ops.py +0 -0
  384. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_skill_security.py +0 -0
  385. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_submission.py +0 -0
  386. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_trust_scoring.py +0 -0
  387. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_trust_specs.py +0 -0
  388. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_verification.py +0 -0
  389. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_versioning.py +0 -0
  390. {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plugin-scanner
3
- Version: 2.0.127
3
+ Version: 2.0.128
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.127"
7
+ version = "2.0.128"
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.127"
7
+ version = "2.0.128"
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"
@@ -472,6 +472,22 @@ def _configure_guard_parser(guard_parser: argparse.ArgumentParser) -> None:
472
472
  advisories_parser = guard_subparsers.add_parser("advisories", help="List cached Guard advisories and verdicts")
473
473
  _add_guard_common_args(advisories_parser)
474
474
  advisories_parser.add_argument("--json", action="store_true")
475
+ advisories_sub = advisories_parser.add_subparsers(dest="advisories_subcommand")
476
+
477
+ _adv_list = advisories_sub.add_parser("list", help="List cached advisories")
478
+ _adv_list.add_argument("--json", action="store_true")
479
+ _adv_list.add_argument(
480
+ "--severity",
481
+ choices=["low", "medium", "high", "critical"],
482
+ help="Filter by minimum severity",
483
+ )
484
+
485
+ _adv_sync = advisories_sub.add_parser("sync", help="Sync advisories from Guard Cloud")
486
+ _adv_sync.add_argument("--json", action="store_true")
487
+
488
+ _adv_explain = advisories_sub.add_parser("explain", help="Explain a specific advisory by ID")
489
+ _adv_explain.add_argument("--json", action="store_true")
490
+ _adv_explain.add_argument("advisory_id", help="Advisory ID to explain")
475
491
 
476
492
  events_parser = guard_subparsers.add_parser("events", help="List local Guard lifecycle events")
477
493
  _add_guard_common_args(events_parser)
@@ -1081,11 +1097,44 @@ def run_guard_command(
1081
1097
  return 0
1082
1098
 
1083
1099
  if args.guard_command == "advisories":
1084
- _emit(
1085
- "advisories",
1086
- {"generated_at": _now(), "items": store.list_cached_advisories()},
1087
- getattr(args, "json", False),
1088
- )
1100
+ adv_sub = getattr(args, "advisories_subcommand", None)
1101
+ if adv_sub == "sync":
1102
+ credentials = store.get_sync_credentials()
1103
+ if credentials is None:
1104
+ _emit(
1105
+ "advisories_sync",
1106
+ {"generated_at": _now(), "status": "no_cloud_sync_configured"},
1107
+ getattr(args, "json", False),
1108
+ )
1109
+ else:
1110
+ _emit(
1111
+ "advisories_sync",
1112
+ {"generated_at": _now(), "status": "advisory_sync_not_available", "synced": False},
1113
+ getattr(args, "json", False),
1114
+ )
1115
+ elif adv_sub == "explain":
1116
+ target_id = getattr(args, "advisory_id", None)
1117
+ all_advs = store.list_cached_advisories(limit=None)
1118
+ match = next(
1119
+ (a for a in all_advs if a.get("advisory_id") == target_id or a.get("id") == target_id),
1120
+ None,
1121
+ )
1122
+ if match:
1123
+ _emit("advisory_explain", match, getattr(args, "json", False))
1124
+ else:
1125
+ _emit("advisory_explain", {"error": f"advisory {target_id!r} not found"}, getattr(args, "json", False))
1126
+ else:
1127
+ all_advs = store.list_cached_advisories()
1128
+ sev_filter = getattr(args, "severity", None)
1129
+ _sev_rank = {"low": 0, "medium": 1, "high": 2, "critical": 3}
1130
+ if sev_filter and sev_filter in _sev_rank:
1131
+ min_rank = _sev_rank[sev_filter]
1132
+ all_advs = [a for a in all_advs if _sev_rank.get(str(a.get("severity", "")).lower(), -1) >= min_rank]
1133
+ _emit(
1134
+ "advisories",
1135
+ {"generated_at": _now(), "items": all_advs},
1136
+ getattr(args, "json", False),
1137
+ )
1089
1138
  return 0
1090
1139
 
1091
1140
  if args.guard_command == "events":
@@ -208,10 +208,12 @@ class _GuardDaemonHandler(BaseHTTPRequestHandler):
208
208
  limit=limit_v,
209
209
  )
210
210
  total = count_evidence(conn)
211
- self._write_json({
212
- "items": [vars(r) for r in records],
213
- "total": total,
214
- })
211
+ self._write_json(
212
+ {
213
+ "items": [vars(r) for r in records],
214
+ "total": total,
215
+ }
216
+ )
215
217
  return
216
218
  if parsed.path == "/v1/evidence/export":
217
219
  with store._connect() as conn:
@@ -0,0 +1,65 @@
1
+ """Advisory-driven policy escalation for Guard decisions.
2
+
3
+ When a cached threat intelligence bundle contains a critical advisory that
4
+ matches the current artifact, a lower-severity policy action such as ``allow``
5
+ or ``warn`` can be escalated to ``ask`` or ``block`` to ensure the user
6
+ reviews the risk before the action proceeds.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import TYPE_CHECKING
12
+
13
+ if TYPE_CHECKING:
14
+ from codex_plugin_scanner.guard.models import GuardAction
15
+ from codex_plugin_scanner.guard.runtime.threat_intel import ThreatAdvisory
16
+
17
+ _ESCALATION_TABLE: dict[str, dict[str, str]] = {
18
+ "critical": {
19
+ "allow": "ask",
20
+ "warn": "ask",
21
+ "review": "block",
22
+ },
23
+ "high": {
24
+ "allow": "ask",
25
+ "warn": "ask",
26
+ },
27
+ }
28
+
29
+ _ESCALATION_THRESHOLD_SEVERITIES = frozenset({"critical", "high"})
30
+
31
+
32
+ def escalate_for_advisories(
33
+ policy_action: GuardAction,
34
+ matched_advisories: tuple[ThreatAdvisory, ...],
35
+ ) -> tuple[GuardAction, str | None]:
36
+ """Return the escalated policy action and the advisory id that triggered it.
37
+
38
+ If no advisory warrants escalation, returns the original action and None.
39
+ The most severe advisory match drives escalation; within the same severity
40
+ the first match in the input tuple wins.
41
+ """
42
+ if not matched_advisories:
43
+ return policy_action, None
44
+
45
+ ranked = sorted(
46
+ (a for a in matched_advisories if a.severity.lower() in _ESCALATION_THRESHOLD_SEVERITIES),
47
+ key=lambda a: 0 if a.severity.lower() == "critical" else 1,
48
+ )
49
+
50
+ for advisory in ranked:
51
+ escalation_map = _ESCALATION_TABLE.get(advisory.severity.lower(), {})
52
+ escalated = escalation_map.get(policy_action)
53
+ if escalated is not None:
54
+ return escalated, advisory.advisory_id # type: ignore[return-value]
55
+
56
+ return policy_action, None
57
+
58
+
59
+ def advisory_match_summary(matched_advisories: tuple[ThreatAdvisory, ...]) -> str:
60
+ """Return a brief human-readable summary of matched advisories for logging."""
61
+ if not matched_advisories:
62
+ return "no advisory matches"
63
+ parts = [f"{a.advisory_id}({a.severity.lower()})" for a in matched_advisories[:5]]
64
+ suffix = f" +{len(matched_advisories) - 5} more" if len(matched_advisories) > 5 else ""
65
+ return ", ".join(parts) + suffix
@@ -0,0 +1,164 @@
1
+ """Advisory matchers for multi-source threat intelligence.
2
+
3
+ Each matcher takes a ThreatAdvisory and a target dict, and returns True if
4
+ the advisory applies to the target. Matchers are keyed by their `matcher`
5
+ field value in the advisory.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import re
11
+ from typing import Protocol, runtime_checkable
12
+
13
+ from .threat_intel import ThreatAdvisory
14
+
15
+
16
+ @runtime_checkable
17
+ class AdvisoryMatcher(Protocol):
18
+ """Callable that tests an advisory against a target dict."""
19
+
20
+ def __call__(self, advisory: ThreatAdvisory, target: dict[str, object]) -> bool: ...
21
+
22
+
23
+ def _norm(val: object) -> str:
24
+ if not isinstance(val, str):
25
+ return ""
26
+ return val.strip().lower()
27
+
28
+
29
+ def _package_matches(advisory_pkg: str, target_pkg: str) -> bool:
30
+ if not advisory_pkg or not target_pkg:
31
+ return False
32
+ return _norm(advisory_pkg) == _norm(target_pkg)
33
+
34
+
35
+ def match_osv(advisory: ThreatAdvisory, target: dict[str, object]) -> bool:
36
+ """Match OSV advisories by package name and ecosystem."""
37
+ pkg_name = target.get("package_name")
38
+ ecosystem = target.get("ecosystem")
39
+ advisory_pkg = advisory.matcher.split(":", 1)[-1] if ":" in advisory.matcher else advisory.matcher
40
+ parts = advisory.source.split("/", 1)
41
+ advisory_eco = parts[1] if len(parts) > 1 else parts[0]
42
+ eco_match = not ecosystem or _norm(advisory_eco) in (_norm(str(ecosystem)), "*")
43
+ return eco_match and _package_matches(advisory_pkg, str(pkg_name or ""))
44
+
45
+
46
+ def match_github_advisory(advisory: ThreatAdvisory, target: dict[str, object]) -> bool:
47
+ """Match GitHub Security Advisory by GHSA ID or package name."""
48
+ matcher_val = advisory.matcher
49
+ if re.match(r"^GHSA-", matcher_val, re.IGNORECASE):
50
+ ghsa_id = target.get("ghsa_id")
51
+ return isinstance(ghsa_id, str) and _norm(ghsa_id) == _norm(matcher_val)
52
+ return _package_matches(matcher_val, str(target.get("package_name") or ""))
53
+
54
+
55
+ def match_nvd_cve(advisory: ThreatAdvisory, target: dict[str, object]) -> bool:
56
+ """Match NVD CVE by CVE ID or harness name."""
57
+ matcher_val = advisory.matcher
58
+ if re.match(r"^CVE-", matcher_val, re.IGNORECASE):
59
+ cve_id = target.get("cve_id")
60
+ return isinstance(cve_id, str) and _norm(cve_id) == _norm(matcher_val)
61
+ harness = target.get("harness")
62
+ return isinstance(harness, str) and _norm(harness) == _norm(matcher_val)
63
+
64
+
65
+ def match_npm_advisory(advisory: ThreatAdvisory, target: dict[str, object]) -> bool:
66
+ """Match npm advisory by package name when ecosystem is npm."""
67
+ eco = target.get("ecosystem")
68
+ if not isinstance(eco, str) or _norm(eco) not in ("npm",):
69
+ return False
70
+ return _package_matches(advisory.matcher, str(target.get("package_name") or ""))
71
+
72
+
73
+ def match_pypi_advisory(advisory: ThreatAdvisory, target: dict[str, object]) -> bool:
74
+ """Match PyPI advisory by package name when ecosystem is pypi."""
75
+ eco = target.get("ecosystem")
76
+ if not isinstance(eco, str) or _norm(eco) not in ("pypi", "pip"):
77
+ return False
78
+ return _package_matches(advisory.matcher, str(target.get("package_name") or ""))
79
+
80
+
81
+ def match_github_action(advisory: ThreatAdvisory, target: dict[str, object]) -> bool:
82
+ """Match a GitHub Action by action slug (owner/name)."""
83
+ action_slug = target.get("action_slug")
84
+ if not isinstance(action_slug, str):
85
+ return False
86
+ return _norm(advisory.matcher) == _norm(action_slug)
87
+
88
+
89
+ def match_mcp_server(advisory: ThreatAdvisory, target: dict[str, object]) -> bool:
90
+ """Match an MCP server advisory by exact server name."""
91
+ server_name = target.get("mcp_server")
92
+ if not isinstance(server_name, str):
93
+ return False
94
+ return _norm(server_name) == _norm(advisory.matcher)
95
+
96
+
97
+ def match_skill_hash(advisory: ThreatAdvisory, target: dict[str, object]) -> bool:
98
+ """Match a skill (extension/tool) by its content hash."""
99
+ skill_hash = target.get("skill_hash")
100
+ if not isinstance(skill_hash, str):
101
+ return False
102
+ return _norm(advisory.matcher) == _norm(skill_hash)
103
+
104
+
105
+ def match_malicious_domain(advisory: ThreatAdvisory, target: dict[str, object]) -> bool:
106
+ """Match a network destination against a known-malicious domain.
107
+
108
+ Uses exact match or subdomain boundary match to avoid false positives.
109
+ e.g. advisory matcher ``evil.com`` matches ``evil.com`` and ``sub.evil.com``
110
+ but not ``not-evil.com``.
111
+ """
112
+ hosts: object = target.get("network_hosts")
113
+ if not isinstance(hosts, list):
114
+ hosts = [target.get("network_host")] if target.get("network_host") else []
115
+ matcher_norm = _norm(advisory.matcher)
116
+ return any(_norm(str(h)) == matcher_norm or _norm(str(h)).endswith("." + matcher_norm) for h in hosts if h)
117
+
118
+
119
+ def match_malicious_package_hash(advisory: ThreatAdvisory, target: dict[str, object]) -> bool:
120
+ """Match a package by its content hash (e.g., tarball SHA256)."""
121
+ pkg_hash = target.get("package_hash")
122
+ if not isinstance(pkg_hash, str):
123
+ return False
124
+ return _norm(advisory.matcher) == _norm(pkg_hash)
125
+
126
+
127
+ _MATCHER_REGISTRY: dict[str, AdvisoryMatcher] = {
128
+ "osv": match_osv,
129
+ "github_advisory": match_github_advisory,
130
+ "nvd_cve": match_nvd_cve,
131
+ "npm": match_npm_advisory,
132
+ "pypi": match_pypi_advisory,
133
+ "github_action": match_github_action,
134
+ "mcp_server": match_mcp_server,
135
+ "skill_hash": match_skill_hash,
136
+ "malicious_domain": match_malicious_domain,
137
+ "malicious_package_hash": match_malicious_package_hash,
138
+ }
139
+
140
+
141
+ def get_matcher(matcher_key: str) -> AdvisoryMatcher | None:
142
+ """Return the matcher function for a given matcher key, or None if unknown."""
143
+ return _MATCHER_REGISTRY.get(matcher_key)
144
+
145
+
146
+ def apply_advisory(advisory: ThreatAdvisory, target: dict[str, object]) -> bool:
147
+ """Test a single advisory against a target using the registered matcher.
148
+
149
+ Dispatcher reads the advisory `source` field to select the matcher function.
150
+ Returns False for unknown source keys (safe default — no false positives).
151
+ """
152
+ source_key = advisory.source.split("/")[0].lower()
153
+ matcher_fn = get_matcher(source_key)
154
+ if matcher_fn is None:
155
+ return False
156
+ return matcher_fn(advisory, target)
157
+
158
+
159
+ def match_all_advisories(
160
+ advisories: tuple[ThreatAdvisory, ...],
161
+ target: dict[str, object],
162
+ ) -> tuple[ThreatAdvisory, ...]:
163
+ """Return all advisories from the bundle that match the given target."""
164
+ return tuple(a for a in advisories if apply_advisory(a, target))
@@ -0,0 +1,243 @@
1
+ """HOL Guard threat intelligence bundle handling.
2
+
3
+ Provides typed dataclasses, signature verification, freshness checks,
4
+ rollback protection, and multi-source advisory matching for the
5
+ cloud advisory client subsystem.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import base64
11
+ import json
12
+ import time
13
+ from dataclasses import dataclass
14
+ from typing import Final
15
+
16
+ from cryptography.exceptions import InvalidSignature
17
+ from cryptography.hazmat.primitives import hashes, serialization
18
+ from cryptography.hazmat.primitives.asymmetric import padding
19
+ from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
20
+
21
+ _BUNDLE_MAX_AGE_SECONDS: Final[int] = 86_400 * 7
22
+ _BUNDLE_CLOCK_SKEW_SECONDS: Final[int] = 300
23
+
24
+
25
+ class ThreatIntelError(Exception):
26
+ """Base error for threat intel bundle validation failures."""
27
+
28
+
29
+ class BundleSignatureError(ThreatIntelError):
30
+ """Bundle signature did not verify against the provided public key."""
31
+
32
+
33
+ class BundleExpiredError(ThreatIntelError):
34
+ """Bundle freshness window has elapsed."""
35
+
36
+
37
+ class BundleRollbackError(ThreatIntelError):
38
+ """Bundle version is older than the cached version — possible rollback attack."""
39
+
40
+
41
+ class BundleMalformedError(ThreatIntelError):
42
+ """Bundle JSON is missing required fields or contains invalid types."""
43
+
44
+
45
+ @dataclass(frozen=True, slots=True)
46
+ class ThreatAdvisory:
47
+ """Single advisory record inside a threat intelligence bundle."""
48
+
49
+ advisory_id: str
50
+ source: str
51
+ severity: str
52
+ title: str
53
+ affected_type: str
54
+ matcher: str
55
+ recommendation: str
56
+
57
+ def to_dict(self) -> dict[str, object]:
58
+ return {
59
+ "advisory_id": self.advisory_id,
60
+ "source": self.source,
61
+ "severity": self.severity,
62
+ "title": self.title,
63
+ "affected_type": self.affected_type,
64
+ "matcher": self.matcher,
65
+ "recommendation": self.recommendation,
66
+ }
67
+
68
+ @staticmethod
69
+ def from_dict(data: dict[str, object]) -> ThreatAdvisory:
70
+ def _str(key: str) -> str:
71
+ val = data.get(key)
72
+ if not isinstance(val, str) or not val.strip():
73
+ raise BundleMalformedError(f"ThreatAdvisory missing required string field: {key!r}")
74
+ return val
75
+
76
+ return ThreatAdvisory(
77
+ advisory_id=_str("advisory_id"),
78
+ source=_str("source"),
79
+ severity=_str("severity"),
80
+ title=_str("title"),
81
+ affected_type=_str("affected_type"),
82
+ matcher=_str("matcher"),
83
+ recommendation=_str("recommendation"),
84
+ )
85
+
86
+
87
+ @dataclass(frozen=True, slots=True)
88
+ class ThreatIntelBundle:
89
+ """Signed, versioned advisory bundle from the HOL Guard cloud."""
90
+
91
+ version: int
92
+ generated_at: float
93
+ expires_at: float
94
+ source: str
95
+ signature: str
96
+ advisories: tuple[ThreatAdvisory, ...]
97
+
98
+ def to_dict(self) -> dict[str, object]:
99
+ return {
100
+ "version": self.version,
101
+ "generated_at": self.generated_at,
102
+ "expires_at": self.expires_at,
103
+ "source": self.source,
104
+ "signature": self.signature,
105
+ "advisories": [a.to_dict() for a in self.advisories],
106
+ }
107
+
108
+ @staticmethod
109
+ def from_dict(data: dict[str, object]) -> ThreatIntelBundle:
110
+ def _int(key: str) -> int:
111
+ val = data.get(key)
112
+ if not isinstance(val, int):
113
+ raise BundleMalformedError(f"ThreatIntelBundle missing required int field: {key!r}")
114
+ return val
115
+
116
+ def _float(key: str) -> float:
117
+ val = data.get(key)
118
+ if isinstance(val, (int, float)):
119
+ return float(val)
120
+ raise BundleMalformedError(f"ThreatIntelBundle missing required numeric field: {key!r}")
121
+
122
+ def _str(key: str) -> str:
123
+ val = data.get(key)
124
+ if not isinstance(val, str) or not val.strip():
125
+ raise BundleMalformedError(f"ThreatIntelBundle missing required string field: {key!r}")
126
+ return val
127
+
128
+ raw_advisories = data.get("advisories")
129
+ if not isinstance(raw_advisories, list):
130
+ raise BundleMalformedError("ThreatIntelBundle 'advisories' must be a list")
131
+
132
+ parsed_advisories: list[ThreatAdvisory] = []
133
+ for idx, item in enumerate(raw_advisories):
134
+ if not isinstance(item, dict):
135
+ raise BundleMalformedError(
136
+ f"ThreatIntelBundle advisory at index {idx} must be an object, got {type(item).__name__}"
137
+ )
138
+ parsed_advisories.append(ThreatAdvisory.from_dict(item))
139
+
140
+ return ThreatIntelBundle(
141
+ version=_int("version"),
142
+ generated_at=_float("generated_at"),
143
+ expires_at=_float("expires_at"),
144
+ source=_str("source"),
145
+ signature=_str("signature"),
146
+ advisories=tuple(parsed_advisories),
147
+ )
148
+
149
+
150
+ def _canonical_payload(bundle: ThreatIntelBundle) -> bytes:
151
+ """Deterministic JSON payload used for signature computation.
152
+
153
+ Signs only the stable fields — excludes `signature` itself.
154
+ Timestamps are serialized as integers to avoid float precision
155
+ differences across JSON implementations.
156
+ """
157
+ payload = {
158
+ "version": bundle.version,
159
+ "generated_at": int(bundle.generated_at),
160
+ "expires_at": int(bundle.expires_at),
161
+ "source": bundle.source,
162
+ "advisories": [a.to_dict() for a in bundle.advisories],
163
+ }
164
+ return json.dumps(payload, sort_keys=True, separators=(",", ":")).encode()
165
+
166
+
167
+ def verify_bundle_signature(bundle: ThreatIntelBundle, public_key_pem: bytes) -> None:
168
+ """Verify the RSA-PSS signature on a bundle.
169
+
170
+ Raises BundleSignatureError if the signature does not verify.
171
+ """
172
+ try:
173
+ loaded_key = serialization.load_pem_public_key(public_key_pem)
174
+ except (ValueError, TypeError) as exc:
175
+ raise BundleSignatureError(f"Failed to load public key: {exc}") from exc
176
+
177
+ if not isinstance(loaded_key, RSAPublicKey):
178
+ raise BundleSignatureError("Public key must be RSA")
179
+
180
+ try:
181
+ sig_bytes = base64.b64decode(bundle.signature)
182
+ except Exception as exc:
183
+ raise BundleSignatureError(f"Signature is not valid base64: {exc}") from exc
184
+
185
+ payload = _canonical_payload(bundle)
186
+ try:
187
+ loaded_key.verify(
188
+ sig_bytes,
189
+ payload,
190
+ padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH),
191
+ hashes.SHA256(),
192
+ )
193
+ except InvalidSignature as exc:
194
+ raise BundleSignatureError("Bundle signature verification failed") from exc
195
+
196
+
197
+ def check_bundle_freshness(bundle: ThreatIntelBundle, now: float | None = None) -> None:
198
+ """Raise BundleExpiredError if the bundle is outside its freshness window.
199
+
200
+ Uses wall-clock time by default; pass `now` in tests for determinism.
201
+ """
202
+ ts = now if now is not None else time.time()
203
+ if ts > bundle.expires_at + _BUNDLE_CLOCK_SKEW_SECONDS:
204
+ raise BundleExpiredError(f"Bundle expired at {bundle.expires_at:.0f}, current time is {ts:.0f}")
205
+ if ts < bundle.generated_at - _BUNDLE_CLOCK_SKEW_SECONDS:
206
+ raise BundleExpiredError(
207
+ f"Bundle generated_at {bundle.generated_at:.0f} is in the future (current time {ts:.0f})"
208
+ )
209
+ bundle_age = ts - bundle.generated_at
210
+ if bundle_age > _BUNDLE_MAX_AGE_SECONDS:
211
+ raise BundleExpiredError(
212
+ f"Bundle age {bundle_age:.0f}s exceeds maximum allowed age of {_BUNDLE_MAX_AGE_SECONDS}s"
213
+ )
214
+
215
+
216
+ def check_bundle_rollback(bundle: ThreatIntelBundle, cached_version: int) -> None:
217
+ """Raise BundleRollbackError if the bundle version regresses.
218
+
219
+ Protects against a server serving an older bundle to downgrade protections.
220
+ """
221
+ if bundle.version < cached_version:
222
+ raise BundleRollbackError(f"Bundle version {bundle.version} is older than cached version {cached_version}")
223
+
224
+
225
+ def load_bundle_from_json(raw_json: str) -> ThreatIntelBundle:
226
+ """Parse a raw JSON string into a ThreatIntelBundle.
227
+
228
+ Raises BundleMalformedError on invalid JSON or schema violations.
229
+ """
230
+ try:
231
+ data = json.loads(raw_json)
232
+ except json.JSONDecodeError as exc:
233
+ raise BundleMalformedError(f"Bundle JSON is invalid: {exc}") from exc
234
+
235
+ if not isinstance(data, dict):
236
+ raise BundleMalformedError("Bundle root must be a JSON object")
237
+
238
+ return ThreatIntelBundle.from_dict(data)
239
+
240
+
241
+ def advisory_severity_rank(severity: str) -> int:
242
+ """Numeric rank for severity comparison (higher = more severe)."""
243
+ return {"info": 0, "low": 1, "medium": 2, "high": 3, "critical": 4}.get(severity.lower(), 0)
@@ -75,6 +75,11 @@ from .store_evidence import (
75
75
  evidence_index_statements,
76
76
  evidence_schema_statement,
77
77
  )
78
+ from .store_threat_intel import (
79
+ threat_intel_bundle_schema_statement,
80
+ threat_intel_index_statements,
81
+ threat_intel_matches_schema_statement,
82
+ )
78
83
  from .types import CapabilitySet
79
84
 
80
85
  _SYNC_TOKEN_REF = "guard-cloud-token"
@@ -651,12 +656,16 @@ class GuardStore:
651
656
  connect_state_schema_statement(),
652
657
  approval_schema_statement(),
653
658
  evidence_schema_statement(),
659
+ threat_intel_bundle_schema_statement(),
660
+ threat_intel_matches_schema_statement(),
654
661
  )
655
662
  with self._connect() as connection:
656
663
  for statement in statements:
657
664
  connection.execute(statement)
658
665
  for idx_stmt in evidence_index_statements():
659
666
  connection.execute(idx_stmt)
667
+ for idx_stmt in threat_intel_index_statements():
668
+ connection.execute(idx_stmt)
660
669
  self._ensure_policy_column(connection, "publisher", "text")
661
670
  self._ensure_policy_column(connection, "artifact_hash", "text")
662
671
  self._ensure_policy_column(connection, "owner", "text")