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.
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/PKG-INFO +1 -1
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/pyproject.toml +1 -1
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/pyproject.toml.bak +1 -1
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/codex.py +36 -12
- plugin_scanner-2.0.79/src/codex_plugin_scanner/guard/advisory_model.py +150 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/cli/commands.py +250 -10
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/protect.py +36 -13
- plugin_scanner-2.0.79/src/codex_plugin_scanner/guard/redaction.py +93 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/store.py +34 -2
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/version.py +1 -1
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_codex_install.py +81 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_protect.py +326 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_runtime.py +261 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.clusterfuzzlite/Dockerfile +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.clusterfuzzlite/build.sh +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.clusterfuzzlite/project.yaml +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.clusterfuzzlite/requirements-atheris.txt +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.dockerignore +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.github/CODEOWNERS +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.github/ISSUE_TEMPLATE/bug-report.yml +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.github/dependabot.yml +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.github/workflows/ci.yml +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.github/workflows/codeql.yml +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.github/workflows/dependabot-uv-lock.yml +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.github/workflows/fuzz.yml +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.github/workflows/harness-smoke.yml +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.github/workflows/publish.yml +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.github/workflows/scorecard.yml +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.gitignore +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/.pre-commit-hooks.yaml +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/CONTRIBUTING.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/Dockerfile +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/LICENSE +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/README.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/SECURITY.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/index.html +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/package.json +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/pnpm-lock.yaml +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/public/apple-touch-icon.png +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/public/brand/Logo_Icon_Dark.png +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/public/brand/Logo_Whole.png +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/public/favicon-16x16.png +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/public/favicon-32x32.png +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/public/favicon.ico +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/src/app.tsx +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/src/approval-center-layout.tsx +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/src/approval-center-primitives.tsx +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/src/approval-center-utils.ts +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/src/fleet-workspace.tsx +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/src/guard-api.ts +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/src/guard-demo.ts +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/src/guard-types.ts +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/src/main.tsx +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/src/receipts-workspace.tsx +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/src/runtime-overview.tsx +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/src/settings-workspace.tsx +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/src/styles.css +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/src/vite-env.d.ts +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/tsconfig.json +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/dashboard/vite.config.ts +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/docker-requirements.txt +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/docs/guard/approval-audit.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/docs/guard/architecture.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/docs/guard/get-started.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/docs/guard/harness-support.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/docs/guard/local-vs-cloud.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/docs/guard/testing-matrix.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/docs/trust/mcp-trust-draft.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/docs/trust/plugin-trust-draft.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/docs/trust/skill-trust-local.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/fuzzers/manifest_fuzzer.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/requirements.txt +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/schemas/plugin-quality.v1.json +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/schemas/scan-result.v1.json +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/schemas/verify-result.v1.json +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/__init__.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/action_runner.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/argparse_utils.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/__init__.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/best_practices.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/claude.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/code_quality.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/ecosystem_common.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/gemini.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/manifest.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/manifest_support.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/marketplace.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/mcp_security.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/opencode.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/operational_security.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/security.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/checks/skill_security.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/cli.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/cli_ui.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/config.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/ecosystems/__init__.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/ecosystems/base.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/ecosystems/claude.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/ecosystems/codex.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/ecosystems/detect.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/ecosystems/gemini.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/ecosystems/opencode.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/ecosystems/registry.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/ecosystems/types.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/github_reporting.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/__init__.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/__init__.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/antigravity.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/base.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/claude_code.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/copilot.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/cursor.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/gemini.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/hermes.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/mcp_servers.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/opencode.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/opencode_artifacts.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/approvals.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/bridge/__init__.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/capabilities.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/cli/__init__.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/cli/approval_commands.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/cli/bootstrap.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/cli/connect_flow.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/cli/install_commands.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/cli/product.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/cli/prompt.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/cli/render.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/cli/update_commands.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/codex_config.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/config.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/consumer/__init__.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/consumer/service.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/__init__.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/client.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/manager.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/server.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/static/apple-touch-icon.png +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/static/assets/guard-dashboard.js +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/static/assets/index.css +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Icon_Dark.png +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Whole.png +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/static/favicon-16x16.png +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/static/favicon-32x32.png +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/static/favicon.ico +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/daemon/static/index.html +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/edge_events.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/incident.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/launcher.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/mcp_tool_calls.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/models.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/policy/__init__.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/policy/engine.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/proxy/__init__.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/proxy/remote.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/proxy/runtime_mcp.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/proxy/stdio.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/receipts/__init__.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/receipts/manager.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/risk.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/runtime/__init__.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/runtime/runner.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/runtime/secret_file_requests.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/runtime/surface_server.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/schemas/__init__.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/schemas/consumer_mode.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/schemas/guard_event_v1.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/schemas/surface_server.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/shims.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/store_approvals.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/store_connect.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/types.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/integrations/__init__.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/integrations/cisco_mcp_scanner.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/integrations/cisco_skill_scanner.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/lint_fixes.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/marketplace_support.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/models.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/path_support.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/policy.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/quality_artifact.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/repo_detect.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/reporting.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/rules/__init__.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/rules/registry.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/rules/specs.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/scanner.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/submission.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/suppressions.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/trust_domain_scoring.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/trust_helpers.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/trust_mcp_scoring.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/trust_models.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/trust_plugin_scoring.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/trust_scoring.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/trust_skill_scoring.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/trust_specs.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/verification.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/__init__.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/conftest.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/__init__.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/bad-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/bad-plugin/.mcp.json +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/bad-plugin/secrets.js +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/claude-plugin-good/.claude-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/claude-plugin-good/LICENSE +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/claude-plugin-good/README.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/claude-plugin-good/SECURITY.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/claude-plugin-good/hooks/hooks.json +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/claude-plugin-good/skills/example/SKILL.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/code-quality-bad/evil.js +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/code-quality-bad/inject.js +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/gemini-extension-good/GEMINI.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/gemini-extension-good/LICENSE +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/gemini-extension-good/README.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/gemini-extension-good/SECURITY.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/gemini-extension-good/commands/hello.toml +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/gemini-extension-good/gemini-extension.json +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/good-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/good-plugin/.codexignore +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/good-plugin/LICENSE +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/good-plugin/README.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/good-plugin/SECURITY.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/good-plugin/assets/icon.svg +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/good-plugin/assets/logo.svg +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/good-plugin/assets/screenshot.svg +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/good-plugin/skills/example/SKILL.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/guard-codex-malicious-mcp/.codex/config.toml +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/hermes-plugin-evil/config.yaml +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/hermes-plugin-evil/mcp_servers.json +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/hermes-plugin-evil/skills/security/malicious/SKILL.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/SKILL.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/references/api-setup.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/scripts/deploy.sh +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/hermes-plugin-evil/skills/utils/benign/SKILL.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/malformed-json/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/malicious-skill-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/malicious-skill-plugin/.codexignore +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/malicious-skill-plugin/LICENSE +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/malicious-skill-plugin/README.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/malicious-skill-plugin/SECURITY.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/malicious-skill-plugin/skills/leaky-skill/SKILL.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/mcp-canary-server.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/minimal-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/missing-fields/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/mit-license/LICENSE +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/multi-ecosystem-repo/codex-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/multi-ecosystem-repo/codex-plugin/LICENSE +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/multi-ecosystem-repo/codex-plugin/README.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/multi-ecosystem-repo/codex-plugin/SECURITY.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/multi-ecosystem-repo/gemini-ext/README.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/multi-ecosystem-repo/gemini-ext/gemini-extension.json +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/multi-plugin-repo/.agents/plugins/marketplace.json +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codexignore +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/LICENSE +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/README.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/SECURITY.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/skills/example/SKILL.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/skills/example/SKILL.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/no-version/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/opencode-good/.opencode/commands/hello.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/opencode-good/.opencode/plugins/example.ts +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/opencode-good/LICENSE +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/opencode-good/README.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/opencode-good/SECURITY.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/opencode-good/opencode.jsonc +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/skills-missing-dir/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/skills-no-frontmatter/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/skills-no-frontmatter/skills/bad-skill/SKILL.md +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/with-marketplace/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/with-marketplace/marketplace-broken.json +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/fixtures/with-marketplace/marketplace.json +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test-trust-scoring.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test-trust-specs.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_action_runner.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_best_practices.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_cisco_install_surfaces.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_cli.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_code_quality.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_config.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_coverage_remaining.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_ecosystems.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_edge_cases.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_final_coverage.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_approvals.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_bootstrap.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_capabilities.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_claude_adapter.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_cli.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_codex_e2e.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_codex_proxy.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_config_paths.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_connect_flow.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_consumer_mode.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_copilot_adapter.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_copilot_proxy.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_daemon_manager.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_event_schema_v1.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_events.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_launch_env.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_opencode_proxy.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_product_flow.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_render.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_risk.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_store_migrations.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_surface_server.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_guard_verdicts.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_hermes_adapter.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_integration.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_lint_fixes.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_live_cisco_smoke.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_manifest.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_marketplace.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_mcp_security.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_operational_security.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_policy.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_quality_artifact.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_rule_registry.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_scanner.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_schema_contracts.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_security.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_security_ops.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_skill_security.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_submission.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_trust_scoring.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_trust_specs.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_verification.py +0 -0
- {plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/tests/test_versioning.py +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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"
|
{plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/adapters/codex.py
RENAMED
|
@@ -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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
and "
|
|
185
|
-
and "'
|
|
186
|
-
and "'
|
|
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
|
-
|
|
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}/")
|
{plugin_scanner-2.0.77 → plugin_scanner-2.0.79}/src/codex_plugin_scanner/guard/cli/commands.py
RENAMED
|
@@ -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 =
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
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 =
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
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,
|