plugin-scanner 2.0.112__tar.gz → 2.0.113__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.112 → plugin_scanner-2.0.113}/PKG-INFO +1 -1
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/pyproject.toml +1 -1
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/pyproject.toml.bak +1 -1
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/runtime/data_flow.py +26 -2
- plugin_scanner-2.0.113/src/codex_plugin_scanner/guard/runtime/data_flow_rules.py +472 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/runtime/detectors.py +20 -2
- plugin_scanner-2.0.113/src/codex_plugin_scanner/guard/runtime/secret_sources.py +99 -0
- plugin_scanner-2.0.113/src/codex_plugin_scanner/guard/runtime/shell_commands.py +202 -0
- plugin_scanner-2.0.113/src/codex_plugin_scanner/guard/runtime/temp_files.py +57 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/version.py +1 -1
- plugin_scanner-2.0.113/tests/test_guard_data_flow.py +331 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_runtime_detectors.py +25 -7
- plugin_scanner-2.0.112/tests/test_guard_data_flow.py +0 -117
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.clusterfuzzlite/Dockerfile +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.clusterfuzzlite/build.sh +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.clusterfuzzlite/project.yaml +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.clusterfuzzlite/requirements-atheris.txt +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.dockerignore +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.github/CODEOWNERS +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.github/ISSUE_TEMPLATE/bug-report.yml +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.github/dependabot.yml +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.github/workflows/ci.yml +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.github/workflows/codeql.yml +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.github/workflows/dependabot-uv-lock.yml +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.github/workflows/fuzz.yml +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.github/workflows/harness-smoke.yml +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.github/workflows/publish.yml +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.github/workflows/scorecard.yml +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.gitignore +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/.pre-commit-hooks.yaml +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/CONTRIBUTING.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/Dockerfile +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/LICENSE +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/README.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/SECURITY.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/index.html +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/package.json +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/pnpm-lock.yaml +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/public/apple-touch-icon.png +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/public/brand/Logo_Icon_Dark.png +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/public/brand/Logo_Whole.png +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/public/favicon-16x16.png +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/public/favicon-32x32.png +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/public/favicon.ico +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/src/app.tsx +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/src/approval-center-layout.tsx +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/src/approval-center-primitives.tsx +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/src/approval-center-utils.ts +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/src/fleet-workspace.tsx +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/src/guard-api.test.ts +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/src/guard-api.ts +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/src/guard-demo.ts +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/src/guard-types.ts +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/src/main.tsx +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/src/receipts-workspace.tsx +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/src/runtime-overview.tsx +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/src/settings-workspace.tsx +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/src/styles.css +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/src/vite-env.d.ts +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/tsconfig.json +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/dashboard/vite.config.ts +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/docker-requirements.txt +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/docs/guard/approval-audit.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/docs/guard/architecture.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/docs/guard/get-started.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/docs/guard/harness-support.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/docs/guard/local-vs-cloud.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/docs/guard/testing-matrix.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/docs/trust/mcp-trust-draft.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/docs/trust/plugin-trust-draft.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/docs/trust/skill-trust-local.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/fuzzers/manifest_fuzzer.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/requirements.txt +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/schemas/plugin-quality.v1.json +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/schemas/scan-result.v1.json +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/schemas/verify-result.v1.json +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/__init__.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/action_runner.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/argparse_utils.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/checks/__init__.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/checks/best_practices.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/checks/claude.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/checks/code_quality.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/checks/ecosystem_common.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/checks/gemini.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/checks/manifest.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/checks/manifest_support.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/checks/marketplace.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/checks/mcp_security.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/checks/opencode.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/checks/operational_security.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/checks/security.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/checks/skill_security.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/cli.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/cli_ui.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/config.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/ecosystems/__init__.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/ecosystems/base.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/ecosystems/claude.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/ecosystems/codex.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/ecosystems/detect.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/ecosystems/gemini.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/ecosystems/opencode.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/ecosystems/registry.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/ecosystems/types.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/github_reporting.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/__init__.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/__init__.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/antigravity.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/base.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/claude_code.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/cloud_identity.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/codex.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/copilot.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/cursor.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/gemini.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/hermes.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/mcp_servers.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/openclaw.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/openclaw_config.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/openclaw_support.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/opencode.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/adapters/opencode_artifacts.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/advisory_model.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/approvals.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/bridge/__init__.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/capabilities.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/cli/__init__.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/cli/approval_commands.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/cli/bootstrap.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/cli/commands.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/cli/connect_flow.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/cli/install_commands.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/cli/product.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/cli/prompt.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/cli/render.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/cli/update_commands.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/codex_config.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/config.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/consumer/__init__.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/consumer/service.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/daemon/__init__.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/daemon/client.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/daemon/manager.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/daemon/server.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/daemon/static/apple-touch-icon.png +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/daemon/static/assets/guard-dashboard.js +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/daemon/static/assets/index.css +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Icon_Dark.png +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Whole.png +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/daemon/static/favicon-16x16.png +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/daemon/static/favicon-32x32.png +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/daemon/static/favicon.ico +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/daemon/static/index.html +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/edge_events.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/incident.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/launcher.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/mcp_tool_calls.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/models.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/policy/__init__.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/policy/engine.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/protect.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/proxy/__init__.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/proxy/remote.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/proxy/runtime_mcp.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/proxy/stdio.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/receipts/__init__.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/receipts/manager.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/redaction.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/risk.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/runtime/__init__.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/runtime/actions.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/runtime/decisions.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/runtime/runner.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/runtime/secret_file_requests.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/runtime/secret_sensitivity.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/runtime/signals.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/runtime/surface_server.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/schemas/__init__.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/schemas/consumer_mode.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/schemas/guard_event_v1.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/schemas/surface_server.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/shims.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/store.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/store_approvals.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/store_connect.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/guard/types.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/integrations/__init__.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/integrations/cisco_mcp_scanner.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/integrations/cisco_skill_scanner.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/lint_fixes.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/marketplace_support.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/models.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/path_support.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/policy.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/quality_artifact.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/repo_detect.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/reporting.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/rules/__init__.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/rules/registry.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/rules/specs.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/scanner.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/submission.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/suppressions.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/trust_domain_scoring.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/trust_helpers.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/trust_mcp_scoring.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/trust_models.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/trust_plugin_scoring.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/trust_scoring.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/trust_skill_scoring.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/trust_specs.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/src/codex_plugin_scanner/verification.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/__init__.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/conftest.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/__init__.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/bad-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/bad-plugin/.mcp.json +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/bad-plugin/secrets.js +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/claude-plugin-good/.claude-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/claude-plugin-good/LICENSE +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/claude-plugin-good/README.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/claude-plugin-good/SECURITY.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/claude-plugin-good/hooks/hooks.json +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/claude-plugin-good/skills/example/SKILL.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/code-quality-bad/evil.js +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/code-quality-bad/inject.js +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/gemini-extension-good/GEMINI.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/gemini-extension-good/LICENSE +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/gemini-extension-good/README.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/gemini-extension-good/SECURITY.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/gemini-extension-good/commands/hello.toml +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/gemini-extension-good/gemini-extension.json +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/good-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/good-plugin/.codexignore +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/good-plugin/LICENSE +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/good-plugin/README.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/good-plugin/SECURITY.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/good-plugin/assets/icon.svg +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/good-plugin/assets/logo.svg +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/good-plugin/assets/screenshot.svg +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/good-plugin/skills/example/SKILL.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/guard-codex-malicious-mcp/.codex/config.toml +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/hermes-plugin-evil/config.yaml +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/hermes-plugin-evil/mcp_servers.json +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/hermes-plugin-evil/skills/security/malicious/SKILL.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/SKILL.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/references/api-setup.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/scripts/deploy.sh +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/hermes-plugin-evil/skills/utils/benign/SKILL.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/malformed-json/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/malicious-skill-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/malicious-skill-plugin/.codexignore +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/malicious-skill-plugin/LICENSE +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/malicious-skill-plugin/README.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/malicious-skill-plugin/SECURITY.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/malicious-skill-plugin/skills/leaky-skill/SKILL.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/mcp-canary-server.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/minimal-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/missing-fields/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/mit-license/LICENSE +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/multi-ecosystem-repo/codex-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/multi-ecosystem-repo/codex-plugin/LICENSE +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/multi-ecosystem-repo/codex-plugin/README.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/multi-ecosystem-repo/codex-plugin/SECURITY.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/multi-ecosystem-repo/gemini-ext/README.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/multi-ecosystem-repo/gemini-ext/gemini-extension.json +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/multi-plugin-repo/.agents/plugins/marketplace.json +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codexignore +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/LICENSE +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/README.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/SECURITY.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/skills/example/SKILL.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/skills/example/SKILL.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/no-version/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/opencode-good/.opencode/commands/hello.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/opencode-good/.opencode/plugins/example.ts +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/opencode-good/LICENSE +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/opencode-good/README.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/opencode-good/SECURITY.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/opencode-good/opencode.jsonc +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/skills-missing-dir/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/skills-no-frontmatter/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/skills-no-frontmatter/skills/bad-skill/SKILL.md +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/with-marketplace/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/with-marketplace/marketplace-broken.json +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/fixtures/with-marketplace/marketplace.json +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test-trust-scoring.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test-trust-specs.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_action_runner.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_best_practices.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_cisco_install_surfaces.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_cli.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_code_quality.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_config.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_coverage_remaining.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_ecosystems.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_edge_cases.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_final_coverage.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_access_graph.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_approvals.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_bootstrap.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_capabilities.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_claude_adapter.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_cli.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_codex_e2e.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_codex_install.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_codex_proxy.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_config_paths.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_connect_flow.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_consumer_mode.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_copilot_adapter.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_copilot_proxy.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_daemon_manager.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_event_schema_v1.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_events.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_launch_env.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_opencode_proxy.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_product_flow.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_protect.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_render.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_risk.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_runtime.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_runtime_action_harnesses.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_runtime_actions.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_runtime_decisions.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_runtime_signals.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_store_migrations.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_surface_server.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_guard_verdicts.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_hermes_adapter.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_integration.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_lint_fixes.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_live_cisco_smoke.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_manifest.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_marketplace.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_mcp_security.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_openclaw_adapter.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_operational_security.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_policy.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_quality_artifact.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_rule_registry.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_scanner.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_schema_contracts.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_security.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_security_ops.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_skill_security.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_submission.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_trust_scoring.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_trust_specs.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_verification.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/tests/test_versioning.py +0 -0
- {plugin_scanner-2.0.112 → plugin_scanner-2.0.113}/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.113
|
|
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.113"
|
|
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.113"
|
|
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"
|
|
@@ -207,6 +207,12 @@ def extract_pipes(command: str) -> tuple[ShellPipe, ...]:
|
|
|
207
207
|
return tuple(pipes)
|
|
208
208
|
|
|
209
209
|
|
|
210
|
+
def extract_command_segments(command: str) -> tuple[str, ...]:
|
|
211
|
+
"""Return top-level shell command segments split on command separators."""
|
|
212
|
+
|
|
213
|
+
return _split_top_level_commands(command)
|
|
214
|
+
|
|
215
|
+
|
|
210
216
|
def extract_http_methods(command: str) -> tuple[str, ...]:
|
|
211
217
|
"""Return explicit or strongly implied HTTP methods referenced by shell text."""
|
|
212
218
|
|
|
@@ -226,6 +232,15 @@ def extract_urls(command: str) -> tuple[str, ...]:
|
|
|
226
232
|
return _dedupe(url for url in urls if url)
|
|
227
233
|
|
|
228
234
|
|
|
235
|
+
def extract_url_ranges(command: str) -> tuple[tuple[int, int], ...]:
|
|
236
|
+
"""Return HTTP(S) URL character ranges in shell text."""
|
|
237
|
+
|
|
238
|
+
return tuple(
|
|
239
|
+
(match.start(), match.start() + len(_strip_url_suffix(match.group(0))))
|
|
240
|
+
for match in _URL_PATTERN.finditer(command)
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
|
|
229
244
|
def _append_http_method(methods: list[str], method: str) -> None:
|
|
230
245
|
normalized = method.upper()
|
|
231
246
|
if normalized in _HTTP_METHODS:
|
|
@@ -264,18 +279,27 @@ def _split_top_level_commands(command: str) -> tuple[str, ...]:
|
|
|
264
279
|
if next_index != index + 1:
|
|
265
280
|
index = next_index
|
|
266
281
|
continue
|
|
267
|
-
if state.is_top_level and command[index]
|
|
282
|
+
if state.is_top_level and command[index] in {";", "\n"}:
|
|
268
283
|
_append_segment(parts, command[start:index])
|
|
269
284
|
start = index + 1
|
|
270
285
|
elif state.is_top_level and (command.startswith("&&", index) or command.startswith("||", index)):
|
|
271
286
|
_append_segment(parts, command[start:index])
|
|
272
287
|
start = index + 2
|
|
273
288
|
index += 1
|
|
289
|
+
elif state.is_top_level and command[index] == "&" and _is_background_separator(command, index):
|
|
290
|
+
_append_segment(parts, command[start:index])
|
|
291
|
+
start = index + 1
|
|
274
292
|
index += 1
|
|
275
293
|
_append_segment(parts, command[start:])
|
|
276
294
|
return tuple(parts)
|
|
277
295
|
|
|
278
296
|
|
|
297
|
+
def _is_background_separator(command: str, index: int) -> bool:
|
|
298
|
+
previous_char = command[index - 1] if index > 0 else ""
|
|
299
|
+
next_char = command[index + 1] if index + 1 < len(command) else ""
|
|
300
|
+
return previous_char not in {">", "<", "|"} and next_char not in {"&", ">"}
|
|
301
|
+
|
|
302
|
+
|
|
279
303
|
def _split_top_level_pipes(command: str) -> tuple[str, ...]:
|
|
280
304
|
parts: list[str] = []
|
|
281
305
|
start = 0
|
|
@@ -291,7 +315,7 @@ def _split_top_level_pipes(command: str) -> tuple[str, ...]:
|
|
|
291
315
|
next_is_pipe = index + 1 < len(command) and command[index + 1] == "|"
|
|
292
316
|
if not previous_is_pipe and not next_is_pipe:
|
|
293
317
|
_append_segment(parts, command[start:index])
|
|
294
|
-
start = index + 1
|
|
318
|
+
start = index + 2 if index + 1 < len(command) and command[index + 1] == "&" else index + 1
|
|
295
319
|
index += 1
|
|
296
320
|
_append_segment(parts, command[start:])
|
|
297
321
|
return tuple(parts)
|
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
"""Data-flow exfiltration rules for Guard runtime shell actions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from collections.abc import Sequence
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from urllib.parse import urlparse
|
|
9
|
+
|
|
10
|
+
from codex_plugin_scanner.guard.runtime.actions import GuardActionEnvelope
|
|
11
|
+
from codex_plugin_scanner.guard.runtime.data_flow import (
|
|
12
|
+
ShellPipe,
|
|
13
|
+
extract_command_segments,
|
|
14
|
+
extract_command_substitutions,
|
|
15
|
+
extract_pipes,
|
|
16
|
+
extract_urls,
|
|
17
|
+
)
|
|
18
|
+
from codex_plugin_scanner.guard.runtime.secret_sensitivity import SecretPathMatch, classify_secret_path
|
|
19
|
+
from codex_plugin_scanner.guard.runtime.secret_sources import secret_path_matches_in_command, strip_shell_token
|
|
20
|
+
from codex_plugin_scanner.guard.runtime.shell_commands import (
|
|
21
|
+
command_execution_segments,
|
|
22
|
+
command_tokens_after_env_assignments,
|
|
23
|
+
git_remote_add_url_tokens,
|
|
24
|
+
is_scp_remote_target,
|
|
25
|
+
npm_publish_index,
|
|
26
|
+
npm_publish_is_dry_run,
|
|
27
|
+
scp_operands,
|
|
28
|
+
segment_executes_command,
|
|
29
|
+
)
|
|
30
|
+
from codex_plugin_scanner.guard.runtime.signals import RiskSignalCategory, RiskSignalV2
|
|
31
|
+
from codex_plugin_scanner.guard.runtime.temp_files import chmod_temp_targets, temp_write_targets
|
|
32
|
+
|
|
33
|
+
_CURL_DATA_FILE_PATTERN = re.compile(
|
|
34
|
+
r"(?s)(?:^|[\s;&|])(?i:curl|curl\.exe)\b.*?"
|
|
35
|
+
r"(?:(?:--data(?:-binary|-raw|-urlencode)?|-d)\s*@|--upload-file(?:=|\s+)|-T\s*)"
|
|
36
|
+
r"(?P<path>\"[^\"]+\"|'[^']+'|[^\s;&|]+)"
|
|
37
|
+
)
|
|
38
|
+
_CURL_DATA_STDIN_PATTERN = re.compile(
|
|
39
|
+
r"(?s)(?:^|[\s;&|])(?i:curl|curl\.exe)\b[^\r\n;&|]*?"
|
|
40
|
+
r"(?:(?:--data(?:-binary|-raw|-urlencode)?|-d)\s*@-|(?:--form|-F)(?:=|\s*)[^\s;&|]*@[.-](?=$|[\s;&|])|"
|
|
41
|
+
r"--upload-file(?:=|\s+)[.-](?=$|[\s;&|])|-T\s*[.-](?=$|[\s;&|]))"
|
|
42
|
+
)
|
|
43
|
+
_CURL_DATA_VALUE_PATTERN = re.compile(
|
|
44
|
+
r"(?s)(?:^|[\s;&|])(?i:curl|curl\.exe)\b[^\r\n;&|]*?"
|
|
45
|
+
r"(?:--data(?:-binary|-raw|-urlencode)?|-d)(?:=|\s*)"
|
|
46
|
+
r"(?P<value>\"[^\"]+\"|'[^']+'|[^\s;&|]+)"
|
|
47
|
+
)
|
|
48
|
+
_PYTHON_SECRET_POST_PATTERN = re.compile(
|
|
49
|
+
r"(?is)\bpython(?:3)?\b.*?-c\s+.*?"
|
|
50
|
+
r"(?:requests\.post|urllib\.request|http\.client).*?open\(['\"](?P<path>[^'\"]+)['\"]"
|
|
51
|
+
)
|
|
52
|
+
_NODE_SECRET_FETCH_PATTERN = re.compile(
|
|
53
|
+
r"(?is)\bnode\b.*?-e\s+.*?(?:fetch|axios\.post|https\.request|http\.request).*?"
|
|
54
|
+
r"readFileSync\(['\"](?P<path>[^'\"]+)['\"]"
|
|
55
|
+
)
|
|
56
|
+
_DNS_LONG_LABEL_PATTERN = re.compile(
|
|
57
|
+
r"(?i)(?:^|[\s;&|])(?:dig|nslookup|host)\s+"
|
|
58
|
+
r"(?P<host>(?:[A-Za-z0-9_-]+\.)*[A-Za-z0-9_-]{48,}(?:\.[A-Za-z0-9_-]+)*)"
|
|
59
|
+
)
|
|
60
|
+
_SCP_PATTERN = re.compile(r"(?is)(?:^|[\s;&|])scp\b(?P<body>[^\r\n;&|]+)")
|
|
61
|
+
_TOKEN_SOURCE_PATTERN = re.compile(r"(?i)\b(?:NPM_TOKEN|NODE_AUTH_TOKEN|_authToken|npm[_-]?token)\b")
|
|
62
|
+
_CLIPBOARD_COMMANDS = frozenset({"pbcopy", "xclip", "xsel", "wl-copy", "clip", "clip.exe"})
|
|
63
|
+
_WEBHOOK_HOST_PATTERN = re.compile(
|
|
64
|
+
r"webhook\.site|hooks\.slack\.com|discord\.com|pastebin\.com|gist\.github\.com|transfer\.sh|requestbin"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def detect_data_flow_exfiltration(
|
|
69
|
+
action: GuardActionEnvelope,
|
|
70
|
+
*,
|
|
71
|
+
workspace: Path | None,
|
|
72
|
+
) -> tuple[RiskSignalV2, ...]:
|
|
73
|
+
if action.action_type != "shell_command" or action.command is None:
|
|
74
|
+
return ()
|
|
75
|
+
command = action.command
|
|
76
|
+
findings: list[RiskSignalV2] = []
|
|
77
|
+
secret_matches = _secret_path_matches_in_command(command, workspace=workspace)
|
|
78
|
+
pipes = extract_pipes(command)
|
|
79
|
+
if _has_secret_pipe_to_http_upload(pipes, command, workspace=workspace):
|
|
80
|
+
findings.append(
|
|
81
|
+
_data_flow_signal(
|
|
82
|
+
"secret-pipe-http",
|
|
83
|
+
"Shell pipeline sends a local secret to a network host",
|
|
84
|
+
"This command pipes a local secret into an HTTP upload.",
|
|
85
|
+
"secret path and HTTP upload appear in the same pipe chain",
|
|
86
|
+
category="network",
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
if _curl_uploads_secret_file(command, workspace=workspace):
|
|
90
|
+
findings.append(
|
|
91
|
+
_data_flow_signal(
|
|
92
|
+
"curl-data-file",
|
|
93
|
+
"Curl uploads a local secret file",
|
|
94
|
+
"This command sends a local secret file as curl request data.",
|
|
95
|
+
"curl data flag references a sensitive local path",
|
|
96
|
+
category="network",
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
if _python_posts_secret(command, workspace=workspace):
|
|
100
|
+
findings.append(
|
|
101
|
+
_data_flow_signal(
|
|
102
|
+
"python-secret-post",
|
|
103
|
+
"Python posts a local secret",
|
|
104
|
+
"This Python snippet reads a local secret and posts it to a network host.",
|
|
105
|
+
"python -c combines a sensitive file read with an HTTP post",
|
|
106
|
+
category="network",
|
|
107
|
+
)
|
|
108
|
+
)
|
|
109
|
+
if _node_fetches_secret(command, workspace=workspace):
|
|
110
|
+
findings.append(
|
|
111
|
+
_data_flow_signal(
|
|
112
|
+
"node-secret-fetch",
|
|
113
|
+
"Node sends a local secret",
|
|
114
|
+
"This Node snippet reads a local secret and sends it to a network host.",
|
|
115
|
+
"node -e combines fs.readFileSync on a sensitive path with fetch/request",
|
|
116
|
+
category="network",
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
if _encoded_secret_send(command, secret_matches, workspace=workspace):
|
|
120
|
+
findings.append(
|
|
121
|
+
_data_flow_signal(
|
|
122
|
+
"encoded-secret-send",
|
|
123
|
+
"Encoded local secret is sent to a network host",
|
|
124
|
+
"This command encodes a local secret before sending it to a network host.",
|
|
125
|
+
"base64 appears between a sensitive path and HTTP upload",
|
|
126
|
+
category="network",
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
if _has_dns_exfil_hostname(command):
|
|
130
|
+
findings.append(
|
|
131
|
+
_data_flow_signal(
|
|
132
|
+
"dns-exfil",
|
|
133
|
+
"DNS query looks like encoded exfiltration",
|
|
134
|
+
"This command sends an unusually long encoded-looking DNS label.",
|
|
135
|
+
"DNS tool is called with a long encoded-looking label",
|
|
136
|
+
category="network",
|
|
137
|
+
)
|
|
138
|
+
)
|
|
139
|
+
if _has_webhook_exfil(command, workspace=workspace):
|
|
140
|
+
findings.append(
|
|
141
|
+
_data_flow_signal(
|
|
142
|
+
"webhook-sink",
|
|
143
|
+
"Local secret is sent to a public collection endpoint",
|
|
144
|
+
"This command targets a paste, gist, transfer, or webhook endpoint with local secret data.",
|
|
145
|
+
"known collection host appears with local secret source",
|
|
146
|
+
category="network",
|
|
147
|
+
)
|
|
148
|
+
)
|
|
149
|
+
if _scp_sends_secret(command, workspace=workspace):
|
|
150
|
+
findings.append(
|
|
151
|
+
_data_flow_signal(
|
|
152
|
+
"scp-secret",
|
|
153
|
+
"SCP sends a local secret file",
|
|
154
|
+
"This command copies a local secret file to a remote host.",
|
|
155
|
+
"scp command references a sensitive local source",
|
|
156
|
+
category="network",
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
if _git_remote_adds_token_url(command):
|
|
160
|
+
findings.append(
|
|
161
|
+
_data_flow_signal(
|
|
162
|
+
"git-remote-token",
|
|
163
|
+
"Git remote URL contains an access token",
|
|
164
|
+
"This command stores a token-bearing URL in git remote configuration.",
|
|
165
|
+
"git remote add URL includes credentials before host",
|
|
166
|
+
category="secret",
|
|
167
|
+
)
|
|
168
|
+
)
|
|
169
|
+
if _npm_publish_with_token_source(command, workspace=workspace):
|
|
170
|
+
findings.append(
|
|
171
|
+
_data_flow_signal(
|
|
172
|
+
"npm-publish-token-source",
|
|
173
|
+
"NPM publish uses local token material",
|
|
174
|
+
"This command publishes a package while local npm token material is in scope.",
|
|
175
|
+
"npm publish appears with npm token source evidence",
|
|
176
|
+
category="network",
|
|
177
|
+
)
|
|
178
|
+
)
|
|
179
|
+
if _clipboard_receives_secret(pipes, command, workspace=workspace):
|
|
180
|
+
findings.append(
|
|
181
|
+
_data_flow_signal(
|
|
182
|
+
"clipboard-secret",
|
|
183
|
+
"Clipboard receives a local secret",
|
|
184
|
+
"This command copies local secret contents into the clipboard.",
|
|
185
|
+
"clipboard command receives sensitive source through a pipe",
|
|
186
|
+
category="secret",
|
|
187
|
+
)
|
|
188
|
+
)
|
|
189
|
+
if _world_readable_temp_secret(command, workspace=workspace):
|
|
190
|
+
findings.append(
|
|
191
|
+
_data_flow_signal(
|
|
192
|
+
"world-readable-temp-secret",
|
|
193
|
+
"Local secret is written to a world-readable temp file",
|
|
194
|
+
"This command writes local secret contents to a world-readable temp file.",
|
|
195
|
+
"sensitive source is redirected to /tmp and chmod makes it world-readable",
|
|
196
|
+
category="secret",
|
|
197
|
+
)
|
|
198
|
+
)
|
|
199
|
+
return tuple(_dedupe_signals(findings))
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _secret_path_matches_in_command(command: str, *, workspace: Path | None) -> tuple[SecretPathMatch, ...]:
|
|
203
|
+
return secret_path_matches_in_command(command, workspace=workspace, extra_paths=_curl_data_file_paths(command))
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _curl_data_file_paths(command: str) -> tuple[str, ...]:
|
|
207
|
+
paths: list[str] = []
|
|
208
|
+
for segment in command_execution_segments(command):
|
|
209
|
+
if not segment_executes_command(segment, {"curl", "curl.exe"}):
|
|
210
|
+
continue
|
|
211
|
+
for match in _CURL_DATA_FILE_PATTERN.finditer(segment):
|
|
212
|
+
paths.append(strip_shell_token(match.group("path")))
|
|
213
|
+
return tuple(paths)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _has_secret_pipe_to_http_upload(pipes: Sequence[ShellPipe], command: str, *, workspace: Path | None) -> bool:
|
|
217
|
+
if not pipes or not _has_http_upload(command):
|
|
218
|
+
return False
|
|
219
|
+
return any(
|
|
220
|
+
extract_pipes(segment)
|
|
221
|
+
and _secret_path_matches_in_command(segment, workspace=workspace)
|
|
222
|
+
and _contains_http_upload_sink(segment)
|
|
223
|
+
for segment in extract_command_segments(command)
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _has_http_upload(command: str) -> bool:
|
|
228
|
+
return any(
|
|
229
|
+
segment_executes_command(segment, {"curl", "curl.exe"}) and _CURL_DATA_STDIN_PATTERN.search(segment)
|
|
230
|
+
for segment in command_execution_segments(command)
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _contains_http_upload_sink(command: str) -> bool:
|
|
235
|
+
return bool(extract_urls(command)) and _has_http_upload(command)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _curl_uploads_secret_file(command: str, *, workspace: Path | None) -> bool:
|
|
239
|
+
return any(
|
|
240
|
+
extract_urls(segment)
|
|
241
|
+
and any(classify_secret_path(path, cwd=workspace) is not None for path in _curl_data_file_paths(segment))
|
|
242
|
+
for segment in extract_command_segments(command)
|
|
243
|
+
) or _curl_data_substitution_reads_secret(command, workspace=workspace)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _curl_data_substitution_reads_secret(command: str, *, workspace: Path | None) -> bool:
|
|
247
|
+
for segment in command_execution_segments(command):
|
|
248
|
+
if not segment_executes_command(segment, {"curl", "curl.exe"}):
|
|
249
|
+
continue
|
|
250
|
+
if not extract_urls(segment):
|
|
251
|
+
continue
|
|
252
|
+
for match in _CURL_DATA_VALUE_PATTERN.finditer(segment):
|
|
253
|
+
value = match.group("value")
|
|
254
|
+
if not any(
|
|
255
|
+
_secret_path_matches_in_command(substitution, workspace=workspace)
|
|
256
|
+
for substitution in extract_command_substitutions(value)
|
|
257
|
+
):
|
|
258
|
+
continue
|
|
259
|
+
return True
|
|
260
|
+
return False
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _python_posts_secret(command: str, *, workspace: Path | None) -> bool:
|
|
264
|
+
for segment in extract_command_segments(command):
|
|
265
|
+
if (
|
|
266
|
+
segment_executes_command(segment, {"python", "python3"})
|
|
267
|
+
and extract_urls(segment)
|
|
268
|
+
and any(
|
|
269
|
+
classify_secret_path(match.group("path"), cwd=workspace) is not None
|
|
270
|
+
for match in _PYTHON_SECRET_POST_PATTERN.finditer(segment)
|
|
271
|
+
)
|
|
272
|
+
):
|
|
273
|
+
return True
|
|
274
|
+
return False
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _node_fetches_secret(command: str, *, workspace: Path | None) -> bool:
|
|
278
|
+
for segment in extract_command_segments(command):
|
|
279
|
+
if (
|
|
280
|
+
segment_executes_command(segment, {"node"})
|
|
281
|
+
and extract_urls(segment)
|
|
282
|
+
and any(
|
|
283
|
+
classify_secret_path(match.group("path"), cwd=workspace) is not None
|
|
284
|
+
for match in _NODE_SECRET_FETCH_PATTERN.finditer(segment)
|
|
285
|
+
)
|
|
286
|
+
):
|
|
287
|
+
return True
|
|
288
|
+
return False
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _encoded_secret_send(command: str, secret_matches: Sequence[SecretPathMatch], *, workspace: Path | None) -> bool:
|
|
292
|
+
if not secret_matches:
|
|
293
|
+
return False
|
|
294
|
+
return any(
|
|
295
|
+
_secret_path_matches_in_command(segment, workspace=workspace)
|
|
296
|
+
and "base64" in segment.lower()
|
|
297
|
+
and _has_http_upload(segment)
|
|
298
|
+
and bool(extract_urls(segment))
|
|
299
|
+
for segment in extract_command_segments(command)
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def _has_dns_exfil_hostname(command: str) -> bool:
|
|
304
|
+
return any(
|
|
305
|
+
segment_executes_command(segment, {"dig", "nslookup", "host"})
|
|
306
|
+
and any(_has_long_encoded_label(token) for token in _dns_query_tokens(segment))
|
|
307
|
+
for segment in extract_command_segments(command)
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _dns_query_tokens(segment: str) -> tuple[str, ...]:
|
|
312
|
+
tokens = command_tokens_after_env_assignments(segment)
|
|
313
|
+
return tuple(token.strip("'\"") for token in tokens[1:] if "." in token and not token.startswith(("-", "+", "@")))
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _has_long_encoded_label(host: str) -> bool:
|
|
317
|
+
return any(len(label) >= 48 for label in host.split("."))
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _has_webhook_sink(urls: Sequence[str]) -> bool:
|
|
321
|
+
for url in urls:
|
|
322
|
+
host = (urlparse(url).hostname or "").lower()
|
|
323
|
+
if _WEBHOOK_HOST_PATTERN.search(host):
|
|
324
|
+
return True
|
|
325
|
+
return False
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _has_webhook_exfil(command: str, *, workspace: Path | None) -> bool:
|
|
329
|
+
for segment in extract_command_segments(command):
|
|
330
|
+
if not _has_webhook_sink(extract_urls(segment)):
|
|
331
|
+
continue
|
|
332
|
+
if _secret_path_matches_in_command(segment, workspace=workspace):
|
|
333
|
+
return True
|
|
334
|
+
if _curl_uploads_secret_file(segment, workspace=workspace):
|
|
335
|
+
return True
|
|
336
|
+
if _has_secret_pipe_to_http_upload(extract_pipes(segment), segment, workspace=workspace):
|
|
337
|
+
return True
|
|
338
|
+
return False
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _scp_sends_secret(command: str, *, workspace: Path | None) -> bool:
|
|
342
|
+
for segment in extract_command_segments(command):
|
|
343
|
+
if not segment_executes_command(segment, {"scp"}):
|
|
344
|
+
continue
|
|
345
|
+
match = _SCP_PATTERN.search(segment)
|
|
346
|
+
if match is None:
|
|
347
|
+
continue
|
|
348
|
+
operands = scp_operands(match.group("body"))
|
|
349
|
+
if len(operands) < 2:
|
|
350
|
+
continue
|
|
351
|
+
target = operands[-1]
|
|
352
|
+
sources = operands[:-1]
|
|
353
|
+
if not is_scp_remote_target(target):
|
|
354
|
+
continue
|
|
355
|
+
if any(not is_scp_remote_target(source) and classify_secret_path(source, cwd=workspace) for source in sources):
|
|
356
|
+
return True
|
|
357
|
+
return False
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _git_remote_adds_token_url(command: str) -> bool:
|
|
361
|
+
for segment in extract_command_segments(command):
|
|
362
|
+
tokens = command_tokens_after_env_assignments(segment)
|
|
363
|
+
url_tokens = git_remote_add_url_tokens(tokens)
|
|
364
|
+
if not url_tokens:
|
|
365
|
+
continue
|
|
366
|
+
for url in extract_urls(" ".join(url_tokens)):
|
|
367
|
+
parsed = urlparse(url)
|
|
368
|
+
if parsed.username and _looks_like_token(parsed.username):
|
|
369
|
+
return True
|
|
370
|
+
if parsed.password and _looks_like_token(parsed.password):
|
|
371
|
+
return True
|
|
372
|
+
return False
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def _looks_like_token(value: str) -> bool:
|
|
376
|
+
lowered = value.lower()
|
|
377
|
+
return lowered.startswith(("ghp_", "github_pat_", "glpat-", "x-access-token")) or len(value) >= 24
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _npm_publish_with_token_source(command: str, *, workspace: Path | None) -> bool:
|
|
381
|
+
for segment in extract_command_segments(command):
|
|
382
|
+
tokens = command_tokens_after_env_assignments(segment)
|
|
383
|
+
publish_index = npm_publish_index(tokens)
|
|
384
|
+
if publish_index is None:
|
|
385
|
+
continue
|
|
386
|
+
if npm_publish_is_dry_run(tokens, publish_index):
|
|
387
|
+
continue
|
|
388
|
+
if _TOKEN_SOURCE_PATTERN.search(segment):
|
|
389
|
+
return True
|
|
390
|
+
if _has_npm_secret_match(_secret_path_matches_in_command(segment, workspace=workspace)):
|
|
391
|
+
return True
|
|
392
|
+
return False
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def _has_npm_secret_match(secret_matches: Sequence[SecretPathMatch]) -> bool:
|
|
396
|
+
return any("npm" in match.family.lower() or ".npmrc" in match.requested_path.lower() for match in secret_matches)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def _clipboard_receives_secret(pipes: Sequence[ShellPipe], command: str, *, workspace: Path | None) -> bool:
|
|
400
|
+
if not pipes:
|
|
401
|
+
return False
|
|
402
|
+
return any(
|
|
403
|
+
(segment_pipes := extract_pipes(segment))
|
|
404
|
+
and _secret_path_matches_in_command(segment, workspace=workspace)
|
|
405
|
+
and any(segment_executes_command(pipe.right, _CLIPBOARD_COMMANDS) for pipe in segment_pipes)
|
|
406
|
+
for segment in extract_command_segments(command)
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def _world_readable_temp_secret(command: str, *, workspace: Path | None) -> bool:
|
|
411
|
+
write_targets = {
|
|
412
|
+
target
|
|
413
|
+
for segment in extract_command_segments(command)
|
|
414
|
+
if _secret_path_matches_in_command(segment, workspace=workspace)
|
|
415
|
+
for target in temp_write_targets(segment)
|
|
416
|
+
}
|
|
417
|
+
if not write_targets:
|
|
418
|
+
return False
|
|
419
|
+
return any(
|
|
420
|
+
target in write_targets and _mode_makes_world_readable(mode) for target, mode in chmod_temp_targets(command)
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def _mode_makes_world_readable(mode: str) -> bool:
|
|
425
|
+
normalized = mode.lower()
|
|
426
|
+
if normalized.isdigit():
|
|
427
|
+
return normalized[-1] in {"4", "5", "6", "7"}
|
|
428
|
+
for clause in normalized.split(","):
|
|
429
|
+
if "+r" in clause:
|
|
430
|
+
who = clause.split("+", 1)[0]
|
|
431
|
+
if not who or "a" in who or "o" in who:
|
|
432
|
+
return True
|
|
433
|
+
if "=" in clause:
|
|
434
|
+
who, permissions = clause.split("=", 1)
|
|
435
|
+
if "r" in permissions and (not who or "a" in who or "o" in who):
|
|
436
|
+
return True
|
|
437
|
+
return False
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def _data_flow_signal(
|
|
441
|
+
signal_key: str,
|
|
442
|
+
title: str,
|
|
443
|
+
plain_reason: str,
|
|
444
|
+
technical_detail: str,
|
|
445
|
+
*,
|
|
446
|
+
category: RiskSignalCategory,
|
|
447
|
+
) -> RiskSignalV2:
|
|
448
|
+
return RiskSignalV2(
|
|
449
|
+
signal_id=f"data-flow:{signal_key}",
|
|
450
|
+
category=category,
|
|
451
|
+
severity="critical",
|
|
452
|
+
confidence="strong",
|
|
453
|
+
detector="data_flow.exfiltration",
|
|
454
|
+
title=title,
|
|
455
|
+
plain_reason=plain_reason,
|
|
456
|
+
technical_detail=technical_detail,
|
|
457
|
+
evidence_ref="command",
|
|
458
|
+
redaction_level="summary",
|
|
459
|
+
false_positive_hint="Allow only if this exact command intentionally moves non-sensitive local data.",
|
|
460
|
+
advisory_id=None,
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def _dedupe_signals(signals: Sequence[RiskSignalV2]) -> tuple[RiskSignalV2, ...]:
|
|
465
|
+
seen: set[str] = set()
|
|
466
|
+
result: list[RiskSignalV2] = []
|
|
467
|
+
for signal in signals:
|
|
468
|
+
if signal.signal_id in seen:
|
|
469
|
+
continue
|
|
470
|
+
seen.add(signal.signal_id)
|
|
471
|
+
result.append(signal)
|
|
472
|
+
return tuple(result)
|
|
@@ -10,6 +10,7 @@ from typing import Literal, Protocol
|
|
|
10
10
|
|
|
11
11
|
from codex_plugin_scanner.guard.config import GuardConfig
|
|
12
12
|
from codex_plugin_scanner.guard.runtime.actions import GuardActionEnvelope
|
|
13
|
+
from codex_plugin_scanner.guard.runtime.data_flow_rules import detect_data_flow_exfiltration
|
|
13
14
|
from codex_plugin_scanner.guard.runtime.secret_sensitivity import SecretPathMatch, classify_secret_path
|
|
14
15
|
from codex_plugin_scanner.guard.runtime.signals import RiskSignalCategory, RiskSignalV2
|
|
15
16
|
|
|
@@ -120,7 +121,7 @@ class DetectorRegistry:
|
|
|
120
121
|
if elapsed_ms > timeout_ms:
|
|
121
122
|
telemetry.append(_telemetry(detector, "timeout", elapsed_ms=elapsed_ms))
|
|
122
123
|
continue
|
|
123
|
-
signals.extend(detector_signals)
|
|
124
|
+
signals.extend(_filter_signals(detector_signals, category_filter))
|
|
124
125
|
telemetry.append(_telemetry(detector, "ok", elapsed_ms=elapsed_ms))
|
|
125
126
|
return DetectorRunResult(signals=tuple(signals), telemetry=tuple(telemetry))
|
|
126
127
|
|
|
@@ -136,8 +137,16 @@ class SecretPathDetector:
|
|
|
136
137
|
return tuple(_secret_path_signal(match, index=index) for index, match in enumerate(matches))
|
|
137
138
|
|
|
138
139
|
|
|
140
|
+
class DataFlowExfiltrationDetector:
|
|
141
|
+
detector_id = "data_flow.exfiltration"
|
|
142
|
+
categories: tuple[RiskSignalCategory, ...] = ("secret", "network")
|
|
143
|
+
|
|
144
|
+
def detect(self, action: GuardActionEnvelope, context: DetectorContext) -> tuple[RiskSignalV2, ...]:
|
|
145
|
+
return detect_data_flow_exfiltration(action, workspace=context.workspace)
|
|
146
|
+
|
|
147
|
+
|
|
139
148
|
def register_default_detectors() -> tuple[GuardDetector, ...]:
|
|
140
|
-
return (
|
|
149
|
+
return (DataFlowExfiltrationDetector(), SecretPathDetector())
|
|
141
150
|
|
|
142
151
|
|
|
143
152
|
def _secret_path_signal(match: SecretPathMatch, *, index: int) -> RiskSignalV2:
|
|
@@ -170,6 +179,15 @@ def _elapsed_ms(started_at: float, finished_at: float) -> int:
|
|
|
170
179
|
return max(0, round((finished_at - started_at) * 1000))
|
|
171
180
|
|
|
172
181
|
|
|
182
|
+
def _filter_signals(
|
|
183
|
+
signals: Sequence[RiskSignalV2],
|
|
184
|
+
category_filter: frozenset[RiskSignalCategory] | None,
|
|
185
|
+
) -> tuple[RiskSignalV2, ...]:
|
|
186
|
+
if category_filter is None:
|
|
187
|
+
return tuple(signals)
|
|
188
|
+
return tuple(signal for signal in signals if signal.category in category_filter)
|
|
189
|
+
|
|
190
|
+
|
|
173
191
|
def _slug(value: str) -> str:
|
|
174
192
|
return "-".join(part for part in value.lower().replace(".", " ").replace("/", " ").split() if part)
|
|
175
193
|
|