plugin-scanner 2.0.87__tar.gz → 2.0.89__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 (333) hide show
  1. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/PKG-INFO +1 -1
  2. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/docker-requirements.txt +3 -3
  3. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/pyproject.toml +2 -2
  4. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/pyproject.toml.bak +2 -2
  5. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/cli/commands.py +91 -3
  6. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/cli/render.py +27 -5
  7. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/redaction.py +3 -3
  8. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/runtime/secret_file_requests.py +165 -16
  9. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/version.py +1 -1
  10. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_cisco_install_surfaces.py +3 -13
  11. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_guard_protect.py +19 -0
  12. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_guard_render.py +68 -0
  13. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_guard_risk.py +75 -12
  14. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_guard_runtime.py +455 -1
  15. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/uv.lock +4 -4
  16. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/.clusterfuzzlite/Dockerfile +0 -0
  17. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/.clusterfuzzlite/build.sh +0 -0
  18. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/.clusterfuzzlite/project.yaml +0 -0
  19. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/.clusterfuzzlite/requirements-atheris.txt +0 -0
  20. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/.dockerignore +0 -0
  21. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/.github/CODEOWNERS +0 -0
  22. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/.github/ISSUE_TEMPLATE/bug-report.yml +0 -0
  23. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  24. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
  25. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/.github/dependabot.yml +0 -0
  26. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/.github/workflows/ci.yml +0 -0
  27. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/.github/workflows/codeql.yml +0 -0
  28. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/.github/workflows/dependabot-uv-lock.yml +0 -0
  29. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/.github/workflows/fuzz.yml +0 -0
  30. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/.github/workflows/harness-smoke.yml +0 -0
  31. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/.github/workflows/publish.yml +0 -0
  32. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/.github/workflows/scorecard.yml +0 -0
  33. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/.gitignore +0 -0
  34. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/.pre-commit-hooks.yaml +0 -0
  35. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/CONTRIBUTING.md +0 -0
  36. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/Dockerfile +0 -0
  37. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/LICENSE +0 -0
  38. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/README.md +0 -0
  39. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/SECURITY.md +0 -0
  40. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/dashboard/index.html +0 -0
  41. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/dashboard/package.json +0 -0
  42. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/dashboard/pnpm-lock.yaml +0 -0
  43. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/dashboard/public/apple-touch-icon.png +0 -0
  44. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/dashboard/public/brand/Logo_Icon_Dark.png +0 -0
  45. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/dashboard/public/brand/Logo_Whole.png +0 -0
  46. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/dashboard/public/favicon-16x16.png +0 -0
  47. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/dashboard/public/favicon-32x32.png +0 -0
  48. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/dashboard/public/favicon.ico +0 -0
  49. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/dashboard/src/app.tsx +0 -0
  50. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/dashboard/src/approval-center-layout.tsx +0 -0
  51. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/dashboard/src/approval-center-primitives.tsx +0 -0
  52. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/dashboard/src/approval-center-utils.ts +0 -0
  53. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/dashboard/src/fleet-workspace.tsx +0 -0
  54. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/dashboard/src/guard-api.ts +0 -0
  55. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/dashboard/src/guard-demo.ts +0 -0
  56. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/dashboard/src/guard-types.ts +0 -0
  57. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/dashboard/src/main.tsx +0 -0
  58. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/dashboard/src/receipts-workspace.tsx +0 -0
  59. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/dashboard/src/runtime-overview.tsx +0 -0
  60. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/dashboard/src/settings-workspace.tsx +0 -0
  61. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/dashboard/src/styles.css +0 -0
  62. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/dashboard/src/vite-env.d.ts +0 -0
  63. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/dashboard/tsconfig.json +0 -0
  64. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/dashboard/vite.config.ts +0 -0
  65. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/docs/guard/approval-audit.md +0 -0
  66. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/docs/guard/architecture.md +0 -0
  67. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/docs/guard/get-started.md +0 -0
  68. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/docs/guard/harness-support.md +0 -0
  69. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/docs/guard/local-vs-cloud.md +0 -0
  70. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/docs/guard/testing-matrix.md +0 -0
  71. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/docs/trust/mcp-trust-draft.md +0 -0
  72. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/docs/trust/plugin-trust-draft.md +0 -0
  73. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/docs/trust/skill-trust-local.md +0 -0
  74. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/fuzzers/manifest_fuzzer.py +0 -0
  75. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/requirements.txt +0 -0
  76. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/schemas/plugin-quality.v1.json +0 -0
  77. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/schemas/scan-result.v1.json +0 -0
  78. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/schemas/verify-result.v1.json +0 -0
  79. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/__init__.py +0 -0
  80. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/action_runner.py +0 -0
  81. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/argparse_utils.py +0 -0
  82. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/checks/__init__.py +0 -0
  83. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/checks/best_practices.py +0 -0
  84. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/checks/claude.py +0 -0
  85. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/checks/code_quality.py +0 -0
  86. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/checks/ecosystem_common.py +0 -0
  87. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/checks/gemini.py +0 -0
  88. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/checks/manifest.py +0 -0
  89. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/checks/manifest_support.py +0 -0
  90. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/checks/marketplace.py +0 -0
  91. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/checks/mcp_security.py +0 -0
  92. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/checks/opencode.py +0 -0
  93. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/checks/operational_security.py +0 -0
  94. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/checks/security.py +0 -0
  95. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/checks/skill_security.py +0 -0
  96. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/cli.py +0 -0
  97. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/cli_ui.py +0 -0
  98. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/config.py +0 -0
  99. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/ecosystems/__init__.py +0 -0
  100. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/ecosystems/base.py +0 -0
  101. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/ecosystems/claude.py +0 -0
  102. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/ecosystems/codex.py +0 -0
  103. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/ecosystems/detect.py +0 -0
  104. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/ecosystems/gemini.py +0 -0
  105. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/ecosystems/opencode.py +0 -0
  106. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/ecosystems/registry.py +0 -0
  107. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/ecosystems/types.py +0 -0
  108. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/github_reporting.py +0 -0
  109. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/__init__.py +0 -0
  110. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/adapters/__init__.py +0 -0
  111. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/adapters/antigravity.py +0 -0
  112. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/adapters/base.py +0 -0
  113. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/adapters/claude_code.py +0 -0
  114. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/adapters/codex.py +0 -0
  115. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/adapters/copilot.py +0 -0
  116. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/adapters/cursor.py +0 -0
  117. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/adapters/gemini.py +0 -0
  118. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/adapters/hermes.py +0 -0
  119. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/adapters/mcp_servers.py +0 -0
  120. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/adapters/opencode.py +0 -0
  121. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/adapters/opencode_artifacts.py +0 -0
  122. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/advisory_model.py +0 -0
  123. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/approvals.py +0 -0
  124. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/bridge/__init__.py +0 -0
  125. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/capabilities.py +0 -0
  126. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/cli/__init__.py +0 -0
  127. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/cli/approval_commands.py +0 -0
  128. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/cli/bootstrap.py +0 -0
  129. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/cli/connect_flow.py +0 -0
  130. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/cli/install_commands.py +0 -0
  131. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/cli/product.py +0 -0
  132. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/cli/prompt.py +0 -0
  133. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/cli/update_commands.py +0 -0
  134. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/codex_config.py +0 -0
  135. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/config.py +0 -0
  136. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/consumer/__init__.py +0 -0
  137. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/consumer/service.py +0 -0
  138. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/daemon/__init__.py +0 -0
  139. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/daemon/client.py +0 -0
  140. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/daemon/manager.py +0 -0
  141. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/daemon/server.py +0 -0
  142. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/daemon/static/apple-touch-icon.png +0 -0
  143. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/daemon/static/assets/guard-dashboard.js +0 -0
  144. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/daemon/static/assets/index.css +0 -0
  145. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Icon_Dark.png +0 -0
  146. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Whole.png +0 -0
  147. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/daemon/static/favicon-16x16.png +0 -0
  148. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/daemon/static/favicon-32x32.png +0 -0
  149. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/daemon/static/favicon.ico +0 -0
  150. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/daemon/static/index.html +0 -0
  151. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/edge_events.py +0 -0
  152. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/incident.py +0 -0
  153. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/launcher.py +0 -0
  154. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/mcp_tool_calls.py +0 -0
  155. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/models.py +0 -0
  156. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/policy/__init__.py +0 -0
  157. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/policy/engine.py +0 -0
  158. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/protect.py +0 -0
  159. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/proxy/__init__.py +0 -0
  160. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/proxy/remote.py +0 -0
  161. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/proxy/runtime_mcp.py +0 -0
  162. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/proxy/stdio.py +0 -0
  163. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/receipts/__init__.py +0 -0
  164. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/receipts/manager.py +0 -0
  165. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/risk.py +0 -0
  166. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/runtime/__init__.py +0 -0
  167. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/runtime/runner.py +0 -0
  168. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/runtime/surface_server.py +0 -0
  169. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/schemas/__init__.py +0 -0
  170. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/schemas/consumer_mode.py +0 -0
  171. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/schemas/guard_event_v1.py +0 -0
  172. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/schemas/surface_server.py +0 -0
  173. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/shims.py +0 -0
  174. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/store.py +0 -0
  175. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/store_approvals.py +0 -0
  176. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/store_connect.py +0 -0
  177. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/guard/types.py +0 -0
  178. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/integrations/__init__.py +0 -0
  179. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/integrations/cisco_mcp_scanner.py +0 -0
  180. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/integrations/cisco_skill_scanner.py +0 -0
  181. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/lint_fixes.py +0 -0
  182. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/marketplace_support.py +0 -0
  183. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/models.py +0 -0
  184. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/path_support.py +0 -0
  185. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/policy.py +0 -0
  186. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/quality_artifact.py +0 -0
  187. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/repo_detect.py +0 -0
  188. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/reporting.py +0 -0
  189. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/rules/__init__.py +0 -0
  190. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/rules/registry.py +0 -0
  191. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/rules/specs.py +0 -0
  192. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/scanner.py +0 -0
  193. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/submission.py +0 -0
  194. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/suppressions.py +0 -0
  195. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/trust_domain_scoring.py +0 -0
  196. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/trust_helpers.py +0 -0
  197. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/trust_mcp_scoring.py +0 -0
  198. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/trust_models.py +0 -0
  199. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/trust_plugin_scoring.py +0 -0
  200. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/trust_scoring.py +0 -0
  201. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/trust_skill_scoring.py +0 -0
  202. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/trust_specs.py +0 -0
  203. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/src/codex_plugin_scanner/verification.py +0 -0
  204. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/__init__.py +0 -0
  205. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/conftest.py +0 -0
  206. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/__init__.py +0 -0
  207. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/bad-plugin/.codex-plugin/plugin.json +0 -0
  208. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/bad-plugin/.mcp.json +0 -0
  209. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/bad-plugin/secrets.js +0 -0
  210. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/claude-plugin-good/.claude-plugin/plugin.json +0 -0
  211. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/claude-plugin-good/LICENSE +0 -0
  212. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/claude-plugin-good/README.md +0 -0
  213. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/claude-plugin-good/SECURITY.md +0 -0
  214. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/claude-plugin-good/hooks/hooks.json +0 -0
  215. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/claude-plugin-good/skills/example/SKILL.md +0 -0
  216. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/code-quality-bad/evil.js +0 -0
  217. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/code-quality-bad/inject.js +0 -0
  218. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/gemini-extension-good/GEMINI.md +0 -0
  219. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/gemini-extension-good/LICENSE +0 -0
  220. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/gemini-extension-good/README.md +0 -0
  221. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/gemini-extension-good/SECURITY.md +0 -0
  222. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/gemini-extension-good/commands/hello.toml +0 -0
  223. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/gemini-extension-good/gemini-extension.json +0 -0
  224. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/good-plugin/.codex-plugin/plugin.json +0 -0
  225. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/good-plugin/.codexignore +0 -0
  226. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/good-plugin/LICENSE +0 -0
  227. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/good-plugin/README.md +0 -0
  228. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/good-plugin/SECURITY.md +0 -0
  229. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/good-plugin/assets/icon.svg +0 -0
  230. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/good-plugin/assets/logo.svg +0 -0
  231. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/good-plugin/assets/screenshot.svg +0 -0
  232. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/good-plugin/skills/example/SKILL.md +0 -0
  233. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/guard-codex-malicious-mcp/.codex/config.toml +0 -0
  234. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/hermes-plugin-evil/config.yaml +0 -0
  235. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/hermes-plugin-evil/mcp_servers.json +0 -0
  236. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/hermes-plugin-evil/skills/security/malicious/SKILL.md +0 -0
  237. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/SKILL.md +0 -0
  238. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/references/api-setup.md +0 -0
  239. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/scripts/deploy.sh +0 -0
  240. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/hermes-plugin-evil/skills/utils/benign/SKILL.md +0 -0
  241. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/malformed-json/.codex-plugin/plugin.json +0 -0
  242. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/malicious-skill-plugin/.codex-plugin/plugin.json +0 -0
  243. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/malicious-skill-plugin/.codexignore +0 -0
  244. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/malicious-skill-plugin/LICENSE +0 -0
  245. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/malicious-skill-plugin/README.md +0 -0
  246. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/malicious-skill-plugin/SECURITY.md +0 -0
  247. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/malicious-skill-plugin/skills/leaky-skill/SKILL.md +0 -0
  248. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/mcp-canary-server.py +0 -0
  249. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/minimal-plugin/.codex-plugin/plugin.json +0 -0
  250. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/missing-fields/.codex-plugin/plugin.json +0 -0
  251. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/mit-license/LICENSE +0 -0
  252. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/multi-ecosystem-repo/codex-plugin/.codex-plugin/plugin.json +0 -0
  253. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/multi-ecosystem-repo/codex-plugin/LICENSE +0 -0
  254. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/multi-ecosystem-repo/codex-plugin/README.md +0 -0
  255. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/multi-ecosystem-repo/codex-plugin/SECURITY.md +0 -0
  256. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/multi-ecosystem-repo/gemini-ext/README.md +0 -0
  257. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/multi-ecosystem-repo/gemini-ext/gemini-extension.json +0 -0
  258. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/multi-plugin-repo/.agents/plugins/marketplace.json +0 -0
  259. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codex-plugin/plugin.json +0 -0
  260. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codexignore +0 -0
  261. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/LICENSE +0 -0
  262. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/README.md +0 -0
  263. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/SECURITY.md +0 -0
  264. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/skills/example/SKILL.md +0 -0
  265. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/.codex-plugin/plugin.json +0 -0
  266. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/skills/example/SKILL.md +0 -0
  267. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/no-version/.codex-plugin/plugin.json +0 -0
  268. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/opencode-good/.opencode/commands/hello.md +0 -0
  269. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/opencode-good/.opencode/plugins/example.ts +0 -0
  270. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/opencode-good/LICENSE +0 -0
  271. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/opencode-good/README.md +0 -0
  272. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/opencode-good/SECURITY.md +0 -0
  273. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/opencode-good/opencode.jsonc +0 -0
  274. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/skills-missing-dir/.codex-plugin/plugin.json +0 -0
  275. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/skills-no-frontmatter/.codex-plugin/plugin.json +0 -0
  276. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/skills-no-frontmatter/skills/bad-skill/SKILL.md +0 -0
  277. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/with-marketplace/.codex-plugin/plugin.json +0 -0
  278. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/with-marketplace/marketplace-broken.json +0 -0
  279. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/fixtures/with-marketplace/marketplace.json +0 -0
  280. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test-trust-scoring.py +0 -0
  281. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test-trust-specs.py +0 -0
  282. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_action_runner.py +0 -0
  283. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_best_practices.py +0 -0
  284. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_cli.py +0 -0
  285. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_code_quality.py +0 -0
  286. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_config.py +0 -0
  287. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_coverage_remaining.py +0 -0
  288. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_ecosystems.py +0 -0
  289. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_edge_cases.py +0 -0
  290. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_final_coverage.py +0 -0
  291. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_guard_approvals.py +0 -0
  292. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_guard_bootstrap.py +0 -0
  293. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_guard_capabilities.py +0 -0
  294. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_guard_claude_adapter.py +0 -0
  295. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_guard_cli.py +0 -0
  296. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_guard_codex_e2e.py +0 -0
  297. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_guard_codex_install.py +0 -0
  298. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_guard_codex_proxy.py +0 -0
  299. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_guard_config_paths.py +0 -0
  300. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_guard_connect_flow.py +0 -0
  301. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_guard_consumer_mode.py +0 -0
  302. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_guard_copilot_adapter.py +0 -0
  303. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_guard_copilot_proxy.py +0 -0
  304. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_guard_daemon_manager.py +0 -0
  305. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_guard_event_schema_v1.py +0 -0
  306. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_guard_events.py +0 -0
  307. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_guard_launch_env.py +0 -0
  308. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_guard_opencode_proxy.py +0 -0
  309. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_guard_product_flow.py +0 -0
  310. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_guard_store_migrations.py +0 -0
  311. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_guard_surface_server.py +0 -0
  312. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_guard_verdicts.py +0 -0
  313. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_hermes_adapter.py +0 -0
  314. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_integration.py +0 -0
  315. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_lint_fixes.py +0 -0
  316. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_live_cisco_smoke.py +0 -0
  317. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_manifest.py +0 -0
  318. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_marketplace.py +0 -0
  319. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_mcp_security.py +0 -0
  320. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_operational_security.py +0 -0
  321. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_policy.py +0 -0
  322. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_quality_artifact.py +0 -0
  323. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_rule_registry.py +0 -0
  324. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_scanner.py +0 -0
  325. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_schema_contracts.py +0 -0
  326. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_security.py +0 -0
  327. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_security_ops.py +0 -0
  328. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_skill_security.py +0 -0
  329. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_submission.py +0 -0
  330. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_trust_scoring.py +0 -0
  331. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_trust_specs.py +0 -0
  332. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_verification.py +0 -0
  333. {plugin_scanner-2.0.87 → plugin_scanner-2.0.89}/tests/test_versioning.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plugin-scanner
3
- Version: 2.0.87
3
+ Version: 2.0.89
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
@@ -857,9 +857,9 @@ linkify-it-py==2.1.0 \
857
857
  --hash=sha256:0d252c1594ecba2ecedc444053db5d3a9b7ec1b0dd929c8f1d74dce89f86c05e \
858
858
  --hash=sha256:43360231720999c10e9328dc3691160e27a718e280673d444c38d7d3aaa3b98b
859
859
  # via markdown-it-py
860
- litellm==1.83.14 \
861
- --hash=sha256:24aef9b47cdc424c833e32f3727f411741c690832cd1fe4405e0077144fe09c9 \
862
- --hash=sha256:92b11ba2a32cf80707ddf388d18526696c7999a21b418c5e3b6eda1243d2cfdb
860
+ litellm==1.83.0 \
861
+ --hash=sha256:860bebc76c4bb27b4cf90b4a77acd66dba25aced37e3db98750de8a1766bfb7a \
862
+ --hash=sha256:88c536d339248f3987571493015784671ba3f193a328e1ea6780dbebaa2094a8
863
863
  # via
864
864
  # cisco-ai-mcp-scanner
865
865
  # cisco-ai-skill-scanner
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "plugin-scanner"
7
- version = "2.0.87"
7
+ version = "2.0.89"
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"
@@ -53,7 +53,7 @@ publish = [
53
53
  override-dependencies = [
54
54
  "click==8.1.8",
55
55
  "jsonschema==4.23.0",
56
- "litellm>=1.83.7",
56
+ "litellm==1.83.0",
57
57
  "openai==2.30.0",
58
58
  "python-dotenv>=1.2.2",
59
59
  "python-multipart>=0.0.26",
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "hol-guard"
7
- version = "2.0.87"
7
+ version = "2.0.89"
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"
@@ -53,7 +53,7 @@ publish = [
53
53
  override-dependencies = [
54
54
  "click==8.1.8",
55
55
  "jsonschema==4.23.0",
56
- "litellm>=1.83.7",
56
+ "litellm==1.83.0",
57
57
  "openai==2.30.0",
58
58
  "python-dotenv>=1.2.2",
59
59
  "python-multipart>=0.0.26",
@@ -1601,8 +1601,9 @@ def run_guard_command(
1601
1601
  }
1602
1602
  ]
1603
1603
  }
1604
+ browser_approval_daemon_client = None
1604
1605
  try:
1605
- daemon_client = load_guard_surface_daemon_client(guard_home)
1606
+ browser_approval_daemon_client = load_guard_surface_daemon_client(guard_home)
1606
1607
  except RuntimeError:
1607
1608
  queued = queue_blocked_approvals(
1608
1609
  detection=runtime_detection,
@@ -1612,7 +1613,7 @@ def run_guard_command(
1612
1613
  now=_now(),
1613
1614
  )
1614
1615
  else:
1615
- session = daemon_client.start_session(
1616
+ session = browser_approval_daemon_client.start_session(
1616
1617
  harness=args.harness,
1617
1618
  surface="harness-adapter",
1618
1619
  workspace=str(workspace) if workspace else None,
@@ -1622,7 +1623,7 @@ def run_guard_command(
1622
1623
  capabilities=["approval-resolution", "receipt-view"],
1623
1624
  )
1624
1625
  response_payload["session_id"] = str(session["session_id"])
1625
- blocked_operation = daemon_client.queue_blocked_operation(
1626
+ blocked_operation = browser_approval_daemon_client.queue_blocked_operation(
1626
1627
  session_id=str(session["session_id"]),
1627
1628
  operation_type="tool_call",
1628
1629
  harness=args.harness,
@@ -1674,6 +1675,27 @@ def run_guard_command(
1674
1675
  output_stream=output_stream,
1675
1676
  )
1676
1677
  return 0
1678
+ codex_browser_decision = _codex_browser_approval_decision(
1679
+ args=args,
1680
+ event_name=event_name,
1681
+ policy_action=policy_action,
1682
+ response_payload=response_payload,
1683
+ store=store,
1684
+ config=config,
1685
+ daemon_client=locals().get("browser_approval_daemon_client"),
1686
+ )
1687
+ if codex_browser_decision == "allow":
1688
+ if event_name != "PreToolUse":
1689
+ _emit_native_hook_response(
1690
+ harness=args.harness,
1691
+ policy_action="allow",
1692
+ event_name=event_name,
1693
+ reason="",
1694
+ output_stream=output_stream,
1695
+ )
1696
+ return 0
1697
+ if codex_browser_decision == "block":
1698
+ policy_action = "block"
1677
1699
  if _should_emit_native_hook_exit_block(args, event_name=event_name, policy_action=policy_action):
1678
1700
  _emit_native_hook_block_stderr(
1679
1701
  _native_hook_reason_for_harness(
@@ -1920,6 +1942,72 @@ def _should_emit_native_hook_exit_block(args: argparse.Namespace, *, event_name:
1920
1942
  )
1921
1943
 
1922
1944
 
1945
+ def _codex_browser_approval_decision(
1946
+ *,
1947
+ args: argparse.Namespace,
1948
+ event_name: str,
1949
+ policy_action: str,
1950
+ response_payload: dict[str, object],
1951
+ store: GuardStore,
1952
+ config: GuardConfig,
1953
+ daemon_client: object | None = None,
1954
+ ) -> str | None:
1955
+ if _canonical_harness_name(args.harness) != "codex":
1956
+ return None
1957
+ if getattr(args, "json", False):
1958
+ return None
1959
+ if event_name not in {"PreToolUse", "PostToolUse", "UserPromptSubmit"}:
1960
+ return None
1961
+ if policy_action not in {"block", "sandbox-required", "require-reapproval"}:
1962
+ return None
1963
+ approval_requests = response_payload.get("approval_requests")
1964
+ if not isinstance(approval_requests, list):
1965
+ return None
1966
+ request_ids = [
1967
+ item["request_id"]
1968
+ for item in approval_requests
1969
+ if isinstance(item, dict) and isinstance(item.get("request_id"), str)
1970
+ ]
1971
+ if not request_ids:
1972
+ return None
1973
+ has_daemon_operation = isinstance(response_payload.get("operation_id"), str)
1974
+ wait_timeout_seconds = min(config.approval_wait_timeout_seconds, 25 if has_daemon_operation else 5)
1975
+ wait_result = wait_for_approval_requests(
1976
+ store=store,
1977
+ request_ids=request_ids,
1978
+ timeout_seconds=wait_timeout_seconds,
1979
+ )
1980
+ response_payload["approval_wait"] = wait_result
1981
+ if not bool(wait_result.get("resolved")):
1982
+ response_payload["review_hint"] = (
1983
+ "Approval is still pending in HOL Guard. Approve it in the browser, then retry the same Codex action."
1984
+ )
1985
+ return None
1986
+ resolved_items = [item for item in wait_result.get("items", []) if isinstance(item, dict)]
1987
+ if any(str(item.get("resolution_action")) == "block" for item in resolved_items):
1988
+ _update_codex_browser_operation_status(response_payload, daemon_client, "blocked")
1989
+ response_payload["review_hint"] = "Browser decision saved. HOL Guard kept this Codex action blocked."
1990
+ return "block"
1991
+ _update_codex_browser_operation_status(response_payload, daemon_client, "completed")
1992
+ response_payload["review_hint"] = "Approval received in HOL Guard. Codex is resuming this action."
1993
+ return "allow"
1994
+
1995
+
1996
+ def _update_codex_browser_operation_status(
1997
+ response_payload: dict[str, object],
1998
+ daemon_client: object | None,
1999
+ status: str,
2000
+ ) -> None:
2001
+ operation_id = response_payload.get("operation_id")
2002
+ if daemon_client is None or not isinstance(operation_id, str) or not operation_id:
2003
+ return
2004
+ update_operation_status = getattr(daemon_client, "update_operation_status", None)
2005
+ if not callable(update_operation_status):
2006
+ return
2007
+ with suppress(Exception):
2008
+ update_operation_status(operation_id=operation_id, status=status)
2009
+
2010
+
1923
2011
  def _should_emit_prequeue_native_hook_response(
1924
2012
  args: argparse.Namespace,
1925
2013
  *,
@@ -11,6 +11,8 @@ from collections.abc import Callable
11
11
  from pathlib import Path
12
12
  from typing import Any
13
13
 
14
+ from ..redaction import redact_text
15
+
14
16
  try:
15
17
  from rich import box
16
18
  from rich.console import Console
@@ -39,9 +41,18 @@ _SAFE_POLICY_LITERALS = frozenset(
39
41
  {"allow", "warn", "review", "block", "require-reapproval", "sandbox-required", "strict", "balanced", "custom"}
40
42
  )
41
43
  _SENSITIVE_STRING_PATTERNS: tuple[tuple[re.Pattern[str], str], ...] = (
44
+ (
45
+ re.compile(
46
+ r"-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----.*?-----END [A-Z0-9 ]*PRIVATE KEY-----",
47
+ re.DOTALL,
48
+ ),
49
+ "*****",
50
+ ),
42
51
  (re.compile(r"(?i)(authorization:\s*)(bearer\s+)?[^\s,;]+"), r"\1*****"),
43
52
  (re.compile(r"(?i)(api[-_ ]?key:\s*)[^\s,;]+"), r"\1*****"),
44
53
  (re.compile(r"(?i)(bearer\s+)[^\s,;]+"), r"\1*****"),
54
+ (re.compile(r"(?im)\b(?:_authToken|npm[_ -]?token)\s*[:=]\s*[^\s]+"), "npm token redacted"),
55
+ (re.compile(r"\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis|amqp)://[^\s]+", re.IGNORECASE), "*****"),
45
56
  (
46
57
  re.compile(
47
58
  r"(?i)([a-z0-9_-]*(?:token|secret|api[-_]?key|password|credential)[a-z0-9_-]*=)(?:'[^']*'|\"[^\"]*\"|[^&\s]+)"
@@ -54,12 +65,13 @@ _SENSITIVE_STRING_PATTERNS: tuple[tuple[re.Pattern[str], str], ...] = (
54
65
  def emit_guard_payload(command: str, payload: dict[str, object], as_json: bool) -> None:
55
66
  """Render Guard payloads as JSON or human-friendly rich output."""
56
67
 
57
- redacted_payload = _redact_payload(payload)
58
68
  if as_json or not _RICH_AVAILABLE:
59
- sys.stdout.write(_render_redacted_json_payload(_json_payload_for_command(command, redacted_payload)))
69
+ redacted_output = redact_text(_safe_json_output_text(command, payload))
70
+ sys.stdout.write(redacted_output.text)
60
71
  sys.stdout.write("\n")
61
72
  return
62
73
 
74
+ redacted_payload = _redact_payload(payload)
63
75
  console = Console(file=sys.stdout, soft_wrap=True)
64
76
  renderer = _RENDERERS.get(command, _render_fallback)
65
77
  renderer(console, redacted_payload)
@@ -88,11 +100,21 @@ def _render_redacted_json_payload(redacted_payload: object) -> str:
88
100
  return _serialize_redacted_json(redacted_payload, indent=0)
89
101
 
90
102
 
91
- def _json_payload_for_command(command: str, redacted_payload: dict[str, object]) -> dict[str, object]:
103
+ def _safe_json_output_text(command: str, payload: dict[str, object]) -> str:
104
+ json_payload = _json_payload_for_command(command, payload)
105
+ sanitized_payload = _sanitize_payload_for_output(json_payload)
106
+ return _render_redacted_json_payload(sanitized_payload)
107
+
108
+
109
+ def _sanitize_payload_for_output(value: object) -> object:
110
+ return _redact_payload(value)
111
+
112
+
113
+ def _json_payload_for_command(command: str, payload: dict[str, object]) -> dict[str, object]:
92
114
  json_renderer = _JSON_RENDERERS.get(command)
93
115
  if json_renderer is None:
94
- return redacted_payload
95
- return json_renderer(redacted_payload)
116
+ return dict(payload)
117
+ return json_renderer(dict(payload))
96
118
 
97
119
 
98
120
  def _render_settings_json_payload(redacted_payload: dict[str, object]) -> dict[str, object]:
@@ -42,7 +42,7 @@ _REDACTION_PATTERNS: tuple[tuple[str, re.Pattern[str], str], ...] = (
42
42
  ),
43
43
  (
44
44
  "npm-token",
45
- re.compile(r"(?im)\b(_authToken|npm[_ -]?token)\s*[:=]\s*([^\s]+)"),
45
+ re.compile(r"(?im)\b(_authToken|npm[_ -]?token)\s*[:=]\s*([^\s\"',}]+)"),
46
46
  r"\1=*****",
47
47
  ),
48
48
  (
@@ -51,7 +51,7 @@ _REDACTION_PATTERNS: tuple[tuple[str, re.Pattern[str], str], ...] = (
51
51
  r"-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----.*?-----END [A-Z0-9 ]*PRIVATE KEY-----",
52
52
  re.DOTALL,
53
53
  ),
54
- "-----BEGIN PRIVATE KEY-----\n*****\n-----END PRIVATE KEY-----",
54
+ "*****",
55
55
  ),
56
56
  (
57
57
  "secret-env",
@@ -67,7 +67,7 @@ _REDACTION_PATTERNS: tuple[tuple[str, re.Pattern[str], str], ...] = (
67
67
  ),
68
68
  (
69
69
  "connection-string",
70
- re.compile(r"\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis|amqp)://[^\s]+", re.IGNORECASE),
70
+ re.compile(r"\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis|amqp)://[^\s\"',}]+", re.IGNORECASE),
71
71
  "*****",
72
72
  ),
73
73
  )
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import ast
6
6
  import base64
7
7
  import binascii
8
+ import contextlib
8
9
  import hashlib
9
10
  import json
10
11
  import os
@@ -2431,7 +2432,105 @@ def _runtime_read_roots(cwd: Path | None, home_dir: Path | None) -> tuple[Path,
2431
2432
 
2432
2433
 
2433
2434
  def _path_is_within_roots(path: Path, roots: tuple[Path, ...]) -> bool:
2434
- return any(path.is_relative_to(root) for root in roots)
2435
+ path_text = os.path.realpath(os.fspath(path))
2436
+ root_texts = _runtime_read_root_texts(roots)
2437
+ return any(_path_text_is_within_root_text(path_text, root_text) for root_text in root_texts)
2438
+
2439
+
2440
+ def _path_text_is_within_root(path_text: str, root: Path) -> bool:
2441
+ return _path_text_is_within_root_text(path_text, os.path.realpath(os.fspath(root)))
2442
+
2443
+
2444
+ def _path_text_is_within_root_text(path_text: str, root_text: str) -> bool:
2445
+ normalized_path_text = os.path.normcase(path_text)
2446
+ normalized_root_text = os.path.normcase(root_text)
2447
+ try:
2448
+ return os.path.commonpath((normalized_path_text, normalized_root_text)) == normalized_root_text
2449
+ except ValueError:
2450
+ return False
2451
+
2452
+
2453
+ def _runtime_read_root_texts(roots: tuple[Path, ...]) -> tuple[str, ...]:
2454
+ return tuple(os.path.realpath(os.fspath(root)) for root in roots)
2455
+
2456
+
2457
+ def _runtime_relative_parts(path_text: str, root_text: str) -> tuple[str, ...] | None:
2458
+ try:
2459
+ relative_text = os.path.relpath(path_text, root_text)
2460
+ except ValueError:
2461
+ return None
2462
+ if relative_text in {"", "."}:
2463
+ return None
2464
+ parts = Path(relative_text).parts
2465
+ if not parts or any(_runtime_relative_part_is_unsafe(part) for part in parts):
2466
+ return None
2467
+ return parts
2468
+
2469
+
2470
+ def _runtime_relative_part_is_unsafe(part: str) -> bool:
2471
+ if part in {"", ".", ".."}:
2472
+ return True
2473
+ separators = (os.sep, os.altsep) if os.altsep else (os.sep,)
2474
+ return any(separator in part for separator in separators)
2475
+
2476
+
2477
+ def _runtime_entry_name_matches(
2478
+ entry_name: str,
2479
+ requested_name: str,
2480
+ *,
2481
+ entry_path: str,
2482
+ requested_path: str,
2483
+ ) -> bool:
2484
+ if entry_name == requested_name or os.path.normcase(entry_name) == os.path.normcase(requested_name):
2485
+ return True
2486
+ if entry_name.casefold() != requested_name.casefold():
2487
+ return False
2488
+ try:
2489
+ return os.path.samefile(entry_path, requested_path)
2490
+ except OSError:
2491
+ return False
2492
+
2493
+
2494
+ def _runtime_entry_for_name(directory_text: str, requested_name: str) -> os.DirEntry[str] | None:
2495
+ requested_path = os.path.join(directory_text, requested_name)
2496
+ try:
2497
+ with os.scandir(directory_text) as entries:
2498
+ return next(
2499
+ (
2500
+ entry
2501
+ for entry in entries
2502
+ if _runtime_entry_name_matches(
2503
+ entry.name,
2504
+ requested_name,
2505
+ entry_path=entry.path,
2506
+ requested_path=requested_path,
2507
+ )
2508
+ ),
2509
+ None,
2510
+ )
2511
+ except OSError:
2512
+ return None
2513
+
2514
+
2515
+ def _runtime_file_entry_under_root(path_text: str, root_text: str) -> os.DirEntry[str] | None:
2516
+ relative_parts = _runtime_relative_parts(path_text, root_text)
2517
+ if relative_parts is None:
2518
+ return None
2519
+ current_dir_text = root_text
2520
+ for directory_name in relative_parts[:-1]:
2521
+ directory_entry = _runtime_entry_for_name(current_dir_text, directory_name)
2522
+ if directory_entry is None:
2523
+ return None
2524
+ try:
2525
+ directory_stat = directory_entry.stat(follow_symlinks=False)
2526
+ except OSError:
2527
+ return None
2528
+ if not stat.S_ISDIR(directory_stat.st_mode):
2529
+ return None
2530
+ current_dir_text = os.path.realpath(directory_entry.path)
2531
+ if not _path_text_is_within_root_text(current_dir_text, root_text):
2532
+ return None
2533
+ return _runtime_entry_for_name(current_dir_text, relative_parts[-1])
2435
2534
 
2436
2535
 
2437
2536
  def _resolved_runtime_path(
@@ -2449,29 +2548,55 @@ def _resolved_runtime_path(
2449
2548
  read_roots = allowed_roots or _runtime_read_roots(cwd, home_dir)
2450
2549
  if not read_roots:
2451
2550
  return None
2452
- try:
2453
- resolved_path = normalized_path.resolve(strict=False)
2454
- except OSError:
2551
+ path_text = os.path.realpath(os.fspath(normalized_path))
2552
+ root_texts = _runtime_read_root_texts(read_roots)
2553
+ if not any(_path_text_is_within_root_text(path_text, root_text) for root_text in root_texts):
2455
2554
  return None
2456
- return resolved_path if _path_is_within_roots(resolved_path, read_roots) else None
2555
+ return Path(path_text)
2457
2556
 
2458
2557
 
2459
2558
  def _read_small_runtime_text_file(path: Path, *, allowed_roots: tuple[Path, ...]) -> str | None:
2559
+ path_text = os.path.realpath(os.fspath(path))
2560
+ root_texts = _runtime_read_root_texts(allowed_roots)
2561
+ if not any(_path_text_is_within_root_text(path_text, root_text) for root_text in root_texts):
2562
+ return None
2563
+ runtime_entry = next(
2564
+ (
2565
+ entry
2566
+ for root_text in root_texts
2567
+ if _path_text_is_within_root_text(path_text, root_text)
2568
+ for entry in (_runtime_file_entry_under_root(path_text, root_text),)
2569
+ if entry is not None
2570
+ ),
2571
+ None,
2572
+ )
2573
+ if runtime_entry is None:
2574
+ return None
2575
+ open_flags = os.O_RDONLY
2576
+ nofollow_flag = getattr(os, "O_NOFOLLOW", 0)
2577
+ if isinstance(nofollow_flag, int):
2578
+ open_flags |= nofollow_flag
2460
2579
  try:
2461
- resolved_path = path.resolve(strict=True)
2580
+ entry_stat = runtime_entry.stat(follow_symlinks=False)
2462
2581
  except OSError:
2463
2582
  return None
2464
- if not _path_is_within_roots(resolved_path, allowed_roots):
2583
+ if not stat.S_ISREG(entry_stat.st_mode) or entry_stat.st_size > _MAX_DECODED_PAYLOAD_BYTES:
2465
2584
  return None
2466
2585
  try:
2467
- stat_result = resolved_path.stat()
2586
+ descriptor = os.open(runtime_entry.path, open_flags)
2468
2587
  except OSError:
2469
2588
  return None
2470
- if not stat.S_ISREG(stat_result.st_mode) or stat_result.st_size > _MAX_DECODED_PAYLOAD_BYTES:
2471
- return None
2472
2589
  try:
2473
- return resolved_path.read_text(encoding="utf-8")
2590
+ stat_result = os.fstat(descriptor)
2591
+ if not stat.S_ISREG(stat_result.st_mode) or stat_result.st_size > _MAX_DECODED_PAYLOAD_BYTES:
2592
+ os.close(descriptor)
2593
+ return None
2594
+ with os.fdopen(descriptor, encoding="utf-8") as runtime_file:
2595
+ content = runtime_file.read(_MAX_DECODED_PAYLOAD_BYTES + 1)
2596
+ return content if len(content) <= _MAX_DECODED_PAYLOAD_BYTES else None
2474
2597
  except (OSError, UnicodeDecodeError):
2598
+ with contextlib.suppress(OSError):
2599
+ os.close(descriptor)
2475
2600
  return None
2476
2601
 
2477
2602
 
@@ -2899,16 +3024,14 @@ def _contains_mutating_shell_redirection(parts: list[str]) -> bool:
2899
3024
  else:
2900
3025
  index += 1
2901
3026
  else:
2902
- match = re.fullmatch(r"(?P<prefix>[^<>\s]*?)(?P<fd>[0-2]?)(?P<op>>\||>>|>)(?P<target>.*)", token)
2903
- if match is None:
3027
+ redirection = _split_attached_redirection_token(token)
3028
+ if redirection is None:
2904
3029
  index += 1
2905
3030
  continue
2906
- prefix = match.group("prefix") or ""
3031
+ prefix, fd, _op, target = redirection
2907
3032
  if prefix.endswith("="):
2908
3033
  index += 1
2909
3034
  continue
2910
- fd = match.group("fd")
2911
- target = match.group("target")
2912
3035
  if target:
2913
3036
  index += 1
2914
3037
  elif index + 1 < len(parts):
@@ -2927,6 +3050,32 @@ def _contains_mutating_shell_redirection(parts: list[str]) -> bool:
2927
3050
  return False
2928
3051
 
2929
3052
 
3053
+ def _split_attached_redirection_token(token: str) -> tuple[str, str, str, str] | None:
3054
+ for index, character in enumerate(token):
3055
+ if character != ">":
3056
+ continue
3057
+ op = _attached_redirection_operator(token, index)
3058
+ prefix = token[:index]
3059
+ if any(character.isspace() or character in {"<", ">"} for character in prefix):
3060
+ continue
3061
+ target = token[index + len(op) :]
3062
+ fd = ""
3063
+ if prefix and prefix[-1] in {"0", "1", "2"}:
3064
+ fd = prefix[-1]
3065
+ prefix = prefix[:-1]
3066
+ return prefix, fd, op, target
3067
+ return None
3068
+
3069
+
3070
+ def _attached_redirection_operator(token: str, index: int) -> str:
3071
+ next_character = token[index + 1 : index + 2]
3072
+ if next_character == "|":
3073
+ return ">|"
3074
+ if next_character == ">":
3075
+ return ">>"
3076
+ return ">"
3077
+
3078
+
2930
3079
  def _normalized_redirect_target(target: str) -> str:
2931
3080
  return target.strip().strip(");,").strip("'\"")
2932
3081
 
@@ -1,3 +1,3 @@
1
1
  """Single source of truth for tool version."""
2
2
 
3
- __version__ = "2.0.87"
3
+ __version__ = "2.0.89"
@@ -27,7 +27,7 @@ def test_pyproject_keeps_cisco_mcp_scanner_optional() -> None:
27
27
  assert "cisco-ai-skill-scanner~=2.0.9" in dependency_entries
28
28
  assert "click==8.1.8" in override_entries
29
29
  assert "jsonschema==4.23.0" in override_entries
30
- assert "litellm>=1.83.7" in override_entries
30
+ assert "litellm==1.83.0" in override_entries
31
31
  assert "openai==2.30.0" in override_entries
32
32
  assert "python-dotenv>=1.2.2" in override_entries
33
33
  assert "python-multipart>=0.0.26" in override_entries
@@ -79,17 +79,7 @@ def test_repo_controlled_surfaces_prefer_cisco_extra_where_supported() -> None:
79
79
  source_copy_index = dockerfile.index("COPY src /app/src")
80
80
  assert requirements_copy_index < source_copy_index
81
81
  assert "cisco-ai-mcp-scanner==" in docker_requirements
82
- patched_litellm_versions = (
83
- "litellm==1.83.7",
84
- "litellm==1.83.8",
85
- "litellm==1.83.9",
86
- "litellm==1.83.10",
87
- "litellm==1.83.11",
88
- "litellm==1.83.12",
89
- "litellm==1.83.13",
90
- "litellm==1.83.14",
91
- )
92
- assert any(version in docker_requirements for version in patched_litellm_versions)
82
+ assert "litellm==1.83.0" in docker_requirements
93
83
  assert "python-dotenv==1.2.2" in docker_requirements
94
84
  assert "python-multipart==0.0.26" in docker_requirements
95
85
  assert "--hash=sha256:" in docker_requirements
@@ -104,4 +94,4 @@ def test_publish_workflow_builds_only_guard_and_scanner_packages() -> None:
104
94
  assert "Build codex compatibility alias" not in publish_workflow
105
95
  assert 'name = "codex-plugin-scanner"' not in publish_workflow
106
96
  assert 'codex-plugin-scanner = "codex_plugin_scanner.cli:main"' not in publish_workflow
107
- assert 'uv tool install codex-plugin-scanner==' not in publish_workflow
97
+ assert "uv tool install codex-plugin-scanner==" not in publish_workflow
@@ -873,6 +873,25 @@ class TestGuardProtect:
873
873
 
874
874
  assert output.text == " API_TOKEN=*****\n\tDATABASE_URL=*****\n"
875
875
 
876
+ def test_guard_redaction_preserves_serialized_json_delimiters(self) -> None:
877
+ payload = json.dumps(
878
+ {
879
+ "token": "_authToken:abcdefghi",
880
+ "dsn": "postgres://user:pass@db.internal/app",
881
+ "key": "-----BEGIN PRIVATE KEY-----\nsecret\n-----END PRIVATE KEY-----",
882
+ }
883
+ )
884
+
885
+ output = redact_text(payload)
886
+ redacted_payload = json.loads(output.text)
887
+
888
+ assert "abcdefghi" not in output.text
889
+ assert "pass" not in output.text
890
+ assert "secret" not in output.text
891
+ assert redacted_payload["token"] == "_authToken=*****"
892
+ assert redacted_payload["dsn"] == "*****"
893
+ assert redacted_payload["key"] == "*****"
894
+
876
895
  def test_guard_store_keeps_distinct_advisories_without_ids(self, tmp_path) -> None:
877
896
  store = GuardStore(tmp_path / "guard-home")
878
897
 
@@ -58,6 +58,74 @@ def test_guard_render_redacts_sensitive_values_before_rich_renderer(monkeypatch)
58
58
  assert payload["launch_summary"] == "api_key=***** token=*****"
59
59
 
60
60
 
61
+ def test_guard_json_render_redacts_after_command_specific_payload_shape(capsys) -> None:
62
+ emit_guard_payload(
63
+ "connect",
64
+ {
65
+ "connected": True,
66
+ "connect_url": "https://hol.org/guard/connect?token=super-secret-token",
67
+ "sync_url": "https://hol.org/api/guard/receipts/sync?api_key=super-secret-key",
68
+ "api_key": "super-secret-key",
69
+ },
70
+ True,
71
+ )
72
+
73
+ output = capsys.readouterr().out
74
+ rendered = json.loads(output)
75
+ assert "super-secret" not in output
76
+ assert rendered["connect_url"] == "https://hol.org/guard/connect?token=*****"
77
+ assert rendered["sync_url"] == "https://hol.org/api/guard/receipts/sync?api_key=*****"
78
+ assert rendered["api_key"] == "*****"
79
+
80
+
81
+ def test_guard_json_render_redacts_private_key_without_breaking_json(capsys) -> None:
82
+ private_key = (
83
+ "-----BEGIN PRIVATE KEY-----\n"
84
+ "super-secret-material\n"
85
+ "-----END PRIVATE KEY-----"
86
+ )
87
+
88
+ emit_guard_payload("status", {"details": private_key}, True)
89
+
90
+ output = capsys.readouterr().out
91
+ rendered = json.loads(output)
92
+ assert "super-secret-material" not in output
93
+ assert rendered["details"] == "*****"
94
+
95
+
96
+ def test_guard_json_render_redacts_token_lines_without_breaking_json(capsys) -> None:
97
+ emit_guard_payload("status", {"details": "_authToken:abcdefghi"}, True)
98
+
99
+ output = capsys.readouterr().out
100
+ rendered = json.loads(output)
101
+ assert "abcdefghi" not in output
102
+ assert rendered["details"] == "npm token redacted"
103
+
104
+
105
+ def test_guard_json_render_redacts_connection_string_without_breaking_json(capsys) -> None:
106
+ emit_guard_payload("status", {"details": "postgres://user:password@localhost/db"}, True)
107
+
108
+ output = capsys.readouterr().out
109
+ rendered = json.loads(output)
110
+ assert "password" not in output
111
+ assert rendered["details"] == "*****"
112
+
113
+
114
+ def test_guard_json_renderer_receives_payload_copy(monkeypatch, capsys) -> None:
115
+ def mutating_renderer(payload: dict[str, object]) -> dict[str, object]:
116
+ payload["api_key"] = "mutated-secret"
117
+ return payload
118
+
119
+ source_payload: dict[str, object] = {"api_key": "original-secret"}
120
+ monkeypatch.setitem(render._JSON_RENDERERS, "mutating", mutating_renderer)
121
+
122
+ emit_guard_payload("mutating", source_payload, True)
123
+
124
+ output = capsys.readouterr().out
125
+ assert source_payload["api_key"] == "original-secret"
126
+ assert "mutated-secret" not in output
127
+
128
+
61
129
  def test_guard_settings_json_omits_billing_flag(capsys) -> None:
62
130
  emit_guard_payload(
63
131
  "settings",