plugin-scanner 2.0.77__tar.gz → 2.0.79__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.77 → plugin_scanner-2.0.79}/PKG-INFO +1 -1
  2. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/pyproject.toml +1 -1
  3. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/pyproject.toml.bak +1 -1
  4. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/codex.py +36 -12
  5. plugin_scanner-2.0.79/src/codex_plugin_scanner/guard/advisory_model.py +150 -0
  6. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/cli/commands.py +250 -10
  7. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/protect.py +36 -13
  8. plugin_scanner-2.0.79/src/codex_plugin_scanner/guard/redaction.py +93 -0
  9. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/store.py +34 -2
  10. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/version.py +1 -1
  11. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_codex_install.py +81 -0
  12. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_protect.py +326 -0
  13. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_runtime.py +261 -0
  14. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.clusterfuzzlite/Dockerfile +0 -0
  15. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.clusterfuzzlite/build.sh +0 -0
  16. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.clusterfuzzlite/project.yaml +0 -0
  17. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.clusterfuzzlite/requirements-atheris.txt +0 -0
  18. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.dockerignore +0 -0
  19. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.github/CODEOWNERS +0 -0
  20. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.github/ISSUE_TEMPLATE/bug-report.yml +0 -0
  21. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  22. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
  23. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.github/dependabot.yml +0 -0
  24. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.github/workflows/ci.yml +0 -0
  25. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.github/workflows/codeql.yml +0 -0
  26. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.github/workflows/dependabot-uv-lock.yml +0 -0
  27. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.github/workflows/fuzz.yml +0 -0
  28. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.github/workflows/harness-smoke.yml +0 -0
  29. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.github/workflows/publish.yml +0 -0
  30. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.github/workflows/scorecard.yml +0 -0
  31. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.gitignore +0 -0
  32. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.pre-commit-hooks.yaml +0 -0
  33. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/CONTRIBUTING.md +0 -0
  34. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/Dockerfile +0 -0
  35. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/LICENSE +0 -0
  36. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/README.md +0 -0
  37. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/SECURITY.md +0 -0
  38. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/index.html +0 -0
  39. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/package.json +0 -0
  40. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/pnpm-lock.yaml +0 -0
  41. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/public/apple-touch-icon.png +0 -0
  42. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/public/brand/Logo_Icon_Dark.png +0 -0
  43. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/public/brand/Logo_Whole.png +0 -0
  44. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/public/favicon-16x16.png +0 -0
  45. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/public/favicon-32x32.png +0 -0
  46. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/public/favicon.ico +0 -0
  47. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/src/app.tsx +0 -0
  48. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/src/approval-center-layout.tsx +0 -0
  49. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/src/approval-center-primitives.tsx +0 -0
  50. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/src/approval-center-utils.ts +0 -0
  51. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/src/fleet-workspace.tsx +0 -0
  52. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/src/guard-api.ts +0 -0
  53. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/src/guard-demo.ts +0 -0
  54. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/src/guard-types.ts +0 -0
  55. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/src/main.tsx +0 -0
  56. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/src/receipts-workspace.tsx +0 -0
  57. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/src/runtime-overview.tsx +0 -0
  58. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/src/settings-workspace.tsx +0 -0
  59. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/src/styles.css +0 -0
  60. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/src/vite-env.d.ts +0 -0
  61. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/tsconfig.json +0 -0
  62. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/vite.config.ts +0 -0
  63. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/docker-requirements.txt +0 -0
  64. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/docs/guard/approval-audit.md +0 -0
  65. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/docs/guard/architecture.md +0 -0
  66. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/docs/guard/get-started.md +0 -0
  67. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/docs/guard/harness-support.md +0 -0
  68. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/docs/guard/local-vs-cloud.md +0 -0
  69. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/docs/guard/testing-matrix.md +0 -0
  70. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/docs/trust/mcp-trust-draft.md +0 -0
  71. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/docs/trust/plugin-trust-draft.md +0 -0
  72. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/docs/trust/skill-trust-local.md +0 -0
  73. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/fuzzers/manifest_fuzzer.py +0 -0
  74. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/requirements.txt +0 -0
  75. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/schemas/plugin-quality.v1.json +0 -0
  76. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/schemas/scan-result.v1.json +0 -0
  77. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/schemas/verify-result.v1.json +0 -0
  78. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/__init__.py +0 -0
  79. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/action_runner.py +0 -0
  80. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/argparse_utils.py +0 -0
  81. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/__init__.py +0 -0
  82. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/best_practices.py +0 -0
  83. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/claude.py +0 -0
  84. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/code_quality.py +0 -0
  85. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/ecosystem_common.py +0 -0
  86. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/gemini.py +0 -0
  87. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/manifest.py +0 -0
  88. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/manifest_support.py +0 -0
  89. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/marketplace.py +0 -0
  90. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/mcp_security.py +0 -0
  91. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/opencode.py +0 -0
  92. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/operational_security.py +0 -0
  93. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/security.py +0 -0
  94. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/skill_security.py +0 -0
  95. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/cli.py +0 -0
  96. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/cli_ui.py +0 -0
  97. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/config.py +0 -0
  98. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/ecosystems/__init__.py +0 -0
  99. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/ecosystems/base.py +0 -0
  100. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/ecosystems/claude.py +0 -0
  101. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/ecosystems/codex.py +0 -0
  102. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/ecosystems/detect.py +0 -0
  103. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/ecosystems/gemini.py +0 -0
  104. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/ecosystems/opencode.py +0 -0
  105. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/ecosystems/registry.py +0 -0
  106. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/ecosystems/types.py +0 -0
  107. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/github_reporting.py +0 -0
  108. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/__init__.py +0 -0
  109. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/__init__.py +0 -0
  110. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/antigravity.py +0 -0
  111. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/base.py +0 -0
  112. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/claude_code.py +0 -0
  113. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/copilot.py +0 -0
  114. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/cursor.py +0 -0
  115. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/gemini.py +0 -0
  116. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/hermes.py +0 -0
  117. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/mcp_servers.py +0 -0
  118. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/opencode.py +0 -0
  119. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/opencode_artifacts.py +0 -0
  120. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/approvals.py +0 -0
  121. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/bridge/__init__.py +0 -0
  122. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/capabilities.py +0 -0
  123. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/cli/__init__.py +0 -0
  124. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/cli/approval_commands.py +0 -0
  125. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/cli/bootstrap.py +0 -0
  126. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/cli/connect_flow.py +0 -0
  127. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/cli/install_commands.py +0 -0
  128. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/cli/product.py +0 -0
  129. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/cli/prompt.py +0 -0
  130. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/cli/render.py +0 -0
  131. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/cli/update_commands.py +0 -0
  132. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/codex_config.py +0 -0
  133. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/config.py +0 -0
  134. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/consumer/__init__.py +0 -0
  135. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/consumer/service.py +0 -0
  136. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/__init__.py +0 -0
  137. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/client.py +0 -0
  138. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/manager.py +0 -0
  139. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/server.py +0 -0
  140. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/static/apple-touch-icon.png +0 -0
  141. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/static/assets/guard-dashboard.js +0 -0
  142. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/static/assets/index.css +0 -0
  143. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Icon_Dark.png +0 -0
  144. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Whole.png +0 -0
  145. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/static/favicon-16x16.png +0 -0
  146. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/static/favicon-32x32.png +0 -0
  147. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/static/favicon.ico +0 -0
  148. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/static/index.html +0 -0
  149. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/edge_events.py +0 -0
  150. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/incident.py +0 -0
  151. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/launcher.py +0 -0
  152. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/mcp_tool_calls.py +0 -0
  153. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/models.py +0 -0
  154. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/policy/__init__.py +0 -0
  155. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/policy/engine.py +0 -0
  156. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/proxy/__init__.py +0 -0
  157. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/proxy/remote.py +0 -0
  158. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/proxy/runtime_mcp.py +0 -0
  159. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/proxy/stdio.py +0 -0
  160. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/receipts/__init__.py +0 -0
  161. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/receipts/manager.py +0 -0
  162. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/risk.py +0 -0
  163. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/runtime/__init__.py +0 -0
  164. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/runtime/runner.py +0 -0
  165. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/runtime/secret_file_requests.py +0 -0
  166. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/runtime/surface_server.py +0 -0
  167. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/schemas/__init__.py +0 -0
  168. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/schemas/consumer_mode.py +0 -0
  169. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/schemas/guard_event_v1.py +0 -0
  170. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/schemas/surface_server.py +0 -0
  171. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/shims.py +0 -0
  172. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/store_approvals.py +0 -0
  173. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/store_connect.py +0 -0
  174. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/types.py +0 -0
  175. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/integrations/__init__.py +0 -0
  176. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/integrations/cisco_mcp_scanner.py +0 -0
  177. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/integrations/cisco_skill_scanner.py +0 -0
  178. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/lint_fixes.py +0 -0
  179. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/marketplace_support.py +0 -0
  180. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/models.py +0 -0
  181. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/path_support.py +0 -0
  182. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/policy.py +0 -0
  183. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/quality_artifact.py +0 -0
  184. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/repo_detect.py +0 -0
  185. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/reporting.py +0 -0
  186. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/rules/__init__.py +0 -0
  187. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/rules/registry.py +0 -0
  188. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/rules/specs.py +0 -0
  189. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/scanner.py +0 -0
  190. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/submission.py +0 -0
  191. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/suppressions.py +0 -0
  192. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/trust_domain_scoring.py +0 -0
  193. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/trust_helpers.py +0 -0
  194. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/trust_mcp_scoring.py +0 -0
  195. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/trust_models.py +0 -0
  196. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/trust_plugin_scoring.py +0 -0
  197. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/trust_scoring.py +0 -0
  198. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/trust_skill_scoring.py +0 -0
  199. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/trust_specs.py +0 -0
  200. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/verification.py +0 -0
  201. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/__init__.py +0 -0
  202. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/conftest.py +0 -0
  203. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/__init__.py +0 -0
  204. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/bad-plugin/.codex-plugin/plugin.json +0 -0
  205. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/bad-plugin/.mcp.json +0 -0
  206. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/bad-plugin/secrets.js +0 -0
  207. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/claude-plugin-good/.claude-plugin/plugin.json +0 -0
  208. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/claude-plugin-good/LICENSE +0 -0
  209. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/claude-plugin-good/README.md +0 -0
  210. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/claude-plugin-good/SECURITY.md +0 -0
  211. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/claude-plugin-good/hooks/hooks.json +0 -0
  212. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/claude-plugin-good/skills/example/SKILL.md +0 -0
  213. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/code-quality-bad/evil.js +0 -0
  214. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/code-quality-bad/inject.js +0 -0
  215. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/gemini-extension-good/GEMINI.md +0 -0
  216. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/gemini-extension-good/LICENSE +0 -0
  217. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/gemini-extension-good/README.md +0 -0
  218. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/gemini-extension-good/SECURITY.md +0 -0
  219. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/gemini-extension-good/commands/hello.toml +0 -0
  220. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/gemini-extension-good/gemini-extension.json +0 -0
  221. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/good-plugin/.codex-plugin/plugin.json +0 -0
  222. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/good-plugin/.codexignore +0 -0
  223. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/good-plugin/LICENSE +0 -0
  224. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/good-plugin/README.md +0 -0
  225. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/good-plugin/SECURITY.md +0 -0
  226. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/good-plugin/assets/icon.svg +0 -0
  227. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/good-plugin/assets/logo.svg +0 -0
  228. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/good-plugin/assets/screenshot.svg +0 -0
  229. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/good-plugin/skills/example/SKILL.md +0 -0
  230. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/guard-codex-malicious-mcp/.codex/config.toml +0 -0
  231. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/hermes-plugin-evil/config.yaml +0 -0
  232. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/hermes-plugin-evil/mcp_servers.json +0 -0
  233. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/hermes-plugin-evil/skills/security/malicious/SKILL.md +0 -0
  234. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/SKILL.md +0 -0
  235. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/references/api-setup.md +0 -0
  236. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/scripts/deploy.sh +0 -0
  237. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/hermes-plugin-evil/skills/utils/benign/SKILL.md +0 -0
  238. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/malformed-json/.codex-plugin/plugin.json +0 -0
  239. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/malicious-skill-plugin/.codex-plugin/plugin.json +0 -0
  240. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/malicious-skill-plugin/.codexignore +0 -0
  241. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/malicious-skill-plugin/LICENSE +0 -0
  242. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/malicious-skill-plugin/README.md +0 -0
  243. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/malicious-skill-plugin/SECURITY.md +0 -0
  244. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/malicious-skill-plugin/skills/leaky-skill/SKILL.md +0 -0
  245. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/mcp-canary-server.py +0 -0
  246. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/minimal-plugin/.codex-plugin/plugin.json +0 -0
  247. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/missing-fields/.codex-plugin/plugin.json +0 -0
  248. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/mit-license/LICENSE +0 -0
  249. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/multi-ecosystem-repo/codex-plugin/.codex-plugin/plugin.json +0 -0
  250. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/multi-ecosystem-repo/codex-plugin/LICENSE +0 -0
  251. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/multi-ecosystem-repo/codex-plugin/README.md +0 -0
  252. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/multi-ecosystem-repo/codex-plugin/SECURITY.md +0 -0
  253. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/multi-ecosystem-repo/gemini-ext/README.md +0 -0
  254. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/multi-ecosystem-repo/gemini-ext/gemini-extension.json +0 -0
  255. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/multi-plugin-repo/.agents/plugins/marketplace.json +0 -0
  256. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codex-plugin/plugin.json +0 -0
  257. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codexignore +0 -0
  258. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/LICENSE +0 -0
  259. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/README.md +0 -0
  260. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/SECURITY.md +0 -0
  261. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/skills/example/SKILL.md +0 -0
  262. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/.codex-plugin/plugin.json +0 -0
  263. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/skills/example/SKILL.md +0 -0
  264. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/no-version/.codex-plugin/plugin.json +0 -0
  265. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/opencode-good/.opencode/commands/hello.md +0 -0
  266. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/opencode-good/.opencode/plugins/example.ts +0 -0
  267. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/opencode-good/LICENSE +0 -0
  268. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/opencode-good/README.md +0 -0
  269. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/opencode-good/SECURITY.md +0 -0
  270. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/opencode-good/opencode.jsonc +0 -0
  271. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/skills-missing-dir/.codex-plugin/plugin.json +0 -0
  272. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/skills-no-frontmatter/.codex-plugin/plugin.json +0 -0
  273. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/skills-no-frontmatter/skills/bad-skill/SKILL.md +0 -0
  274. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/with-marketplace/.codex-plugin/plugin.json +0 -0
  275. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/with-marketplace/marketplace-broken.json +0 -0
  276. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/with-marketplace/marketplace.json +0 -0
  277. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test-trust-scoring.py +0 -0
  278. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test-trust-specs.py +0 -0
  279. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_action_runner.py +0 -0
  280. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_best_practices.py +0 -0
  281. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_cisco_install_surfaces.py +0 -0
  282. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_cli.py +0 -0
  283. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_code_quality.py +0 -0
  284. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_config.py +0 -0
  285. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_coverage_remaining.py +0 -0
  286. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_ecosystems.py +0 -0
  287. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_edge_cases.py +0 -0
  288. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_final_coverage.py +0 -0
  289. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_approvals.py +0 -0
  290. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_bootstrap.py +0 -0
  291. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_capabilities.py +0 -0
  292. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_claude_adapter.py +0 -0
  293. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_cli.py +0 -0
  294. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_codex_e2e.py +0 -0
  295. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_codex_proxy.py +0 -0
  296. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_config_paths.py +0 -0
  297. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_connect_flow.py +0 -0
  298. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_consumer_mode.py +0 -0
  299. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_copilot_adapter.py +0 -0
  300. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_copilot_proxy.py +0 -0
  301. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_daemon_manager.py +0 -0
  302. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_event_schema_v1.py +0 -0
  303. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_events.py +0 -0
  304. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_launch_env.py +0 -0
  305. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_opencode_proxy.py +0 -0
  306. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_product_flow.py +0 -0
  307. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_render.py +0 -0
  308. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_risk.py +0 -0
  309. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_store_migrations.py +0 -0
  310. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_surface_server.py +0 -0
  311. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_verdicts.py +0 -0
  312. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_hermes_adapter.py +0 -0
  313. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_integration.py +0 -0
  314. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_lint_fixes.py +0 -0
  315. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_live_cisco_smoke.py +0 -0
  316. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_manifest.py +0 -0
  317. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_marketplace.py +0 -0
  318. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_mcp_security.py +0 -0
  319. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_operational_security.py +0 -0
  320. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_policy.py +0 -0
  321. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_quality_artifact.py +0 -0
  322. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_rule_registry.py +0 -0
  323. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_scanner.py +0 -0
  324. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_schema_contracts.py +0 -0
  325. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_security.py +0 -0
  326. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_security_ops.py +0 -0
  327. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_skill_security.py +0 -0
  328. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_submission.py +0 -0
  329. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_trust_scoring.py +0 -0
  330. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_trust_specs.py +0 -0
  331. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_verification.py +0 -0
  332. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_versioning.py +0 -0
  333. {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plugin-scanner
3
- Version: 2.0.77
3
+ Version: 2.0.79
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.77"
7
+ version = "2.0.79"
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.77"
7
+ version = "2.0.79"
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"
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import hashlib
6
6
  import json
7
7
  import os
8
+ import re
8
9
  import shlex
9
10
  import sys
10
11
  from copy import deepcopy
@@ -43,11 +44,13 @@ def _read_toml(path: Path) -> dict[str, object]:
43
44
  _MANAGED_HOOK_STATUS_MESSAGE = "HOL Guard checking tool action"
44
45
  _MANAGED_PROMPT_HOOK_STATUS_MESSAGE = "HOL Guard checking prompt"
45
46
  _MANAGED_PERMISSION_HOOK_STATUS_MESSAGE = "HOL Guard checking Codex approval request"
47
+ _MANAGED_POST_TOOL_HOOK_STATUS_MESSAGE = "HOL Guard checking tool result"
46
48
  _LEGACY_MANAGED_HOOK_STATUS_MESSAGES = {
47
49
  "HOL Guard checking Bash command",
48
50
  _MANAGED_HOOK_STATUS_MESSAGE,
49
51
  _MANAGED_PROMPT_HOOK_STATUS_MESSAGE,
50
52
  _MANAGED_PERMISSION_HOOK_STATUS_MESSAGE,
53
+ _MANAGED_POST_TOOL_HOOK_STATUS_MESSAGE,
51
54
  }
52
55
 
53
56
 
@@ -113,7 +116,7 @@ def _pre_tool_hook_group(context: HarnessContext) -> dict[str, object]:
113
116
  {
114
117
  "type": "command",
115
118
  "command": _hook_command(context),
116
- "timeoutSec": 30,
119
+ "timeout": 30,
117
120
  "statusMessage": _MANAGED_HOOK_STATUS_MESSAGE,
118
121
  }
119
122
  ],
@@ -126,7 +129,7 @@ def _prompt_hook_group(context: HarnessContext) -> dict[str, object]:
126
129
  {
127
130
  "type": "command",
128
131
  "command": _hook_command(context),
129
- "timeoutSec": 30,
132
+ "timeout": 30,
130
133
  "statusMessage": _MANAGED_PROMPT_HOOK_STATUS_MESSAGE,
131
134
  }
132
135
  ],
@@ -140,18 +143,33 @@ def _permission_request_hook_group(context: HarnessContext) -> dict[str, object]
140
143
  {
141
144
  "type": "command",
142
145
  "command": _hook_command(context),
143
- "timeoutSec": 30,
146
+ "timeout": 30,
144
147
  "statusMessage": _MANAGED_PERMISSION_HOOK_STATUS_MESSAGE,
145
148
  }
146
149
  ],
147
150
  }
148
151
 
149
152
 
153
+ def _post_tool_hook_group(context: HarnessContext) -> dict[str, object]:
154
+ return {
155
+ "matcher": "Bash",
156
+ "hooks": [
157
+ {
158
+ "type": "command",
159
+ "command": _hook_command(context),
160
+ "timeout": 30,
161
+ "statusMessage": _MANAGED_POST_TOOL_HOOK_STATUS_MESSAGE,
162
+ }
163
+ ],
164
+ }
165
+
166
+
150
167
  def _managed_hook_groups(context: HarnessContext) -> dict[str, dict[str, object]]:
151
168
  return {
152
169
  "PreToolUse": _pre_tool_hook_group(context),
153
170
  "PermissionRequest": _permission_request_hook_group(context),
154
171
  "UserPromptSubmit": _prompt_hook_group(context),
172
+ "PostToolUse": _post_tool_hook_group(context),
155
173
  }
156
174
 
157
175
 
@@ -179,14 +197,13 @@ def _is_managed_hook_command(command: object) -> bool:
179
197
  if tokens[1] != "-c":
180
198
  return False
181
199
  code = tokens[2]
182
- return (
183
- "codex_plugin_scanner.cli" in code
184
- and "main([" in code
185
- and "'guard'" in code
186
- and "'hook'" in code
187
- and "'--harness'" in code
188
- and "'codex'" in code
200
+ has_guard_call = (
201
+ re.search(r"['\"]guard['\"]", code) is not None
202
+ and re.search(r"['\"]hook['\"]", code) is not None
203
+ and re.search(r"['\"]--harness['\"]", code) is not None
204
+ and re.search(r"['\"]codex['\"]", code) is not None
189
205
  )
206
+ return "codex_plugin_scanner.cli" in code and "main([" in code and has_guard_call
190
207
 
191
208
 
192
209
  def _argv_targets_codex(argv: list[str]) -> bool:
@@ -249,7 +266,7 @@ def _remove_hook_groups(groups: object) -> list[object]:
249
266
  def _remove_managed_hook_events(hooks: dict[str, object]) -> tuple[dict[str, object], bool]:
250
267
  updated_hooks = dict(hooks)
251
268
  changed = False
252
- for event_name in ("PreToolUse", "PermissionRequest", "UserPromptSubmit"):
269
+ for event_name in ("PreToolUse", "PermissionRequest", "UserPromptSubmit", "PostToolUse"):
253
270
  original_groups = deepcopy(updated_hooks.get(event_name))
254
271
  remaining = _remove_hook_groups(original_groups)
255
272
  managed_removed = isinstance(original_groups, list) and remaining != original_groups
@@ -273,6 +290,7 @@ def codex_native_hook_state(context: HarnessContext) -> dict[str, object]:
273
290
  pre_tool_groups = hooks.get("PreToolUse") if isinstance(hooks, dict) else None
274
291
  permission_groups = hooks.get("PermissionRequest") if isinstance(hooks, dict) else None
275
292
  prompt_groups = hooks.get("UserPromptSubmit") if isinstance(hooks, dict) else None
293
+ post_tool_groups = hooks.get("PostToolUse") if isinstance(hooks, dict) else None
276
294
  pre_tool_hook_installed = isinstance(pre_tool_groups, list) and any(
277
295
  _is_managed_hook_group(group) for group in pre_tool_groups
278
296
  )
@@ -282,7 +300,12 @@ def codex_native_hook_state(context: HarnessContext) -> dict[str, object]:
282
300
  prompt_hook_installed = isinstance(prompt_groups, list) and any(
283
301
  _is_managed_hook_group(group) for group in prompt_groups
284
302
  )
285
- managed_hook_installed = pre_tool_hook_installed and permission_hook_installed and prompt_hook_installed
303
+ post_tool_hook_installed = isinstance(post_tool_groups, list) and any(
304
+ _is_managed_hook_group(group) for group in post_tool_groups
305
+ )
306
+ managed_hook_installed = (
307
+ pre_tool_hook_installed and permission_hook_installed and prompt_hook_installed and post_tool_hook_installed
308
+ )
286
309
  return {
287
310
  "config_path": str(config_path),
288
311
  "config_present": config_path.is_file(),
@@ -292,6 +315,7 @@ def codex_native_hook_state(context: HarnessContext) -> dict[str, object]:
292
315
  "managed_pre_tool_hook_installed": pre_tool_hook_installed,
293
316
  "managed_permission_request_hook_installed": permission_hook_installed,
294
317
  "managed_prompt_hook_installed": prompt_hook_installed,
318
+ "managed_post_tool_hook_installed": post_tool_hook_installed,
295
319
  "managed_hook_installed": managed_hook_installed,
296
320
  "protection_active": isinstance(features, dict)
297
321
  and features.get("codex_hooks") is True
@@ -0,0 +1,150 @@
1
+ """Guard advisory identity helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from urllib.parse import urlsplit
7
+
8
+ _PACKAGE_URL_ECOSYSTEMS = {
9
+ "npm": "npm",
10
+ "pnpm": "npm",
11
+ "yarn": "npm",
12
+ "pip": "pypi",
13
+ "uv": "pypi",
14
+ "go": "golang",
15
+ }
16
+
17
+
18
+ @dataclass(frozen=True, slots=True)
19
+ class ProtectTargetIdentity:
20
+ """Subset of install target data advisory matching needs."""
21
+
22
+ artifact_id: str
23
+ artifact_name: str
24
+ ecosystem: str
25
+ package_name: str | None
26
+ package_url: str | None
27
+ source_url: str | None
28
+
29
+
30
+ def build_package_url(ecosystem: str, package_name: str | None, version: str | None) -> str | None:
31
+ """Build a simple purl-style identifier for registry package installs."""
32
+
33
+ if package_name is None:
34
+ return None
35
+ purl_type = _PACKAGE_URL_ECOSYSTEMS.get(ecosystem)
36
+ if purl_type is None:
37
+ return None
38
+ base = f"pkg:{purl_type}/{normalize_identity_value(package_name)}"
39
+ if version is None or not version.strip():
40
+ return base
41
+ return f"{base}@{version.strip()}"
42
+
43
+
44
+ def advisory_matches_target(advisory: dict[str, object], target: ProtectTargetIdentity) -> bool:
45
+ """Match advisories against install targets using stable identities first."""
46
+
47
+ advisory_id = advisory.get("artifact_id")
48
+ if isinstance(advisory_id, str) and advisory_id == target.artifact_id:
49
+ return True
50
+
51
+ advisory_ecosystem = advisory.get("ecosystem")
52
+ if isinstance(advisory_ecosystem, str) and advisory_ecosystem not in {target.ecosystem, "*"}:
53
+ return False
54
+
55
+ package_url = advisory.get("package_url")
56
+ if isinstance(package_url, str) and _package_url_matches(package_url, target.package_url):
57
+ return True
58
+
59
+ if _normalized_membership(advisory.get("aliases"), target.package_name, target.artifact_name):
60
+ return True
61
+
62
+ advisory_package = advisory.get("package") or advisory.get("name")
63
+ normalized_advisory_package = (
64
+ normalize_identity_value(advisory_package) if isinstance(advisory_package, str) else ""
65
+ )
66
+ if normalized_advisory_package != "" and normalized_advisory_package in {
67
+ normalize_identity_value(target.package_name),
68
+ normalize_identity_value(target.artifact_name),
69
+ }:
70
+ return True
71
+
72
+ publisher = advisory.get("publisher")
73
+ normalized_publisher = normalize_identity_value(publisher) if isinstance(publisher, str) else ""
74
+ if normalized_publisher != "" and normalized_publisher == normalize_identity_value(target.package_name):
75
+ return True
76
+
77
+ if _normalized_membership(advisory.get("publisher_identities"), target.package_name):
78
+ return True
79
+
80
+ if _endpoint_indicator_matches(advisory.get("endpoint_indicators"), target.source_url):
81
+ return True
82
+
83
+ advisory_source_url = advisory.get("source_url")
84
+ if not isinstance(advisory_source_url, str):
85
+ return False
86
+
87
+ normalized_advisory_source = _normalized_url_indicator(advisory_source_url)
88
+ normalized_target_source = _normalized_url_indicator(target.source_url)
89
+ return (
90
+ normalized_advisory_source != ""
91
+ and normalized_target_source != ""
92
+ and normalized_advisory_source == normalized_target_source
93
+ )
94
+
95
+
96
+ def normalize_identity_value(value: str | None) -> str:
97
+ return value.strip().lower() if isinstance(value, str) and value.strip() else ""
98
+
99
+
100
+ def _normalized_membership(values: object, *candidates: str | None) -> bool:
101
+ if not isinstance(values, list):
102
+ return False
103
+ normalized_values = {normalize_identity_value(item) for item in values if isinstance(item, str)}
104
+ normalized_candidates = {normalize_identity_value(candidate) for candidate in candidates if candidate is not None}
105
+ normalized_candidates.discard("")
106
+ return bool(normalized_values & normalized_candidates)
107
+
108
+
109
+ def _package_url_matches(advisory_url: str, target_url: str | None) -> bool:
110
+ if target_url is None:
111
+ return False
112
+ normalized_advisory = _package_url_base(advisory_url)
113
+ normalized_target = _package_url_base(target_url)
114
+ return normalized_advisory != "" and normalized_advisory == normalized_target
115
+
116
+
117
+ def _package_url_base(package_url: str) -> str:
118
+ normalized = normalize_identity_value(package_url)
119
+ for separator in ("?", "#"):
120
+ normalized = normalized.split(separator, 1)[0]
121
+ last_at = normalized.rfind("@")
122
+ if last_at == -1 or last_at < normalized.rfind("/"):
123
+ return normalized
124
+ return normalized[:last_at]
125
+
126
+
127
+ def _endpoint_indicator_matches(values: object, source_url: str | None) -> bool:
128
+ if source_url is None or not isinstance(values, list):
129
+ return False
130
+ normalized_source = _normalized_url_indicator(source_url)
131
+ return any(
132
+ isinstance(item, str) and _url_indicator_matches(normalized_source, _normalized_url_indicator(item))
133
+ for item in values
134
+ )
135
+
136
+
137
+ def _normalized_url_indicator(value: str | None) -> str:
138
+ if value is None:
139
+ return ""
140
+ parsed = urlsplit(value)
141
+ if not parsed.scheme or not parsed.netloc:
142
+ return normalize_identity_value(value)
143
+ path = parsed.path.rstrip("/")
144
+ return normalize_identity_value(f"{parsed.netloc}{path}")
145
+
146
+
147
+ def _url_indicator_matches(normalized_source: str, normalized_indicator: str) -> bool:
148
+ if normalized_source == "" or normalized_indicator == "":
149
+ return False
150
+ return normalized_source == normalized_indicator or normalized_source.startswith(f"{normalized_indicator}/")
@@ -285,6 +285,7 @@ def _configure_guard_parser(guard_parser: argparse.ArgumentParser) -> None:
285
285
  )
286
286
  _add_guard_common_args(protect_parser)
287
287
  protect_parser.add_argument("--dry-run", action="store_true")
288
+ protect_parser.add_argument("--unsafe-raw-output", action="store_true")
288
289
  protect_parser.add_argument("--json", action="store_true")
289
290
  protect_parser.add_argument("protect_command", nargs=argparse.REMAINDER)
290
291
 
@@ -659,6 +660,7 @@ def run_guard_command(
659
660
  workspace_dir=workspace or Path.cwd(),
660
661
  dry_run=bool(getattr(args, "dry_run", False)),
661
662
  now=_now(),
663
+ unsafe_raw_output=bool(getattr(args, "unsafe_raw_output", False)),
662
664
  )
663
665
  _emit("protect", payload, getattr(args, "json", False))
664
666
  return exit_code
@@ -1417,11 +1419,13 @@ def run_guard_command(
1417
1419
  artifact_id = runtime_artifact.artifact_id
1418
1420
  artifact_name = runtime_artifact.name
1419
1421
  policy_harness = _canonical_harness_name(args.harness)
1420
- stored_policy_action = store.resolve_policy(
1421
- policy_harness,
1422
- artifact_id,
1423
- runtime_artifact_hash,
1424
- str(runtime_workspace) if runtime_workspace else None,
1422
+ stored_policy_action = _runtime_stored_policy_action(
1423
+ store=store,
1424
+ harness=policy_harness,
1425
+ artifact=runtime_artifact,
1426
+ artifact_id=artifact_id,
1427
+ artifact_hash=runtime_artifact_hash,
1428
+ workspace=str(runtime_workspace) if runtime_workspace else None,
1425
1429
  )
1426
1430
  if stored_policy_action is None:
1427
1431
  legacy_artifact = _legacy_claude_alias_runtime_artifact(
@@ -1431,11 +1435,13 @@ def run_guard_command(
1431
1435
  workspace=runtime_workspace,
1432
1436
  )
1433
1437
  if legacy_artifact is not None:
1434
- stored_policy_action = store.resolve_policy(
1435
- args.harness,
1436
- legacy_artifact.artifact_id,
1437
- artifact_hash(legacy_artifact),
1438
- str(runtime_workspace) if runtime_workspace else None,
1438
+ stored_policy_action = _runtime_stored_policy_action(
1439
+ store=store,
1440
+ harness=args.harness,
1441
+ artifact=legacy_artifact,
1442
+ artifact_id=legacy_artifact.artifact_id,
1443
+ artifact_hash=artifact_hash(legacy_artifact),
1444
+ workspace=str(runtime_workspace) if runtime_workspace else None,
1439
1445
  )
1440
1446
  policy_action = _coalesce_string(
1441
1447
  getattr(args, "policy_action", None),
@@ -2769,6 +2775,37 @@ def _native_approval_center_context(response_payload: dict[str, object], *, harn
2769
2775
  )
2770
2776
 
2771
2777
 
2778
+ def _runtime_stored_policy_action(
2779
+ *,
2780
+ store: GuardStore,
2781
+ harness: str,
2782
+ artifact: GuardArtifact,
2783
+ artifact_id: str,
2784
+ artifact_hash: str,
2785
+ workspace: str | None,
2786
+ ) -> str | None:
2787
+ decision = store.resolve_policy_decision(
2788
+ harness,
2789
+ artifact_id,
2790
+ artifact_hash,
2791
+ workspace,
2792
+ artifact.publisher,
2793
+ )
2794
+ if decision is None:
2795
+ return None
2796
+ action = _optional_string(decision.get("action"))
2797
+ if action is None:
2798
+ return None
2799
+ scope = _optional_string(decision.get("scope"))
2800
+ if (
2801
+ action in {"allow", "warn", "review"}
2802
+ and scope in {"workspace", "publisher", "harness", "global"}
2803
+ and _runtime_artifact_risk_classes(artifact)
2804
+ ):
2805
+ return None
2806
+ return action
2807
+
2808
+
2772
2809
  def _runtime_artifact_policy_action(config: GuardConfig, artifact: GuardArtifact, harness: str) -> str:
2773
2810
  if _prompt_requires_hard_block(artifact):
2774
2811
  return "block"
@@ -2931,6 +2968,13 @@ def _runtime_artifact_native_reason(artifact: GuardArtifact, response_payload: d
2931
2968
  harness = response_payload.get("harness")
2932
2969
  prompt_classes = _prompt_request_classes(artifact)
2933
2970
  if harness == "codex" and "secret_read" in prompt_classes:
2971
+ prompt_summary = artifact.metadata.get("prompt_summary")
2972
+ if isinstance(prompt_summary, str) and "credential-looking local file" in prompt_summary:
2973
+ return (
2974
+ "HOL Guard stopped this Codex prompt before Codex could open a credential-looking local file. "
2975
+ "Codex does not expose native approval prompts for Read-tool file reads, so Guard blocks this "
2976
+ "request at prompt time."
2977
+ )
2934
2978
  return (
2935
2979
  "HOL Guard stopped this Codex prompt before Codex could open a sensitive local file. Codex does not "
2936
2980
  "expose native approval prompts for Read-tool file reads, so Guard blocks this request at prompt time."
@@ -3162,6 +3206,13 @@ def _emit_native_hook_response(
3162
3206
  if payload:
3163
3207
  _write_json_line(payload, output_stream=output_stream)
3164
3208
  return
3209
+ if event_name == "PostToolUse" and policy_action in {"block", "sandbox-required", "require-reapproval"}:
3210
+ payload["decision"] = "block"
3211
+ payload["reason"] = reason
3212
+ payload["continue"] = False
3213
+ payload["stopReason"] = reason
3214
+ _write_json_line(payload, output_stream=output_stream)
3215
+ return
3165
3216
  permission_decision = _native_hook_permission_decision(policy_action, harness=harness)
3166
3217
  if harness == "codex" and event_name == "PreToolUse" and permission_decision is None:
3167
3218
  return
@@ -3592,6 +3643,14 @@ def _hook_runtime_artifact(
3592
3643
  ) -> GuardArtifact | None:
3593
3644
  harness = _canonical_harness_name(harness)
3594
3645
  event_name = _hook_event_name(payload)
3646
+ if harness == "codex" and event_name == "PostToolUse":
3647
+ output_artifact = _codex_post_tool_output_artifact(
3648
+ payload=payload,
3649
+ config_path=str(_runtime_policy_path(harness, home_dir, workspace)),
3650
+ source_scope=_coalesce_string(payload.get("source_scope"), "project"),
3651
+ )
3652
+ if output_artifact is not None:
3653
+ return output_artifact
3595
3654
  if event_name == "UserPromptSubmit":
3596
3655
  prompt_text = payload.get("prompt")
3597
3656
  if isinstance(prompt_text, str) and prompt_text.strip():
@@ -3617,6 +3676,13 @@ def _hook_runtime_artifact(
3617
3676
  )
3618
3677
  if prompt_artifacts:
3619
3678
  return _merged_prompt_runtime_artifact(harness, prompt_artifacts)
3679
+ prompt_file_artifact = _codex_prompt_credential_file_artifact(
3680
+ prompt_text=prompt_text,
3681
+ cwd=workspace,
3682
+ config_path=config_path,
3683
+ )
3684
+ if prompt_file_artifact is not None:
3685
+ return prompt_file_artifact
3620
3686
  request = extract_sensitive_file_read_request(
3621
3687
  payload.get("tool_name"),
3622
3688
  payload.get("tool_input", payload.get("arguments")),
@@ -3648,6 +3714,180 @@ def _hook_runtime_artifact(
3648
3714
  )
3649
3715
 
3650
3716
 
3717
+ _CODEX_SECRET_OUTPUT_PATTERN = re.compile(
3718
+ r"(?i)(?:fake[_-]?credential|fake[_-]?secret|"
3719
+ r"(?:api[_-]?key|auth[_-]?token|credential|npm[_-]?token|private[_-]?key|secret|token|password)\s*[:=])"
3720
+ )
3721
+ _CODEX_TOOL_RESPONSE_MAX_DEPTH = 5
3722
+ _CODEX_TOOL_RESPONSE_TEXT_LIMIT = 20000
3723
+ _CODEX_PROMPT_FILE_FINGERPRINT_LENGTH = 24
3724
+
3725
+
3726
+ def _codex_post_tool_output_artifact(
3727
+ *,
3728
+ payload: dict[str, object],
3729
+ config_path: str,
3730
+ source_scope: str,
3731
+ ) -> GuardArtifact | None:
3732
+ response_text = _collect_codex_tool_response_text(payload.get("tool_response"))
3733
+ if not response_text or _CODEX_SECRET_OUTPUT_PATTERN.search(response_text) is None:
3734
+ return None
3735
+ tool_name = _coalesce_string(payload.get("tool_name"), "Bash")
3736
+ tool_input = payload.get("tool_input")
3737
+ command_text = ""
3738
+ if isinstance(tool_input, dict):
3739
+ command = tool_input.get("command")
3740
+ if isinstance(command, str):
3741
+ command_text = command.strip()
3742
+ if not command_text:
3743
+ command_text = tool_name
3744
+ fingerprint = hashlib.sha256(
3745
+ json.dumps(
3746
+ {
3747
+ "tool_name": tool_name,
3748
+ "command_text": command_text,
3749
+ "output_class": "credential-looking output",
3750
+ },
3751
+ sort_keys=True,
3752
+ ).encode("utf-8")
3753
+ ).hexdigest()
3754
+ return GuardArtifact(
3755
+ artifact_id=f"codex:{source_scope}:tool-output:{fingerprint}",
3756
+ name=f"{tool_name} credential-looking output",
3757
+ harness="codex",
3758
+ artifact_type="tool_action_request",
3759
+ source_scope=source_scope,
3760
+ config_path=config_path,
3761
+ metadata={
3762
+ "tool_name": tool_name,
3763
+ "command_text": command_text,
3764
+ "action_class": "credential exfiltration shell command",
3765
+ "request_summary": (
3766
+ f"Codex tool `{tool_name}` produced credential-looking output while running `{command_text}`."
3767
+ ),
3768
+ "runtime_request_signals": ["tool output contains credential-looking material"],
3769
+ "runtime_request_summary": (
3770
+ "Requests a sensitive native tool action: credential-looking output reached Codex."
3771
+ ),
3772
+ "runtime_request_reason": (
3773
+ "Guard inspects supported Codex tool output before Codex uses it, so accidental secret reads can be "
3774
+ "stopped even when the filename was not obviously sensitive."
3775
+ ),
3776
+ },
3777
+ )
3778
+
3779
+
3780
+ def _collect_codex_tool_response_text(value: object, *, depth: int = 0) -> str:
3781
+ if depth > _CODEX_TOOL_RESPONSE_MAX_DEPTH:
3782
+ return ""
3783
+ if isinstance(value, str):
3784
+ return value[:_CODEX_TOOL_RESPONSE_TEXT_LIMIT]
3785
+ if isinstance(value, dict):
3786
+ parts: list[str] = []
3787
+ for key, child in value.items():
3788
+ key_text = str(key).lower()
3789
+ if key_text in {"stdout", "stderr", "output", "text", "content", "result", "message"} or depth > 0:
3790
+ text = _collect_codex_tool_response_text(child, depth=depth + 1)
3791
+ if text:
3792
+ parts.append(text)
3793
+ return "\n".join(parts)[:_CODEX_TOOL_RESPONSE_TEXT_LIMIT]
3794
+ if isinstance(value, list):
3795
+ return "\n".join(_collect_codex_tool_response_text(item, depth=depth + 1) for item in value)[
3796
+ :_CODEX_TOOL_RESPONSE_TEXT_LIMIT
3797
+ ]
3798
+ return ""
3799
+
3800
+
3801
+ _PROMPT_PATH_TOKEN_PATTERN = re.compile(
3802
+ r"(?<![\w/.-])\.[A-Za-z0-9][A-Za-z0-9_.-]{0,255}|"
3803
+ r"(?:~|\.{1,2}|/)[^\s'\"`<>|;(){}\[\]]{0,255}"
3804
+ )
3805
+ _PROMPT_FILE_READ_VERB_PATTERN = re.compile(r"\b(?:read|open|print|show|dump|cat|head|tail|less|view|display)\b", re.I)
3806
+ _PROMPT_CONTENT_SCAN_MAX_BYTES = 64 * 1024
3807
+ _PROMPT_CONTENT_SCAN_SKIP_BASENAMES = frozenset(
3808
+ {
3809
+ ".env",
3810
+ ".npmrc",
3811
+ ".pypirc",
3812
+ ".netrc",
3813
+ ".git-credentials",
3814
+ }
3815
+ )
3816
+
3817
+
3818
+ def _codex_prompt_credential_file_artifact(
3819
+ *,
3820
+ prompt_text: str,
3821
+ cwd: Path | None,
3822
+ config_path: str,
3823
+ ) -> GuardArtifact | None:
3824
+ if _PROMPT_FILE_READ_VERB_PATTERN.search(prompt_text) is None:
3825
+ return None
3826
+ for match in _PROMPT_PATH_TOKEN_PATTERN.finditer(prompt_text):
3827
+ requested_path = match.group(0)
3828
+ path = _resolve_prompt_scan_path(requested_path, cwd=cwd)
3829
+ if path is None or path.name in _PROMPT_CONTENT_SCAN_SKIP_BASENAMES:
3830
+ continue
3831
+ if not path.name.startswith("."):
3832
+ continue
3833
+ if not path.is_file():
3834
+ continue
3835
+ try:
3836
+ with path.open("rb") as handle:
3837
+ content = handle.read(_PROMPT_CONTENT_SCAN_MAX_BYTES).decode("utf-8", errors="ignore")
3838
+ except OSError:
3839
+ continue
3840
+ if _CODEX_SECRET_OUTPUT_PATTERN.search(content) is None:
3841
+ continue
3842
+ normalized_path = str(path)
3843
+ fingerprint = hashlib.sha256(
3844
+ json.dumps(
3845
+ {
3846
+ "harness": "codex",
3847
+ "prompt_path": normalized_path,
3848
+ "content_class": "credential-looking local file",
3849
+ },
3850
+ sort_keys=True,
3851
+ ).encode("utf-8")
3852
+ ).hexdigest()[:_CODEX_PROMPT_FILE_FINGERPRINT_LENGTH]
3853
+ return GuardArtifact(
3854
+ artifact_id=f"codex:project:prompt-file:{fingerprint}",
3855
+ name=f"credential-looking local file {path.name}",
3856
+ harness="codex",
3857
+ artifact_type="prompt_request",
3858
+ source_scope="project",
3859
+ config_path=config_path,
3860
+ metadata={
3861
+ "prompt_signals": ["requested file content contains credential-looking material"],
3862
+ "prompt_summary": "Prompt asks Codex to read a credential-looking local file.",
3863
+ "prompt_matched_text": requested_path,
3864
+ "prompt_request_class": "secret_read",
3865
+ "prompt_request_classes": ["secret_read"],
3866
+ "runtime_request_summary": "Prompt requests direct access to a credential-looking local file.",
3867
+ "runtime_request_reason": (
3868
+ "Guard scanned a small local dotfile before Codex read it and found credential-looking text."
3869
+ ),
3870
+ "normalized_path": normalized_path,
3871
+ },
3872
+ )
3873
+ return None
3874
+
3875
+
3876
+ def _resolve_prompt_scan_path(requested_path: str, *, cwd: Path | None) -> Path | None:
3877
+ stripped = requested_path.strip().strip("'\"").rstrip(".,;:!?)]}")
3878
+ if not stripped:
3879
+ return None
3880
+ try:
3881
+ expanded = Path(stripped).expanduser()
3882
+ except RuntimeError:
3883
+ return None
3884
+ if not expanded.is_absolute():
3885
+ expanded = (cwd or Path.cwd()) / expanded
3886
+ with suppress(OSError):
3887
+ return expanded.resolve(strict=False)
3888
+ return expanded
3889
+
3890
+
3651
3891
  def _legacy_claude_alias_runtime_artifact(
3652
3892
  *,
3653
3893
  artifact: GuardArtifact,