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