plugin-scanner 2.0.121__tar.gz → 2.0.122__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 (377) hide show
  1. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/PKG-INFO +1 -1
  2. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/pyproject.toml +1 -1
  3. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/pyproject.toml.bak +1 -1
  4. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/cli/render.py +29 -1
  5. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/runtime/detectors.py +73 -0
  6. plugin_scanner-2.0.122/src/codex_plugin_scanner/guard/runtime/safe_decode.py +412 -0
  7. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/version.py +1 -1
  8. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_render.py +22 -0
  9. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_runtime_detectors.py +73 -0
  10. plugin_scanner-2.0.122/tests/test_guard_safe_decode.py +365 -0
  11. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/.clusterfuzzlite/Dockerfile +0 -0
  12. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/.clusterfuzzlite/build.sh +0 -0
  13. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/.clusterfuzzlite/project.yaml +0 -0
  14. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/.clusterfuzzlite/requirements-atheris.txt +0 -0
  15. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/.dockerignore +0 -0
  16. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/.github/CODEOWNERS +0 -0
  17. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/.github/ISSUE_TEMPLATE/bug-report.yml +0 -0
  18. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  19. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
  20. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/.github/dependabot.yml +0 -0
  21. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/.github/workflows/ci.yml +0 -0
  22. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/.github/workflows/codeql.yml +0 -0
  23. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/.github/workflows/dependabot-uv-lock.yml +0 -0
  24. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/.github/workflows/fuzz.yml +0 -0
  25. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/.github/workflows/harness-smoke.yml +0 -0
  26. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/.github/workflows/publish.yml +0 -0
  27. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/.github/workflows/scorecard.yml +0 -0
  28. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/.gitignore +0 -0
  29. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/.pre-commit-hooks.yaml +0 -0
  30. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/CONTRIBUTING.md +0 -0
  31. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/Dockerfile +0 -0
  32. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/LICENSE +0 -0
  33. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/README.md +0 -0
  34. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/SECURITY.md +0 -0
  35. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/dashboard/index.html +0 -0
  36. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/dashboard/package.json +0 -0
  37. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/dashboard/pnpm-lock.yaml +0 -0
  38. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/dashboard/public/apple-touch-icon.png +0 -0
  39. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/dashboard/public/brand/Logo_Icon_Dark.png +0 -0
  40. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/dashboard/public/brand/Logo_Whole.png +0 -0
  41. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/dashboard/public/favicon-16x16.png +0 -0
  42. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/dashboard/public/favicon-32x32.png +0 -0
  43. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/dashboard/public/favicon.ico +0 -0
  44. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/dashboard/src/app.tsx +0 -0
  45. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/dashboard/src/approval-center-layout.tsx +0 -0
  46. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/dashboard/src/approval-center-primitives.tsx +0 -0
  47. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/dashboard/src/approval-center-utils.ts +0 -0
  48. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/dashboard/src/data-flow-evidence-card.tsx +0 -0
  49. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/dashboard/src/fleet-workspace.tsx +0 -0
  50. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/dashboard/src/guard-api.test.ts +0 -0
  51. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/dashboard/src/guard-api.ts +0 -0
  52. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/dashboard/src/guard-demo.ts +0 -0
  53. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/dashboard/src/guard-types.ts +0 -0
  54. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/dashboard/src/main.tsx +0 -0
  55. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/dashboard/src/receipts-workspace.tsx +0 -0
  56. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/dashboard/src/runtime-overview.tsx +0 -0
  57. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/dashboard/src/settings-workspace.tsx +0 -0
  58. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/dashboard/src/styles.css +0 -0
  59. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/dashboard/src/vite-env.d.ts +0 -0
  60. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/dashboard/tsconfig.json +0 -0
  61. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/dashboard/vite.config.ts +0 -0
  62. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/docker-requirements.txt +0 -0
  63. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/docs/guard/approval-audit.md +0 -0
  64. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/docs/guard/architecture.md +0 -0
  65. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/docs/guard/get-started.md +0 -0
  66. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/docs/guard/harness-support.md +0 -0
  67. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/docs/guard/local-vs-cloud.md +0 -0
  68. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/docs/guard/testing-matrix.md +0 -0
  69. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/docs/trust/mcp-trust-draft.md +0 -0
  70. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/docs/trust/plugin-trust-draft.md +0 -0
  71. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/docs/trust/skill-trust-local.md +0 -0
  72. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/fuzzers/manifest_fuzzer.py +0 -0
  73. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/requirements.txt +0 -0
  74. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/schemas/plugin-quality.v1.json +0 -0
  75. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/schemas/scan-result.v1.json +0 -0
  76. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/schemas/verify-result.v1.json +0 -0
  77. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/__init__.py +0 -0
  78. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/action_runner.py +0 -0
  79. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/argparse_utils.py +0 -0
  80. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/checks/__init__.py +0 -0
  81. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/checks/best_practices.py +0 -0
  82. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/checks/claude.py +0 -0
  83. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/checks/code_quality.py +0 -0
  84. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/checks/ecosystem_common.py +0 -0
  85. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/checks/gemini.py +0 -0
  86. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/checks/manifest.py +0 -0
  87. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/checks/manifest_support.py +0 -0
  88. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/checks/marketplace.py +0 -0
  89. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/checks/mcp_security.py +0 -0
  90. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/checks/opencode.py +0 -0
  91. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/checks/operational_security.py +0 -0
  92. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/checks/security.py +0 -0
  93. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/checks/skill_security.py +0 -0
  94. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/cli.py +0 -0
  95. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/cli_ui.py +0 -0
  96. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/config.py +0 -0
  97. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/ecosystems/__init__.py +0 -0
  98. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/ecosystems/base.py +0 -0
  99. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/ecosystems/claude.py +0 -0
  100. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/ecosystems/codex.py +0 -0
  101. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/ecosystems/detect.py +0 -0
  102. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/ecosystems/gemini.py +0 -0
  103. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/ecosystems/opencode.py +0 -0
  104. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/ecosystems/registry.py +0 -0
  105. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/ecosystems/types.py +0 -0
  106. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/github_reporting.py +0 -0
  107. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/__init__.py +0 -0
  108. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/access_graph_events.py +0 -0
  109. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/adapters/__init__.py +0 -0
  110. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/adapters/antigravity.py +0 -0
  111. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/adapters/base.py +0 -0
  112. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/adapters/claude_code.py +0 -0
  113. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/adapters/cloud_identity.py +0 -0
  114. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/adapters/codex.py +0 -0
  115. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/adapters/copilot.py +0 -0
  116. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/adapters/cursor.py +0 -0
  117. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/adapters/gemini.py +0 -0
  118. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/adapters/hermes.py +0 -0
  119. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/adapters/mcp_servers.py +0 -0
  120. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/adapters/openclaw.py +0 -0
  121. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/adapters/openclaw_config.py +0 -0
  122. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/adapters/openclaw_support.py +0 -0
  123. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/adapters/opencode.py +0 -0
  124. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/adapters/opencode_artifacts.py +0 -0
  125. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/advisory_model.py +0 -0
  126. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/approvals.py +0 -0
  127. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/bridge/__init__.py +0 -0
  128. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/capabilities.py +0 -0
  129. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/cli/__init__.py +0 -0
  130. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/cli/approval_commands.py +0 -0
  131. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/cli/bootstrap.py +0 -0
  132. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/cli/commands.py +0 -0
  133. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/cli/connect_flow.py +0 -0
  134. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/cli/install_commands.py +0 -0
  135. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/cli/product.py +0 -0
  136. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/cli/prompt.py +0 -0
  137. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/cli/update_commands.py +0 -0
  138. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/codex_config.py +0 -0
  139. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/config.py +0 -0
  140. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/consumer/__init__.py +0 -0
  141. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/consumer/service.py +0 -0
  142. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/daemon/__init__.py +0 -0
  143. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/daemon/client.py +0 -0
  144. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/daemon/manager.py +0 -0
  145. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/daemon/server.py +0 -0
  146. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/daemon/static/apple-touch-icon.png +0 -0
  147. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/daemon/static/assets/guard-dashboard.js +0 -0
  148. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/daemon/static/assets/index.css +0 -0
  149. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Icon_Dark.png +0 -0
  150. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Whole.png +0 -0
  151. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/daemon/static/favicon-16x16.png +0 -0
  152. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/daemon/static/favicon-32x32.png +0 -0
  153. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/daemon/static/favicon.ico +0 -0
  154. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/daemon/static/index.html +0 -0
  155. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/edge_events.py +0 -0
  156. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/incident.py +0 -0
  157. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/launcher.py +0 -0
  158. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/mcp_tool_calls.py +0 -0
  159. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/models.py +0 -0
  160. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/policy/__init__.py +0 -0
  161. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/policy/engine.py +0 -0
  162. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/protect.py +0 -0
  163. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/proxy/__init__.py +0 -0
  164. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/proxy/remote.py +0 -0
  165. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/proxy/runtime_mcp.py +0 -0
  166. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/proxy/stdio.py +0 -0
  167. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/receipts/__init__.py +0 -0
  168. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/receipts/manager.py +0 -0
  169. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/redaction.py +0 -0
  170. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/risk.py +0 -0
  171. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/runtime/__init__.py +0 -0
  172. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/runtime/actions.py +0 -0
  173. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/runtime/data_flow.py +0 -0
  174. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/runtime/data_flow_rules.py +0 -0
  175. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/runtime/data_flow_variables.py +0 -0
  176. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/runtime/decisions.py +0 -0
  177. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/runtime/mcp_protection.py +0 -0
  178. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/runtime/prompt_injection.py +0 -0
  179. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/runtime/runner.py +0 -0
  180. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/runtime/secret_file_requests.py +0 -0
  181. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/runtime/secret_sensitivity.py +0 -0
  182. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/runtime/secret_sources.py +0 -0
  183. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/runtime/shell_commands.py +0 -0
  184. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/runtime/signals.py +0 -0
  185. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/runtime/skill_protection.py +0 -0
  186. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/runtime/supply_chain.py +0 -0
  187. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/runtime/surface_server.py +0 -0
  188. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/runtime/temp_files.py +0 -0
  189. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/schemas/__init__.py +0 -0
  190. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/schemas/consumer_mode.py +0 -0
  191. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/schemas/guard_event_v1.py +0 -0
  192. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/schemas/surface_server.py +0 -0
  193. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/shims.py +0 -0
  194. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/store.py +0 -0
  195. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/store_approvals.py +0 -0
  196. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/store_connect.py +0 -0
  197. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/guard/types.py +0 -0
  198. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/integrations/__init__.py +0 -0
  199. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/integrations/cisco_mcp_scanner.py +0 -0
  200. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/integrations/cisco_skill_scanner.py +0 -0
  201. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/lint_fixes.py +0 -0
  202. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/marketplace_support.py +0 -0
  203. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/models.py +0 -0
  204. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/path_support.py +0 -0
  205. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/policy.py +0 -0
  206. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/quality_artifact.py +0 -0
  207. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/repo_detect.py +0 -0
  208. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/reporting.py +0 -0
  209. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/rules/__init__.py +0 -0
  210. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/rules/registry.py +0 -0
  211. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/rules/specs.py +0 -0
  212. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/scanner.py +0 -0
  213. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/submission.py +0 -0
  214. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/suppressions.py +0 -0
  215. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/trust_domain_scoring.py +0 -0
  216. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/trust_helpers.py +0 -0
  217. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/trust_mcp_scoring.py +0 -0
  218. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/trust_models.py +0 -0
  219. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/trust_plugin_scoring.py +0 -0
  220. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/trust_scoring.py +0 -0
  221. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/trust_skill_scoring.py +0 -0
  222. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/trust_specs.py +0 -0
  223. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/src/codex_plugin_scanner/verification.py +0 -0
  224. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/__init__.py +0 -0
  225. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/conftest.py +0 -0
  226. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/__init__.py +0 -0
  227. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/bad-plugin/.codex-plugin/plugin.json +0 -0
  228. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/bad-plugin/.mcp.json +0 -0
  229. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/bad-plugin/secrets.js +0 -0
  230. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/claude-plugin-good/.claude-plugin/plugin.json +0 -0
  231. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/claude-plugin-good/LICENSE +0 -0
  232. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/claude-plugin-good/README.md +0 -0
  233. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/claude-plugin-good/SECURITY.md +0 -0
  234. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/claude-plugin-good/hooks/hooks.json +0 -0
  235. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/claude-plugin-good/skills/example/SKILL.md +0 -0
  236. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/code-quality-bad/evil.js +0 -0
  237. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/code-quality-bad/inject.js +0 -0
  238. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/gemini-extension-good/GEMINI.md +0 -0
  239. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/gemini-extension-good/LICENSE +0 -0
  240. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/gemini-extension-good/README.md +0 -0
  241. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/gemini-extension-good/SECURITY.md +0 -0
  242. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/gemini-extension-good/commands/hello.toml +0 -0
  243. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/gemini-extension-good/gemini-extension.json +0 -0
  244. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/good-plugin/.codex-plugin/plugin.json +0 -0
  245. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/good-plugin/.codexignore +0 -0
  246. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/good-plugin/LICENSE +0 -0
  247. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/good-plugin/README.md +0 -0
  248. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/good-plugin/SECURITY.md +0 -0
  249. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/good-plugin/assets/icon.svg +0 -0
  250. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/good-plugin/assets/logo.svg +0 -0
  251. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/good-plugin/assets/screenshot.svg +0 -0
  252. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/good-plugin/skills/example/SKILL.md +0 -0
  253. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/guard-codex-malicious-mcp/.codex/config.toml +0 -0
  254. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/hermes-plugin-evil/config.yaml +0 -0
  255. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/hermes-plugin-evil/mcp_servers.json +0 -0
  256. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/hermes-plugin-evil/skills/security/malicious/SKILL.md +0 -0
  257. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/SKILL.md +0 -0
  258. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/references/api-setup.md +0 -0
  259. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/scripts/deploy.sh +0 -0
  260. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/hermes-plugin-evil/skills/utils/benign/SKILL.md +0 -0
  261. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/malformed-json/.codex-plugin/plugin.json +0 -0
  262. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/malicious-skill-plugin/.codex-plugin/plugin.json +0 -0
  263. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/malicious-skill-plugin/.codexignore +0 -0
  264. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/malicious-skill-plugin/LICENSE +0 -0
  265. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/malicious-skill-plugin/README.md +0 -0
  266. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/malicious-skill-plugin/SECURITY.md +0 -0
  267. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/malicious-skill-plugin/skills/leaky-skill/SKILL.md +0 -0
  268. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/mcp-canary-server.py +0 -0
  269. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/minimal-plugin/.codex-plugin/plugin.json +0 -0
  270. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/missing-fields/.codex-plugin/plugin.json +0 -0
  271. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/mit-license/LICENSE +0 -0
  272. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/multi-ecosystem-repo/codex-plugin/.codex-plugin/plugin.json +0 -0
  273. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/multi-ecosystem-repo/codex-plugin/LICENSE +0 -0
  274. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/multi-ecosystem-repo/codex-plugin/README.md +0 -0
  275. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/multi-ecosystem-repo/codex-plugin/SECURITY.md +0 -0
  276. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/multi-ecosystem-repo/gemini-ext/README.md +0 -0
  277. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/multi-ecosystem-repo/gemini-ext/gemini-extension.json +0 -0
  278. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/multi-plugin-repo/.agents/plugins/marketplace.json +0 -0
  279. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codex-plugin/plugin.json +0 -0
  280. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codexignore +0 -0
  281. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/LICENSE +0 -0
  282. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/README.md +0 -0
  283. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/SECURITY.md +0 -0
  284. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/skills/example/SKILL.md +0 -0
  285. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/.codex-plugin/plugin.json +0 -0
  286. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/skills/example/SKILL.md +0 -0
  287. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/no-version/.codex-plugin/plugin.json +0 -0
  288. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/opencode-good/.opencode/commands/hello.md +0 -0
  289. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/opencode-good/.opencode/plugins/example.ts +0 -0
  290. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/opencode-good/LICENSE +0 -0
  291. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/opencode-good/README.md +0 -0
  292. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/opencode-good/SECURITY.md +0 -0
  293. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/opencode-good/opencode.jsonc +0 -0
  294. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/skills-missing-dir/.codex-plugin/plugin.json +0 -0
  295. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/skills-no-frontmatter/.codex-plugin/plugin.json +0 -0
  296. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/skills-no-frontmatter/skills/bad-skill/SKILL.md +0 -0
  297. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/supply-chain/benign-npm-package.json +0 -0
  298. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/supply-chain/benign-pnpm-package.json +0 -0
  299. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/supply-chain/benign-pyproject.toml +0 -0
  300. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/supply-chain/malicious-Dockerfile +0 -0
  301. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/supply-chain/malicious-action.yml +0 -0
  302. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/supply-chain/malicious-npm-package.json +0 -0
  303. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/supply-chain/malicious-setup.py +0 -0
  304. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/with-marketplace/.codex-plugin/plugin.json +0 -0
  305. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/with-marketplace/marketplace-broken.json +0 -0
  306. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/fixtures/with-marketplace/marketplace.json +0 -0
  307. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test-trust-scoring.py +0 -0
  308. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test-trust-specs.py +0 -0
  309. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_action_runner.py +0 -0
  310. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_best_practices.py +0 -0
  311. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_cisco_install_surfaces.py +0 -0
  312. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_cli.py +0 -0
  313. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_code_quality.py +0 -0
  314. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_config.py +0 -0
  315. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_coverage_remaining.py +0 -0
  316. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_ecosystems.py +0 -0
  317. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_edge_cases.py +0 -0
  318. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_final_coverage.py +0 -0
  319. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_access_graph.py +0 -0
  320. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_approvals.py +0 -0
  321. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_bootstrap.py +0 -0
  322. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_capabilities.py +0 -0
  323. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_claude_adapter.py +0 -0
  324. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_cli.py +0 -0
  325. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_cloud_local_sync.py +0 -0
  326. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_codex_e2e.py +0 -0
  327. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_codex_install.py +0 -0
  328. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_codex_proxy.py +0 -0
  329. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_config_paths.py +0 -0
  330. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_connect_flow.py +0 -0
  331. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_consumer_mode.py +0 -0
  332. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_copilot_adapter.py +0 -0
  333. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_copilot_proxy.py +0 -0
  334. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_daemon_manager.py +0 -0
  335. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_data_flow.py +0 -0
  336. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_event_schema_v1.py +0 -0
  337. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_events.py +0 -0
  338. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_launch_env.py +0 -0
  339. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_mcp_protection.py +0 -0
  340. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_opencode_proxy.py +0 -0
  341. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_product_flow.py +0 -0
  342. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_prompt_injection.py +0 -0
  343. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_protect.py +0 -0
  344. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_risk.py +0 -0
  345. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_runtime.py +0 -0
  346. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_runtime_action_harnesses.py +0 -0
  347. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_runtime_actions.py +0 -0
  348. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_runtime_decisions.py +0 -0
  349. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_runtime_signals.py +0 -0
  350. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_skill_protection.py +0 -0
  351. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_store_migrations.py +0 -0
  352. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_supply_chain.py +0 -0
  353. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_surface_server.py +0 -0
  354. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_guard_verdicts.py +0 -0
  355. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_hermes_adapter.py +0 -0
  356. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_integration.py +0 -0
  357. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_lint_fixes.py +0 -0
  358. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_live_cisco_smoke.py +0 -0
  359. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_manifest.py +0 -0
  360. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_marketplace.py +0 -0
  361. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_mcp_security.py +0 -0
  362. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_openclaw_adapter.py +0 -0
  363. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_operational_security.py +0 -0
  364. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_policy.py +0 -0
  365. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_quality_artifact.py +0 -0
  366. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_rule_registry.py +0 -0
  367. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_scanner.py +0 -0
  368. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_schema_contracts.py +0 -0
  369. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_security.py +0 -0
  370. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_security_ops.py +0 -0
  371. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_skill_security.py +0 -0
  372. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_submission.py +0 -0
  373. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_trust_scoring.py +0 -0
  374. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_trust_specs.py +0 -0
  375. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_verification.py +0 -0
  376. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/tests/test_versioning.py +0 -0
  377. {plugin_scanner-2.0.121 → plugin_scanner-2.0.122}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plugin-scanner
3
- Version: 2.0.121
3
+ Version: 2.0.122
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.121"
7
+ version = "2.0.122"
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.121"
7
+ version = "2.0.122"
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"
@@ -575,13 +575,14 @@ def _render_approvals(console: Console, payload: dict[str, object]) -> None:
575
575
  def _render_managed_install(console: Console, payload: dict[str, object]) -> None:
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
+ safe_decode_risks = _coerce_dict_list(payload.get("safe_decode_risks"))
578
579
  managed_install = payload.get("managed_install")
579
580
  if isinstance(managed_install, dict):
580
581
  _render_single_managed_install(console, managed_install)
581
582
  else:
582
583
  managed_installs = _coerce_dict_list(payload.get("managed_installs"))
583
584
  if not managed_installs:
584
- if not skill_scan and not supply_chain_risks:
585
+ if not skill_scan and not supply_chain_risks and not safe_decode_risks:
585
586
  _render_fallback(console, payload)
586
587
  return
587
588
  else:
@@ -600,6 +601,8 @@ def _render_managed_install(console: Console, payload: dict[str, object]) -> Non
600
601
  _render_skill_scan_results(console, skill_scan)
601
602
  if supply_chain_risks:
602
603
  _render_supply_chain_risk_results(console, supply_chain_risks)
604
+ if safe_decode_risks:
605
+ _render_safe_decode_results(console, safe_decode_risks)
603
606
 
604
607
 
605
608
  def _render_supply_chain_risk_results(console: Console, supply_chain_risks: list[dict[str, object]]) -> None:
@@ -626,6 +629,31 @@ def _render_supply_chain_risk_results(console: Console, supply_chain_risks: list
626
629
  )
627
630
 
628
631
 
632
+ def _render_safe_decode_results(console: Console, safe_decode_risks: list[dict[str, object]]) -> None:
633
+ table = Table(box=box.SIMPLE_HEAVY, show_header=True, expand=True)
634
+ table.add_column("Signal", overflow="fold")
635
+ table.add_column("Layers", no_wrap=True)
636
+ table.add_column("Severity", no_wrap=True)
637
+ table.add_column("Explanation", overflow="fold")
638
+ for entry in safe_decode_risks:
639
+ severity = str(entry.get("severity", "medium"))
640
+ severity_color = {"critical": "red", "high": "yellow", "medium": "cyan", "low": "dim"}.get(severity, "white")
641
+ layers = entry.get("technical_detail") or ""
642
+ table.add_row(
643
+ str(entry.get("signal_id", "?")),
644
+ layers[:60] if layers else "-",
645
+ f"[{severity_color}]{severity}[/{severity_color}]",
646
+ str(entry.get("plain_reason", "")),
647
+ )
648
+ console.print(
649
+ Panel(
650
+ table,
651
+ title=f"[bold magenta]Encoded payload risks — {len(safe_decode_risks)} signal(s)[/bold magenta]",
652
+ border_style="magenta",
653
+ )
654
+ )
655
+
656
+
629
657
  def _render_skill_scan_results(console: Console, skill_scan: list[dict[str, object]]) -> None:
630
658
  table = Table(box=box.SIMPLE_HEAVY, show_header=True, expand=True)
631
659
  table.add_column("Skill file", overflow="fold")
@@ -12,6 +12,7 @@ from codex_plugin_scanner.guard.config import GuardConfig
12
12
  from codex_plugin_scanner.guard.runtime.actions import GuardActionEnvelope
13
13
  from codex_plugin_scanner.guard.runtime.data_flow_rules import detect_data_flow_exfiltration
14
14
  from codex_plugin_scanner.guard.runtime.prompt_injection import detect_prompt_injection_requests
15
+ from codex_plugin_scanner.guard.runtime.safe_decode import decode_layers
15
16
  from codex_plugin_scanner.guard.runtime.secret_sensitivity import SecretPathMatch, classify_secret_path
16
17
  from codex_plugin_scanner.guard.runtime.signals import (
17
18
  RiskConfidenceLabel,
@@ -208,10 +209,82 @@ class SupplyChainDetector:
208
209
  return detect_supply_chain_risk(action.prompt_text)
209
210
 
210
211
 
212
+ class SafeDecodeDetector:
213
+ """Detects obfuscated/encoded payloads that contain suspicious signals after decoding."""
214
+
215
+ detector_id = "safe-decode.content"
216
+ categories: tuple[RiskSignalCategory, ...] = (
217
+ "execution",
218
+ "network",
219
+ "secret",
220
+ )
221
+
222
+ def detect(self, action: GuardActionEnvelope, context: DetectorContext) -> tuple[RiskSignalV2, ...]:
223
+ del context
224
+ if action.prompt_text is None:
225
+ return ()
226
+ result = decode_layers(action.prompt_text)
227
+ if not result.layers:
228
+ return ()
229
+ signals: list[RiskSignalV2] = []
230
+ if result.eval_signals or result.exec_signals or result.marshal_signals:
231
+ detail_parts: list[str] = []
232
+ if result.eval_signals:
233
+ detail_parts.append(f"eval(): {result.eval_signals[0]!r}")
234
+ if result.exec_signals:
235
+ detail_parts.append(f"exec(): {result.exec_signals[0]!r}")
236
+ if result.marshal_signals:
237
+ detail_parts.append(f"marshal.loads(): {result.marshal_signals[0]!r}")
238
+ signals.append(
239
+ RiskSignalV2(
240
+ signal_id="encoded.code-execution",
241
+ category="execution",
242
+ severity=severity_label_from_score(8),
243
+ confidence=confidence_label_from_score(0.80),
244
+ detector=self.detector_id,
245
+ title="Encoded code-execution payload detected",
246
+ plain_reason=(
247
+ f"Decoded {len(result.layers)} encoding layer(s) and found "
248
+ f"code-execution signals: {'; '.join(detail_parts[:2])}"
249
+ ),
250
+ technical_detail=f"Layers: {[layer.encoding for layer in result.layers]}; "
251
+ f"eval={len(result.eval_signals)} exec={len(result.exec_signals)} "
252
+ f"marshal={len(result.marshal_signals)}",
253
+ evidence_ref=None,
254
+ redaction_level="summary",
255
+ false_positive_hint="Some build tools legitimately encode setup scripts.",
256
+ advisory_id=None,
257
+ )
258
+ )
259
+ elif result.layers:
260
+ signals.append(
261
+ RiskSignalV2(
262
+ signal_id="encoded.obfuscated-content",
263
+ category="execution",
264
+ severity=severity_label_from_score(5),
265
+ confidence=confidence_label_from_score(0.60),
266
+ detector=self.detector_id,
267
+ title="Multi-layer encoded content detected",
268
+ plain_reason=(
269
+ f"Content decoded through {len(result.layers)} encoding layer(s) "
270
+ f"({', '.join(layer.encoding for layer in result.layers)}). "
271
+ "Obfuscated content may conceal malicious instructions."
272
+ ),
273
+ technical_detail=None,
274
+ evidence_ref=None,
275
+ redaction_level="summary",
276
+ false_positive_hint="Encoded documentation or binary assets are common false positives.",
277
+ advisory_id=None,
278
+ )
279
+ )
280
+ return tuple(signals)
281
+
282
+
211
283
  def register_default_detectors() -> tuple[GuardDetector, ...]:
212
284
  return (
213
285
  DataFlowExfiltrationDetector(),
214
286
  PromptInjectionDetector(),
287
+ SafeDecodeDetector(),
215
288
  SecretPathDetector(),
216
289
  SkillRiskDetector(),
217
290
  SupplyChainDetector(),
@@ -0,0 +1,412 @@
1
+ """Safe multi-layer decoder for Guard encoded payload analysis.
2
+
3
+ Decodes content through layers of encoding (base64, hex, URL, etc.) without
4
+ executing any decoded payloads. Enforces hard limits on input size, decoded
5
+ size, recursion depth, and decode time.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import base64
11
+ import binascii
12
+ import hashlib
13
+ import io
14
+ import re
15
+ import time
16
+ import zipfile
17
+ import zlib
18
+ from dataclasses import dataclass, field
19
+ from typing import Literal
20
+
21
+ _MAX_INPUT_BYTES: int = 256 * 1024
22
+ _MAX_DECODED_BYTES: int = 512 * 1024
23
+ _MAX_RECURSION_DEPTH: int = 3
24
+ _MAX_DECODE_TIME_MS: float = 50.0
25
+
26
+ EncodingType = Literal[
27
+ "base64",
28
+ "base64-urlsafe",
29
+ "base32",
30
+ "hex",
31
+ "url-percent",
32
+ "unicode-escape",
33
+ "gzip-metadata",
34
+ "zlib-metadata",
35
+ "zip-listing",
36
+ "powershell-encoded",
37
+ "shell-heredoc",
38
+ "js-atob",
39
+ ]
40
+
41
+
42
+ @dataclass(frozen=True)
43
+ class DecodedLayer:
44
+ """Metadata for one decoded layer of encoded content."""
45
+
46
+ encoding: EncodingType
47
+ input_length: int
48
+ output_length: int
49
+ content_hash: str
50
+ preview_redacted: str
51
+ depth: int
52
+
53
+
54
+ @dataclass
55
+ class DecodeResult:
56
+ """Result of a recursive decode pipeline run."""
57
+
58
+ layers: list[DecodedLayer] = field(default_factory=list)
59
+ final_text: str = ""
60
+ truncated: bool = False
61
+ timed_out: bool = False
62
+ depth_exceeded: bool = False
63
+ size_exceeded: bool = False
64
+ eval_signals: list[str] = field(default_factory=list)
65
+ exec_signals: list[str] = field(default_factory=list)
66
+ marshal_signals: list[str] = field(default_factory=list)
67
+
68
+
69
+ _B64_CANDIDATE = re.compile(
70
+ r"(?=[A-Za-z0-9+/]*(?:[+/]|(?=[A-Za-z0-9+/]*[A-Z])[A-Za-z0-9+/]*[a-z]))"
71
+ r"(?:"
72
+ r"(?:[A-Za-z0-9+/]{4})+(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)"
73
+ r"|(?:[A-Za-z0-9+/]{4}){4,}"
74
+ r")"
75
+ )
76
+ _B64_URLSAFE_CANDIDATE = re.compile(
77
+ r"(?=[A-Za-z0-9\-_]*[\-_])"
78
+ r"(?:[A-Za-z0-9\-_]{4}){5,}(?:[A-Za-z0-9\-_]{2}==|[A-Za-z0-9\-_]{3}=)?"
79
+ )
80
+ _B32_CANDIDATE = re.compile(r"(?=[A-Za-z2-7]*(?:[A-Z]|[2-7]))[A-Za-z2-7]{16,}={0,6}")
81
+ _HEX_CANDIDATE = re.compile(r"(?:0x)?[0-9a-fA-F]{16,}")
82
+ _URL_PERCENT = re.compile(r"(?:%[0-9a-fA-F]{2}){3,}")
83
+ _UNICODE_ESCAPE = re.compile(r"(?:\\u[0-9a-fA-F]{4}){3,}")
84
+ _POWERSHELL_ENCODED = re.compile(
85
+ r"-(?:En(?:c(?:oded(?:Command)?)?)?)\s+([A-Za-z0-9+/=]{8,})",
86
+ re.IGNORECASE,
87
+ )
88
+ _HEREDOC = re.compile(r"<<-?\s*'?(\w+)'?\s*\n(.*?)\n\1\b", re.DOTALL)
89
+ _JS_ATOB = re.compile(r"atob\(\s*['\"]([A-Za-z0-9+/=]{4,})['\"]", re.IGNORECASE)
90
+
91
+ _JS_EVAL = re.compile(r"\beval\s*\(", re.IGNORECASE)
92
+ _PY_EXEC = re.compile(r"\bexec\s*\(", re.IGNORECASE)
93
+ _PY_MARSHAL = re.compile(r"\bmarshal\s*\.\s*loads\s*\(", re.IGNORECASE)
94
+
95
+ _REDACT_TOKENS = re.compile(
96
+ r"\b(?:password|token|secret|key|aws_|npm_token|node_auth)\w*\s*=\s*\S+",
97
+ re.IGNORECASE,
98
+ )
99
+
100
+
101
+ def _hash_preview(text: str) -> tuple[str, str]:
102
+ encoded = text.encode("utf-8", errors="replace")
103
+ digest = hashlib.sha256(encoded).hexdigest()[:16]
104
+ preview_raw = text[:120]
105
+ preview = _REDACT_TOKENS.sub("[REDACTED]", preview_raw)
106
+ return digest, preview
107
+
108
+
109
+ def _bytes_to_text(raw: bytes) -> str:
110
+ """Convert decoded bytes to text, transparently decompressing gzip/zlib output first.
111
+
112
+ Decompression is bounded by _MAX_DECODED_BYTES to prevent gzip-bomb expansion.
113
+ """
114
+ import contextlib
115
+ import gzip
116
+
117
+ if raw[:2] == b"\x1f\x8b":
118
+ with contextlib.suppress(zlib.error, OSError), gzip.GzipFile(fileobj=io.BytesIO(raw)) as gz:
119
+ raw = gz.read(_MAX_DECODED_BYTES + 1)
120
+ elif raw[:1] == b"\x78":
121
+ with contextlib.suppress(zlib.error):
122
+ decompressor = zlib.decompressobj()
123
+ raw = decompressor.decompress(raw, max_length=_MAX_DECODED_BYTES + 1)
124
+ return raw.decode("utf-8", errors="replace")
125
+
126
+
127
+ def _try_base64(data: str) -> str | None:
128
+ padded = data + "=" * ((4 - len(data) % 4) % 4)
129
+ try:
130
+ decoded = base64.b64decode(padded, validate=False)
131
+ return _bytes_to_text(decoded)
132
+ except (binascii.Error, ValueError):
133
+ return None
134
+
135
+
136
+ def _try_base64_urlsafe(data: str) -> str | None:
137
+ padded = data + "=" * ((4 - len(data) % 4) % 4)
138
+ try:
139
+ decoded = base64.urlsafe_b64decode(padded)
140
+ return _bytes_to_text(decoded)
141
+ except (binascii.Error, ValueError):
142
+ return None
143
+
144
+
145
+ def _try_base32(data: str) -> str | None:
146
+ padded = data + "=" * ((8 - len(data) % 8) % 8)
147
+ try:
148
+ decoded = base64.b32decode(padded.upper())
149
+ return decoded.decode("utf-8", errors="replace")
150
+ except (binascii.Error, ValueError):
151
+ return None
152
+
153
+
154
+ def _try_hex(data: str) -> str | None:
155
+ cleaned = data.removeprefix("0x")
156
+ if len(cleaned) % 2 != 0:
157
+ cleaned = "0" + cleaned
158
+ try:
159
+ decoded = bytes.fromhex(cleaned)
160
+ return decoded.decode("utf-8", errors="replace")
161
+ except (ValueError, UnicodeDecodeError):
162
+ return None
163
+
164
+
165
+ def _try_url_percent(data: str) -> str | None:
166
+ if not _URL_PERCENT.search(data):
167
+ return None
168
+ try:
169
+ from urllib.parse import unquote
170
+
171
+ return unquote(data, errors="replace")
172
+ except Exception:
173
+ return None
174
+
175
+
176
+ def _try_unicode_escape(data: str) -> str | None:
177
+ if not _UNICODE_ESCAPE.search(data):
178
+ return None
179
+ try:
180
+ return data.encode("utf-8").decode("unicode_escape", errors="replace")
181
+ except (UnicodeDecodeError, ValueError):
182
+ return None
183
+
184
+
185
+ def _decode_gzip_metadata(data: str) -> DecodedLayer | None:
186
+ raw = data.encode("latin-1", errors="replace")
187
+ if raw[:2] != b"\x1f\x8b":
188
+ return None
189
+ try:
190
+ decoded = zlib.decompress(raw, wbits=16 + zlib.MAX_WBITS)
191
+ text = decoded.decode("utf-8", errors="replace")
192
+ digest, preview = _hash_preview(text)
193
+ return DecodedLayer(
194
+ encoding="gzip-metadata",
195
+ input_length=len(raw),
196
+ output_length=len(decoded),
197
+ content_hash=digest,
198
+ preview_redacted=preview,
199
+ depth=0,
200
+ )
201
+ except zlib.error:
202
+ return None
203
+
204
+
205
+ def _decode_zlib_metadata(data: str) -> DecodedLayer | None:
206
+ raw = data.encode("latin-1", errors="replace")
207
+ if raw[:1] not in (b"\x78",):
208
+ return None
209
+ try:
210
+ decoded = zlib.decompress(raw)
211
+ text = decoded.decode("utf-8", errors="replace")
212
+ digest, preview = _hash_preview(text)
213
+ return DecodedLayer(
214
+ encoding="zlib-metadata",
215
+ input_length=len(raw),
216
+ output_length=len(decoded),
217
+ content_hash=digest,
218
+ preview_redacted=preview,
219
+ depth=0,
220
+ )
221
+ except zlib.error:
222
+ return None
223
+
224
+
225
+ def _decode_zip_listing(data: str) -> list[str] | None:
226
+ raw = data.encode("latin-1", errors="replace")
227
+ if raw[:2] != b"PK":
228
+ return None
229
+ try:
230
+ with zipfile.ZipFile(io.BytesIO(raw)) as zf:
231
+ names = zf.namelist()
232
+ return [n for n in names if not n.startswith("/") and ".." not in n]
233
+ except (zipfile.BadZipFile, Exception):
234
+ return None
235
+
236
+
237
+ def _try_base64_utf16le(data: str) -> str | None:
238
+ """Decode base64 bytes as UTF-16LE — the PowerShell -EncodedCommand convention."""
239
+ padded = data + "=" * ((4 - len(data) % 4) % 4)
240
+ try:
241
+ raw = base64.b64decode(padded, validate=False)
242
+ return raw.decode("utf-16-le", errors="replace")
243
+ except (binascii.Error, ValueError, UnicodeDecodeError):
244
+ return None
245
+
246
+
247
+ def _extract_powershell_encoded(text: str) -> str | None:
248
+ m = _POWERSHELL_ENCODED.search(text)
249
+ if not m:
250
+ return None
251
+ return _try_base64_utf16le(m.group(1)) or _try_base64(m.group(1))
252
+
253
+
254
+ def _extract_heredoc(text: str) -> str | None:
255
+ m = _HEREDOC.search(text)
256
+ if not m:
257
+ return None
258
+ return m.group(2)
259
+
260
+
261
+ def _extract_js_atob(text: str) -> str | None:
262
+ m = _JS_ATOB.search(text)
263
+ if not m:
264
+ return None
265
+ return _try_base64(m.group(1))
266
+
267
+
268
+ def _detect_eval_signals(text: str) -> list[str]:
269
+ return [m.group(0)[:40] for m in _JS_EVAL.finditer(text)]
270
+
271
+
272
+ def _detect_exec_signals(text: str) -> list[str]:
273
+ return [m.group(0)[:40] for m in _PY_EXEC.finditer(text)]
274
+
275
+
276
+ def _detect_marshal_signals(text: str) -> list[str]:
277
+ return [m.group(0)[:40] for m in _PY_MARSHAL.finditer(text)]
278
+
279
+
280
+ def _find_encoded_candidate(text: str) -> tuple[EncodingType, str] | None:
281
+ m = _POWERSHELL_ENCODED.search(text)
282
+ if m:
283
+ return ("powershell-encoded", m.group(1))
284
+
285
+ m = _JS_ATOB.search(text)
286
+ if m:
287
+ return ("js-atob", m.group(1))
288
+
289
+ m = _HEREDOC.search(text)
290
+ if m:
291
+ return ("shell-heredoc", m.group(2))
292
+
293
+ m = _URL_PERCENT.search(text)
294
+ if m:
295
+ return ("url-percent", text)
296
+
297
+ m = _UNICODE_ESCAPE.search(text)
298
+ if m:
299
+ return ("unicode-escape", text)
300
+
301
+ m = _B64_URLSAFE_CANDIDATE.search(text)
302
+ if m:
303
+ return ("base64-urlsafe", m.group(0))
304
+
305
+ m = _B64_CANDIDATE.search(text)
306
+ if m:
307
+ return ("base64", m.group(0))
308
+
309
+ m = _B32_CANDIDATE.search(text)
310
+ if m:
311
+ return ("base32", m.group(0))
312
+
313
+ m = _HEX_CANDIDATE.search(text)
314
+ if m:
315
+ return ("hex", m.group(0))
316
+
317
+ return None
318
+
319
+
320
+ def decode_layers(
321
+ content: str,
322
+ *,
323
+ max_input_bytes: int = _MAX_INPUT_BYTES,
324
+ max_decoded_bytes: int = _MAX_DECODED_BYTES,
325
+ max_depth: int = _MAX_RECURSION_DEPTH,
326
+ max_time_ms: float = _MAX_DECODE_TIME_MS,
327
+ ) -> DecodeResult:
328
+ """Decode multi-layer encoded content without executing any payload."""
329
+ result = DecodeResult()
330
+ start = time.monotonic()
331
+
332
+ if len(content.encode("utf-8", errors="replace")) > max_input_bytes:
333
+ result.size_exceeded = True
334
+ result.final_text = content
335
+ return result
336
+
337
+ current = content
338
+
339
+ depth = -1
340
+ _depth_limited = False
341
+ for depth in range(max_depth):
342
+ elapsed_ms = (time.monotonic() - start) * 1000.0
343
+ if elapsed_ms > max_time_ms:
344
+ result.timed_out = True
345
+ break
346
+
347
+ result.eval_signals.extend(_detect_eval_signals(current))
348
+ result.exec_signals.extend(_detect_exec_signals(current))
349
+ result.marshal_signals.extend(_detect_marshal_signals(current))
350
+
351
+ candidate = _find_encoded_candidate(current)
352
+ if candidate is None:
353
+ break
354
+
355
+ encoding_type, candidate_data = candidate
356
+ decoded: str | None = None
357
+
358
+ if encoding_type == "base64":
359
+ decoded = _try_base64(candidate_data)
360
+ elif encoding_type == "base64-urlsafe":
361
+ decoded = _try_base64_urlsafe(candidate_data)
362
+ elif encoding_type == "base32":
363
+ decoded = _try_base32(candidate_data)
364
+ elif encoding_type == "hex":
365
+ decoded = _try_hex(candidate_data)
366
+ elif encoding_type == "url-percent":
367
+ decoded = _try_url_percent(candidate_data)
368
+ elif encoding_type == "unicode-escape":
369
+ decoded = _try_unicode_escape(candidate_data)
370
+ elif encoding_type == "powershell-encoded":
371
+ decoded = _try_base64_utf16le(candidate_data) or _try_base64(candidate_data)
372
+ elif encoding_type == "shell-heredoc":
373
+ decoded = candidate_data
374
+ elif encoding_type == "js-atob":
375
+ decoded = _try_base64(candidate_data)
376
+
377
+ if decoded is None or decoded == current:
378
+ break
379
+
380
+ decoded_bytes = len(decoded.encode("utf-8", errors="replace"))
381
+ if decoded_bytes > max_decoded_bytes:
382
+ result.size_exceeded = True
383
+ result.truncated = True
384
+ decoded = decoded[: max_decoded_bytes // 4]
385
+
386
+ digest, preview = _hash_preview(decoded)
387
+ result.layers.append(
388
+ DecodedLayer(
389
+ encoding=encoding_type,
390
+ input_length=len(candidate_data),
391
+ output_length=len(decoded),
392
+ content_hash=digest,
393
+ preview_redacted=preview,
394
+ depth=depth,
395
+ )
396
+ )
397
+ current = decoded
398
+
399
+ if result.size_exceeded:
400
+ break
401
+
402
+ if depth == max_depth - 1:
403
+ _depth_limited = True
404
+
405
+ if _depth_limited and not result.timed_out:
406
+ result.depth_exceeded = True
407
+ result.eval_signals.extend(_detect_eval_signals(current))
408
+ result.exec_signals.extend(_detect_exec_signals(current))
409
+ result.marshal_signals.extend(_detect_marshal_signals(current))
410
+
411
+ result.final_text = current
412
+ return result
@@ -1,3 +1,3 @@
1
1
  """Single source of truth for tool version."""
2
2
 
3
- __version__ = "2.0.121"
3
+ __version__ = "2.0.122"
@@ -662,3 +662,25 @@ def test_emit_guard_payload_renders_supply_chain_risks_table(capsys, monkeypatch
662
662
  output = capsys.readouterr().out
663
663
  assert "Supply chain risks" in output
664
664
  assert "1 signal(s)" in output
665
+
666
+
667
+ def test_emit_guard_payload_renders_safe_decode_risks_table(capsys, monkeypatch) -> None:
668
+ monkeypatch.setattr(render, "_RICH_AVAILABLE", True)
669
+ emit_guard_payload(
670
+ "install",
671
+ {
672
+ "safe_decode_risks": [
673
+ {
674
+ "signal_id": "encoded.code-execution",
675
+ "severity": "high",
676
+ "confidence": "strong",
677
+ "plain_reason": "Decoded base64 layer contains eval() call.",
678
+ "technical_detail": "Layers: ['base64']; eval=1 exec=0 marshal=0",
679
+ }
680
+ ]
681
+ },
682
+ False,
683
+ )
684
+ output = capsys.readouterr().out
685
+ assert "Encoded payload risks" in output
686
+ assert "1 signal(s)" in output