plugin-scanner 2.0.127__tar.gz → 2.0.128__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.127 → plugin_scanner-2.0.128}/PKG-INFO +1 -1
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/pyproject.toml +1 -1
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/pyproject.toml.bak +1 -1
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/cli/commands.py +54 -5
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/daemon/server.py +6 -4
- plugin_scanner-2.0.128/src/codex_plugin_scanner/guard/runtime/advisory_escalation.py +65 -0
- plugin_scanner-2.0.128/src/codex_plugin_scanner/guard/runtime/advisory_matchers.py +164 -0
- plugin_scanner-2.0.128/src/codex_plugin_scanner/guard/runtime/threat_intel.py +243 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/store.py +9 -0
- plugin_scanner-2.0.128/src/codex_plugin_scanner/guard/store_threat_intel.py +209 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/version.py +1 -1
- plugin_scanner-2.0.128/tests/test_guard_advisory_escalation.py +123 -0
- plugin_scanner-2.0.128/tests/test_guard_threat_intel.py +581 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.clusterfuzzlite/Dockerfile +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.clusterfuzzlite/build.sh +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.clusterfuzzlite/project.yaml +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.clusterfuzzlite/requirements-atheris.txt +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.dockerignore +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.github/CODEOWNERS +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.github/ISSUE_TEMPLATE/bug-report.yml +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.github/dependabot.yml +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.github/workflows/ci.yml +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.github/workflows/codeql.yml +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.github/workflows/dependabot-uv-lock.yml +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.github/workflows/fuzz.yml +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.github/workflows/harness-smoke.yml +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.github/workflows/publish.yml +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.github/workflows/scorecard.yml +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.gitignore +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/.pre-commit-hooks.yaml +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/CONTRIBUTING.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/Dockerfile +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/LICENSE +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/README.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/SECURITY.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/index.html +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/package.json +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/pnpm-lock.yaml +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/public/apple-touch-icon.png +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/public/brand/Logo_Icon_Dark.png +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/public/brand/Logo_Whole.png +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/public/favicon-16x16.png +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/public/favicon-32x32.png +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/public/favicon.ico +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/app.tsx +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/approval-center-layout.test.ts +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/approval-center-layout.tsx +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/approval-center-primitives.tsx +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/approval-center-review-cards.tsx +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/approval-center-utils.ts +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/data-flow-evidence-card.tsx +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/fleet-workspace.tsx +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/guard-api.test.ts +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/guard-api.ts +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/guard-demo.ts +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/guard-types.ts +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/main.tsx +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/receipts-workspace.tsx +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/runtime-overview.tsx +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/settings-workspace.tsx +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/styles.css +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/src/vite-env.d.ts +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/tsconfig.json +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/dashboard/vite.config.ts +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/docker-requirements.txt +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/docs/guard/approval-audit.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/docs/guard/architecture.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/docs/guard/get-started.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/docs/guard/harness-support.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/docs/guard/local-vs-cloud.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/docs/guard/testing-matrix.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/docs/trust/mcp-trust-draft.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/docs/trust/plugin-trust-draft.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/docs/trust/skill-trust-local.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/fuzzers/manifest_fuzzer.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/requirements.txt +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/schemas/plugin-quality.v1.json +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/schemas/scan-result.v1.json +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/schemas/verify-result.v1.json +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/__init__.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/action_runner.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/argparse_utils.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/checks/__init__.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/checks/best_practices.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/checks/claude.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/checks/code_quality.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/checks/ecosystem_common.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/checks/gemini.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/checks/manifest.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/checks/manifest_support.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/checks/marketplace.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/checks/mcp_security.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/checks/opencode.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/checks/operational_security.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/checks/security.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/checks/skill_security.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/cli.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/cli_ui.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/config.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/ecosystems/__init__.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/ecosystems/base.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/ecosystems/claude.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/ecosystems/codex.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/ecosystems/detect.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/ecosystems/gemini.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/ecosystems/opencode.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/ecosystems/registry.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/ecosystems/types.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/github_reporting.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/__init__.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/access_graph_events.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/__init__.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/antigravity.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/base.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/claude_code.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/cloud_identity.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/codex.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/copilot.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/cursor.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/gemini.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/hermes.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/mcp_servers.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/openclaw.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/openclaw_config.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/openclaw_support.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/opencode.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/adapters/opencode_artifacts.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/advisory_model.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/approvals.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/bridge/__init__.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/capabilities.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/cli/__init__.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/cli/approval_commands.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/cli/bootstrap.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/cli/connect_flow.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/cli/install_commands.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/cli/product.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/cli/prompt.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/cli/render.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/cli/update_commands.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/codex_config.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/config.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/consumer/__init__.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/consumer/service.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/daemon/__init__.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/daemon/client.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/daemon/manager.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/daemon/static/apple-touch-icon.png +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/daemon/static/assets/guard-dashboard.js +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/daemon/static/assets/index.css +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Icon_Dark.png +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Whole.png +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/daemon/static/favicon-16x16.png +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/daemon/static/favicon-32x32.png +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/daemon/static/favicon.ico +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/daemon/static/index.html +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/edge_events.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/incident.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/launcher.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/mcp_tool_calls.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/models.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/policy/__init__.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/policy/engine.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/protect.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/proxy/__init__.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/proxy/remote.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/proxy/runtime_mcp.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/proxy/stdio.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/receipts/__init__.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/receipts/manager.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/redaction.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/risk.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/__init__.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/actions.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/data_flow.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/data_flow_rules.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/data_flow_variables.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/decisions.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/detectors.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/mcp_protection.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/prompt_injection.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/runner.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/safe_decode.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/sandbox.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/secret_file_requests.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/secret_sensitivity.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/secret_sources.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/shell_commands.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/signals.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/skill_protection.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/supply_chain.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/surface_server.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/runtime/temp_files.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/schemas/__init__.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/schemas/consumer_mode.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/schemas/guard_event_v1.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/schemas/surface_server.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/shims.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/store_approvals.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/store_connect.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/store_evidence.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/types.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/integrations/__init__.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/integrations/cisco_mcp_scanner.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/integrations/cisco_skill_scanner.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/lint_fixes.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/marketplace_support.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/models.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/path_support.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/policy.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/quality_artifact.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/repo_detect.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/reporting.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/rules/__init__.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/rules/registry.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/rules/specs.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/scanner.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/submission.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/suppressions.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/trust_domain_scoring.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/trust_helpers.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/trust_mcp_scoring.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/trust_models.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/trust_plugin_scoring.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/trust_scoring.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/trust_skill_scoring.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/trust_specs.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/verification.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/__init__.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/conftest.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/__init__.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/bad-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/bad-plugin/.mcp.json +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/bad-plugin/secrets.js +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/claude-plugin-good/.claude-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/claude-plugin-good/LICENSE +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/claude-plugin-good/README.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/claude-plugin-good/SECURITY.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/claude-plugin-good/hooks/hooks.json +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/claude-plugin-good/skills/example/SKILL.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/code-quality-bad/evil.js +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/code-quality-bad/inject.js +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/gemini-extension-good/GEMINI.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/gemini-extension-good/LICENSE +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/gemini-extension-good/README.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/gemini-extension-good/SECURITY.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/gemini-extension-good/commands/hello.toml +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/gemini-extension-good/gemini-extension.json +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/good-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/good-plugin/.codexignore +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/good-plugin/LICENSE +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/good-plugin/README.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/good-plugin/SECURITY.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/good-plugin/assets/icon.svg +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/good-plugin/assets/logo.svg +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/good-plugin/assets/screenshot.svg +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/good-plugin/skills/example/SKILL.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/guard-codex-malicious-mcp/.codex/config.toml +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/hermes-plugin-evil/config.yaml +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/hermes-plugin-evil/mcp_servers.json +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/hermes-plugin-evil/skills/security/malicious/SKILL.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/SKILL.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/references/api-setup.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/scripts/deploy.sh +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/hermes-plugin-evil/skills/utils/benign/SKILL.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/malformed-json/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/malicious-skill-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/malicious-skill-plugin/.codexignore +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/malicious-skill-plugin/LICENSE +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/malicious-skill-plugin/README.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/malicious-skill-plugin/SECURITY.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/malicious-skill-plugin/skills/leaky-skill/SKILL.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/mcp-canary-server.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/minimal-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/missing-fields/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/mit-license/LICENSE +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/multi-ecosystem-repo/codex-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/multi-ecosystem-repo/codex-plugin/LICENSE +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/multi-ecosystem-repo/codex-plugin/README.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/multi-ecosystem-repo/codex-plugin/SECURITY.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/multi-ecosystem-repo/gemini-ext/README.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/multi-ecosystem-repo/gemini-ext/gemini-extension.json +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/multi-plugin-repo/.agents/plugins/marketplace.json +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codexignore +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/LICENSE +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/README.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/SECURITY.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/skills/example/SKILL.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/skills/example/SKILL.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/no-version/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/opencode-good/.opencode/commands/hello.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/opencode-good/.opencode/plugins/example.ts +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/opencode-good/LICENSE +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/opencode-good/README.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/opencode-good/SECURITY.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/opencode-good/opencode.jsonc +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/skills-missing-dir/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/skills-no-frontmatter/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/skills-no-frontmatter/skills/bad-skill/SKILL.md +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/supply-chain/benign-npm-package.json +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/supply-chain/benign-pnpm-package.json +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/supply-chain/benign-pyproject.toml +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/supply-chain/malicious-Dockerfile +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/supply-chain/malicious-action.yml +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/supply-chain/malicious-npm-package.json +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/supply-chain/malicious-setup.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/with-marketplace/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/with-marketplace/marketplace-broken.json +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/fixtures/with-marketplace/marketplace.json +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test-trust-scoring.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test-trust-specs.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_action_runner.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_best_practices.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_cisco_install_surfaces.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_cli.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_code_quality.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_config.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_coverage_remaining.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_ecosystems.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_edge_cases.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_final_coverage.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_access_graph.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_approval_store_scale.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_approvals.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_bootstrap.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_capabilities.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_claude_adapter.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_cli.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_cloud_local_sync.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_codex_e2e.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_codex_install.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_codex_proxy.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_config_paths.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_connect_flow.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_consumer_mode.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_copilot_adapter.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_copilot_proxy.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_daemon_manager.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_data_flow.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_event_schema_v1.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_events.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_evidence_store.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_launch_env.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_mcp_protection.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_opencode_proxy.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_product_flow.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_prompt_injection.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_protect.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_render.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_risk.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_runtime.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_runtime_action_harnesses.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_runtime_actions.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_runtime_decisions.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_runtime_detectors.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_runtime_signals.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_safe_decode.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_sandbox.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_skill_protection.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_store_migrations.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_supply_chain.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_surface_server.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_guard_verdicts.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_hermes_adapter.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_integration.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_lint_fixes.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_live_cisco_smoke.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_manifest.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_marketplace.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_mcp_security.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_openclaw_adapter.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_operational_security.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_policy.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_quality_artifact.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_rule_registry.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_scanner.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_schema_contracts.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_security.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_security_ops.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_skill_security.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_submission.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_trust_scoring.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_trust_specs.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_verification.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/tests/test_versioning.py +0 -0
- {plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/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.128
|
|
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.128"
|
|
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.128"
|
|
8
8
|
description = "Protect local AI harnesses with HOL Guard and run scanner checks for Codex, Claude, Cursor, Gemini, and OpenCode."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
{plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/cli/commands.py
RENAMED
|
@@ -472,6 +472,22 @@ def _configure_guard_parser(guard_parser: argparse.ArgumentParser) -> None:
|
|
|
472
472
|
advisories_parser = guard_subparsers.add_parser("advisories", help="List cached Guard advisories and verdicts")
|
|
473
473
|
_add_guard_common_args(advisories_parser)
|
|
474
474
|
advisories_parser.add_argument("--json", action="store_true")
|
|
475
|
+
advisories_sub = advisories_parser.add_subparsers(dest="advisories_subcommand")
|
|
476
|
+
|
|
477
|
+
_adv_list = advisories_sub.add_parser("list", help="List cached advisories")
|
|
478
|
+
_adv_list.add_argument("--json", action="store_true")
|
|
479
|
+
_adv_list.add_argument(
|
|
480
|
+
"--severity",
|
|
481
|
+
choices=["low", "medium", "high", "critical"],
|
|
482
|
+
help="Filter by minimum severity",
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
_adv_sync = advisories_sub.add_parser("sync", help="Sync advisories from Guard Cloud")
|
|
486
|
+
_adv_sync.add_argument("--json", action="store_true")
|
|
487
|
+
|
|
488
|
+
_adv_explain = advisories_sub.add_parser("explain", help="Explain a specific advisory by ID")
|
|
489
|
+
_adv_explain.add_argument("--json", action="store_true")
|
|
490
|
+
_adv_explain.add_argument("advisory_id", help="Advisory ID to explain")
|
|
475
491
|
|
|
476
492
|
events_parser = guard_subparsers.add_parser("events", help="List local Guard lifecycle events")
|
|
477
493
|
_add_guard_common_args(events_parser)
|
|
@@ -1081,11 +1097,44 @@ def run_guard_command(
|
|
|
1081
1097
|
return 0
|
|
1082
1098
|
|
|
1083
1099
|
if args.guard_command == "advisories":
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1100
|
+
adv_sub = getattr(args, "advisories_subcommand", None)
|
|
1101
|
+
if adv_sub == "sync":
|
|
1102
|
+
credentials = store.get_sync_credentials()
|
|
1103
|
+
if credentials is None:
|
|
1104
|
+
_emit(
|
|
1105
|
+
"advisories_sync",
|
|
1106
|
+
{"generated_at": _now(), "status": "no_cloud_sync_configured"},
|
|
1107
|
+
getattr(args, "json", False),
|
|
1108
|
+
)
|
|
1109
|
+
else:
|
|
1110
|
+
_emit(
|
|
1111
|
+
"advisories_sync",
|
|
1112
|
+
{"generated_at": _now(), "status": "advisory_sync_not_available", "synced": False},
|
|
1113
|
+
getattr(args, "json", False),
|
|
1114
|
+
)
|
|
1115
|
+
elif adv_sub == "explain":
|
|
1116
|
+
target_id = getattr(args, "advisory_id", None)
|
|
1117
|
+
all_advs = store.list_cached_advisories(limit=None)
|
|
1118
|
+
match = next(
|
|
1119
|
+
(a for a in all_advs if a.get("advisory_id") == target_id or a.get("id") == target_id),
|
|
1120
|
+
None,
|
|
1121
|
+
)
|
|
1122
|
+
if match:
|
|
1123
|
+
_emit("advisory_explain", match, getattr(args, "json", False))
|
|
1124
|
+
else:
|
|
1125
|
+
_emit("advisory_explain", {"error": f"advisory {target_id!r} not found"}, getattr(args, "json", False))
|
|
1126
|
+
else:
|
|
1127
|
+
all_advs = store.list_cached_advisories()
|
|
1128
|
+
sev_filter = getattr(args, "severity", None)
|
|
1129
|
+
_sev_rank = {"low": 0, "medium": 1, "high": 2, "critical": 3}
|
|
1130
|
+
if sev_filter and sev_filter in _sev_rank:
|
|
1131
|
+
min_rank = _sev_rank[sev_filter]
|
|
1132
|
+
all_advs = [a for a in all_advs if _sev_rank.get(str(a.get("severity", "")).lower(), -1) >= min_rank]
|
|
1133
|
+
_emit(
|
|
1134
|
+
"advisories",
|
|
1135
|
+
{"generated_at": _now(), "items": all_advs},
|
|
1136
|
+
getattr(args, "json", False),
|
|
1137
|
+
)
|
|
1089
1138
|
return 0
|
|
1090
1139
|
|
|
1091
1140
|
if args.guard_command == "events":
|
{plugin_scanner-2.0.127 → plugin_scanner-2.0.128}/src/codex_plugin_scanner/guard/daemon/server.py
RENAMED
|
@@ -208,10 +208,12 @@ class _GuardDaemonHandler(BaseHTTPRequestHandler):
|
|
|
208
208
|
limit=limit_v,
|
|
209
209
|
)
|
|
210
210
|
total = count_evidence(conn)
|
|
211
|
-
self._write_json(
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
211
|
+
self._write_json(
|
|
212
|
+
{
|
|
213
|
+
"items": [vars(r) for r in records],
|
|
214
|
+
"total": total,
|
|
215
|
+
}
|
|
216
|
+
)
|
|
215
217
|
return
|
|
216
218
|
if parsed.path == "/v1/evidence/export":
|
|
217
219
|
with store._connect() as conn:
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Advisory-driven policy escalation for Guard decisions.
|
|
2
|
+
|
|
3
|
+
When a cached threat intelligence bundle contains a critical advisory that
|
|
4
|
+
matches the current artifact, a lower-severity policy action such as ``allow``
|
|
5
|
+
or ``warn`` can be escalated to ``ask`` or ``block`` to ensure the user
|
|
6
|
+
reviews the risk before the action proceeds.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from codex_plugin_scanner.guard.models import GuardAction
|
|
15
|
+
from codex_plugin_scanner.guard.runtime.threat_intel import ThreatAdvisory
|
|
16
|
+
|
|
17
|
+
_ESCALATION_TABLE: dict[str, dict[str, str]] = {
|
|
18
|
+
"critical": {
|
|
19
|
+
"allow": "ask",
|
|
20
|
+
"warn": "ask",
|
|
21
|
+
"review": "block",
|
|
22
|
+
},
|
|
23
|
+
"high": {
|
|
24
|
+
"allow": "ask",
|
|
25
|
+
"warn": "ask",
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
_ESCALATION_THRESHOLD_SEVERITIES = frozenset({"critical", "high"})
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def escalate_for_advisories(
|
|
33
|
+
policy_action: GuardAction,
|
|
34
|
+
matched_advisories: tuple[ThreatAdvisory, ...],
|
|
35
|
+
) -> tuple[GuardAction, str | None]:
|
|
36
|
+
"""Return the escalated policy action and the advisory id that triggered it.
|
|
37
|
+
|
|
38
|
+
If no advisory warrants escalation, returns the original action and None.
|
|
39
|
+
The most severe advisory match drives escalation; within the same severity
|
|
40
|
+
the first match in the input tuple wins.
|
|
41
|
+
"""
|
|
42
|
+
if not matched_advisories:
|
|
43
|
+
return policy_action, None
|
|
44
|
+
|
|
45
|
+
ranked = sorted(
|
|
46
|
+
(a for a in matched_advisories if a.severity.lower() in _ESCALATION_THRESHOLD_SEVERITIES),
|
|
47
|
+
key=lambda a: 0 if a.severity.lower() == "critical" else 1,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
for advisory in ranked:
|
|
51
|
+
escalation_map = _ESCALATION_TABLE.get(advisory.severity.lower(), {})
|
|
52
|
+
escalated = escalation_map.get(policy_action)
|
|
53
|
+
if escalated is not None:
|
|
54
|
+
return escalated, advisory.advisory_id # type: ignore[return-value]
|
|
55
|
+
|
|
56
|
+
return policy_action, None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def advisory_match_summary(matched_advisories: tuple[ThreatAdvisory, ...]) -> str:
|
|
60
|
+
"""Return a brief human-readable summary of matched advisories for logging."""
|
|
61
|
+
if not matched_advisories:
|
|
62
|
+
return "no advisory matches"
|
|
63
|
+
parts = [f"{a.advisory_id}({a.severity.lower()})" for a in matched_advisories[:5]]
|
|
64
|
+
suffix = f" +{len(matched_advisories) - 5} more" if len(matched_advisories) > 5 else ""
|
|
65
|
+
return ", ".join(parts) + suffix
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""Advisory matchers for multi-source threat intelligence.
|
|
2
|
+
|
|
3
|
+
Each matcher takes a ThreatAdvisory and a target dict, and returns True if
|
|
4
|
+
the advisory applies to the target. Matchers are keyed by their `matcher`
|
|
5
|
+
field value in the advisory.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
from typing import Protocol, runtime_checkable
|
|
12
|
+
|
|
13
|
+
from .threat_intel import ThreatAdvisory
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@runtime_checkable
|
|
17
|
+
class AdvisoryMatcher(Protocol):
|
|
18
|
+
"""Callable that tests an advisory against a target dict."""
|
|
19
|
+
|
|
20
|
+
def __call__(self, advisory: ThreatAdvisory, target: dict[str, object]) -> bool: ...
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _norm(val: object) -> str:
|
|
24
|
+
if not isinstance(val, str):
|
|
25
|
+
return ""
|
|
26
|
+
return val.strip().lower()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _package_matches(advisory_pkg: str, target_pkg: str) -> bool:
|
|
30
|
+
if not advisory_pkg or not target_pkg:
|
|
31
|
+
return False
|
|
32
|
+
return _norm(advisory_pkg) == _norm(target_pkg)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def match_osv(advisory: ThreatAdvisory, target: dict[str, object]) -> bool:
|
|
36
|
+
"""Match OSV advisories by package name and ecosystem."""
|
|
37
|
+
pkg_name = target.get("package_name")
|
|
38
|
+
ecosystem = target.get("ecosystem")
|
|
39
|
+
advisory_pkg = advisory.matcher.split(":", 1)[-1] if ":" in advisory.matcher else advisory.matcher
|
|
40
|
+
parts = advisory.source.split("/", 1)
|
|
41
|
+
advisory_eco = parts[1] if len(parts) > 1 else parts[0]
|
|
42
|
+
eco_match = not ecosystem or _norm(advisory_eco) in (_norm(str(ecosystem)), "*")
|
|
43
|
+
return eco_match and _package_matches(advisory_pkg, str(pkg_name or ""))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def match_github_advisory(advisory: ThreatAdvisory, target: dict[str, object]) -> bool:
|
|
47
|
+
"""Match GitHub Security Advisory by GHSA ID or package name."""
|
|
48
|
+
matcher_val = advisory.matcher
|
|
49
|
+
if re.match(r"^GHSA-", matcher_val, re.IGNORECASE):
|
|
50
|
+
ghsa_id = target.get("ghsa_id")
|
|
51
|
+
return isinstance(ghsa_id, str) and _norm(ghsa_id) == _norm(matcher_val)
|
|
52
|
+
return _package_matches(matcher_val, str(target.get("package_name") or ""))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def match_nvd_cve(advisory: ThreatAdvisory, target: dict[str, object]) -> bool:
|
|
56
|
+
"""Match NVD CVE by CVE ID or harness name."""
|
|
57
|
+
matcher_val = advisory.matcher
|
|
58
|
+
if re.match(r"^CVE-", matcher_val, re.IGNORECASE):
|
|
59
|
+
cve_id = target.get("cve_id")
|
|
60
|
+
return isinstance(cve_id, str) and _norm(cve_id) == _norm(matcher_val)
|
|
61
|
+
harness = target.get("harness")
|
|
62
|
+
return isinstance(harness, str) and _norm(harness) == _norm(matcher_val)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def match_npm_advisory(advisory: ThreatAdvisory, target: dict[str, object]) -> bool:
|
|
66
|
+
"""Match npm advisory by package name when ecosystem is npm."""
|
|
67
|
+
eco = target.get("ecosystem")
|
|
68
|
+
if not isinstance(eco, str) or _norm(eco) not in ("npm",):
|
|
69
|
+
return False
|
|
70
|
+
return _package_matches(advisory.matcher, str(target.get("package_name") or ""))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def match_pypi_advisory(advisory: ThreatAdvisory, target: dict[str, object]) -> bool:
|
|
74
|
+
"""Match PyPI advisory by package name when ecosystem is pypi."""
|
|
75
|
+
eco = target.get("ecosystem")
|
|
76
|
+
if not isinstance(eco, str) or _norm(eco) not in ("pypi", "pip"):
|
|
77
|
+
return False
|
|
78
|
+
return _package_matches(advisory.matcher, str(target.get("package_name") or ""))
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def match_github_action(advisory: ThreatAdvisory, target: dict[str, object]) -> bool:
|
|
82
|
+
"""Match a GitHub Action by action slug (owner/name)."""
|
|
83
|
+
action_slug = target.get("action_slug")
|
|
84
|
+
if not isinstance(action_slug, str):
|
|
85
|
+
return False
|
|
86
|
+
return _norm(advisory.matcher) == _norm(action_slug)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def match_mcp_server(advisory: ThreatAdvisory, target: dict[str, object]) -> bool:
|
|
90
|
+
"""Match an MCP server advisory by exact server name."""
|
|
91
|
+
server_name = target.get("mcp_server")
|
|
92
|
+
if not isinstance(server_name, str):
|
|
93
|
+
return False
|
|
94
|
+
return _norm(server_name) == _norm(advisory.matcher)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def match_skill_hash(advisory: ThreatAdvisory, target: dict[str, object]) -> bool:
|
|
98
|
+
"""Match a skill (extension/tool) by its content hash."""
|
|
99
|
+
skill_hash = target.get("skill_hash")
|
|
100
|
+
if not isinstance(skill_hash, str):
|
|
101
|
+
return False
|
|
102
|
+
return _norm(advisory.matcher) == _norm(skill_hash)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def match_malicious_domain(advisory: ThreatAdvisory, target: dict[str, object]) -> bool:
|
|
106
|
+
"""Match a network destination against a known-malicious domain.
|
|
107
|
+
|
|
108
|
+
Uses exact match or subdomain boundary match to avoid false positives.
|
|
109
|
+
e.g. advisory matcher ``evil.com`` matches ``evil.com`` and ``sub.evil.com``
|
|
110
|
+
but not ``not-evil.com``.
|
|
111
|
+
"""
|
|
112
|
+
hosts: object = target.get("network_hosts")
|
|
113
|
+
if not isinstance(hosts, list):
|
|
114
|
+
hosts = [target.get("network_host")] if target.get("network_host") else []
|
|
115
|
+
matcher_norm = _norm(advisory.matcher)
|
|
116
|
+
return any(_norm(str(h)) == matcher_norm or _norm(str(h)).endswith("." + matcher_norm) for h in hosts if h)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def match_malicious_package_hash(advisory: ThreatAdvisory, target: dict[str, object]) -> bool:
|
|
120
|
+
"""Match a package by its content hash (e.g., tarball SHA256)."""
|
|
121
|
+
pkg_hash = target.get("package_hash")
|
|
122
|
+
if not isinstance(pkg_hash, str):
|
|
123
|
+
return False
|
|
124
|
+
return _norm(advisory.matcher) == _norm(pkg_hash)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
_MATCHER_REGISTRY: dict[str, AdvisoryMatcher] = {
|
|
128
|
+
"osv": match_osv,
|
|
129
|
+
"github_advisory": match_github_advisory,
|
|
130
|
+
"nvd_cve": match_nvd_cve,
|
|
131
|
+
"npm": match_npm_advisory,
|
|
132
|
+
"pypi": match_pypi_advisory,
|
|
133
|
+
"github_action": match_github_action,
|
|
134
|
+
"mcp_server": match_mcp_server,
|
|
135
|
+
"skill_hash": match_skill_hash,
|
|
136
|
+
"malicious_domain": match_malicious_domain,
|
|
137
|
+
"malicious_package_hash": match_malicious_package_hash,
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def get_matcher(matcher_key: str) -> AdvisoryMatcher | None:
|
|
142
|
+
"""Return the matcher function for a given matcher key, or None if unknown."""
|
|
143
|
+
return _MATCHER_REGISTRY.get(matcher_key)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def apply_advisory(advisory: ThreatAdvisory, target: dict[str, object]) -> bool:
|
|
147
|
+
"""Test a single advisory against a target using the registered matcher.
|
|
148
|
+
|
|
149
|
+
Dispatcher reads the advisory `source` field to select the matcher function.
|
|
150
|
+
Returns False for unknown source keys (safe default — no false positives).
|
|
151
|
+
"""
|
|
152
|
+
source_key = advisory.source.split("/")[0].lower()
|
|
153
|
+
matcher_fn = get_matcher(source_key)
|
|
154
|
+
if matcher_fn is None:
|
|
155
|
+
return False
|
|
156
|
+
return matcher_fn(advisory, target)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def match_all_advisories(
|
|
160
|
+
advisories: tuple[ThreatAdvisory, ...],
|
|
161
|
+
target: dict[str, object],
|
|
162
|
+
) -> tuple[ThreatAdvisory, ...]:
|
|
163
|
+
"""Return all advisories from the bundle that match the given target."""
|
|
164
|
+
return tuple(a for a in advisories if apply_advisory(a, target))
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""HOL Guard threat intelligence bundle handling.
|
|
2
|
+
|
|
3
|
+
Provides typed dataclasses, signature verification, freshness checks,
|
|
4
|
+
rollback protection, and multi-source advisory matching for the
|
|
5
|
+
cloud advisory client subsystem.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import base64
|
|
11
|
+
import json
|
|
12
|
+
import time
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from typing import Final
|
|
15
|
+
|
|
16
|
+
from cryptography.exceptions import InvalidSignature
|
|
17
|
+
from cryptography.hazmat.primitives import hashes, serialization
|
|
18
|
+
from cryptography.hazmat.primitives.asymmetric import padding
|
|
19
|
+
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
|
|
20
|
+
|
|
21
|
+
_BUNDLE_MAX_AGE_SECONDS: Final[int] = 86_400 * 7
|
|
22
|
+
_BUNDLE_CLOCK_SKEW_SECONDS: Final[int] = 300
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ThreatIntelError(Exception):
|
|
26
|
+
"""Base error for threat intel bundle validation failures."""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class BundleSignatureError(ThreatIntelError):
|
|
30
|
+
"""Bundle signature did not verify against the provided public key."""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class BundleExpiredError(ThreatIntelError):
|
|
34
|
+
"""Bundle freshness window has elapsed."""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class BundleRollbackError(ThreatIntelError):
|
|
38
|
+
"""Bundle version is older than the cached version — possible rollback attack."""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class BundleMalformedError(ThreatIntelError):
|
|
42
|
+
"""Bundle JSON is missing required fields or contains invalid types."""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass(frozen=True, slots=True)
|
|
46
|
+
class ThreatAdvisory:
|
|
47
|
+
"""Single advisory record inside a threat intelligence bundle."""
|
|
48
|
+
|
|
49
|
+
advisory_id: str
|
|
50
|
+
source: str
|
|
51
|
+
severity: str
|
|
52
|
+
title: str
|
|
53
|
+
affected_type: str
|
|
54
|
+
matcher: str
|
|
55
|
+
recommendation: str
|
|
56
|
+
|
|
57
|
+
def to_dict(self) -> dict[str, object]:
|
|
58
|
+
return {
|
|
59
|
+
"advisory_id": self.advisory_id,
|
|
60
|
+
"source": self.source,
|
|
61
|
+
"severity": self.severity,
|
|
62
|
+
"title": self.title,
|
|
63
|
+
"affected_type": self.affected_type,
|
|
64
|
+
"matcher": self.matcher,
|
|
65
|
+
"recommendation": self.recommendation,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def from_dict(data: dict[str, object]) -> ThreatAdvisory:
|
|
70
|
+
def _str(key: str) -> str:
|
|
71
|
+
val = data.get(key)
|
|
72
|
+
if not isinstance(val, str) or not val.strip():
|
|
73
|
+
raise BundleMalformedError(f"ThreatAdvisory missing required string field: {key!r}")
|
|
74
|
+
return val
|
|
75
|
+
|
|
76
|
+
return ThreatAdvisory(
|
|
77
|
+
advisory_id=_str("advisory_id"),
|
|
78
|
+
source=_str("source"),
|
|
79
|
+
severity=_str("severity"),
|
|
80
|
+
title=_str("title"),
|
|
81
|
+
affected_type=_str("affected_type"),
|
|
82
|
+
matcher=_str("matcher"),
|
|
83
|
+
recommendation=_str("recommendation"),
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass(frozen=True, slots=True)
|
|
88
|
+
class ThreatIntelBundle:
|
|
89
|
+
"""Signed, versioned advisory bundle from the HOL Guard cloud."""
|
|
90
|
+
|
|
91
|
+
version: int
|
|
92
|
+
generated_at: float
|
|
93
|
+
expires_at: float
|
|
94
|
+
source: str
|
|
95
|
+
signature: str
|
|
96
|
+
advisories: tuple[ThreatAdvisory, ...]
|
|
97
|
+
|
|
98
|
+
def to_dict(self) -> dict[str, object]:
|
|
99
|
+
return {
|
|
100
|
+
"version": self.version,
|
|
101
|
+
"generated_at": self.generated_at,
|
|
102
|
+
"expires_at": self.expires_at,
|
|
103
|
+
"source": self.source,
|
|
104
|
+
"signature": self.signature,
|
|
105
|
+
"advisories": [a.to_dict() for a in self.advisories],
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def from_dict(data: dict[str, object]) -> ThreatIntelBundle:
|
|
110
|
+
def _int(key: str) -> int:
|
|
111
|
+
val = data.get(key)
|
|
112
|
+
if not isinstance(val, int):
|
|
113
|
+
raise BundleMalformedError(f"ThreatIntelBundle missing required int field: {key!r}")
|
|
114
|
+
return val
|
|
115
|
+
|
|
116
|
+
def _float(key: str) -> float:
|
|
117
|
+
val = data.get(key)
|
|
118
|
+
if isinstance(val, (int, float)):
|
|
119
|
+
return float(val)
|
|
120
|
+
raise BundleMalformedError(f"ThreatIntelBundle missing required numeric field: {key!r}")
|
|
121
|
+
|
|
122
|
+
def _str(key: str) -> str:
|
|
123
|
+
val = data.get(key)
|
|
124
|
+
if not isinstance(val, str) or not val.strip():
|
|
125
|
+
raise BundleMalformedError(f"ThreatIntelBundle missing required string field: {key!r}")
|
|
126
|
+
return val
|
|
127
|
+
|
|
128
|
+
raw_advisories = data.get("advisories")
|
|
129
|
+
if not isinstance(raw_advisories, list):
|
|
130
|
+
raise BundleMalformedError("ThreatIntelBundle 'advisories' must be a list")
|
|
131
|
+
|
|
132
|
+
parsed_advisories: list[ThreatAdvisory] = []
|
|
133
|
+
for idx, item in enumerate(raw_advisories):
|
|
134
|
+
if not isinstance(item, dict):
|
|
135
|
+
raise BundleMalformedError(
|
|
136
|
+
f"ThreatIntelBundle advisory at index {idx} must be an object, got {type(item).__name__}"
|
|
137
|
+
)
|
|
138
|
+
parsed_advisories.append(ThreatAdvisory.from_dict(item))
|
|
139
|
+
|
|
140
|
+
return ThreatIntelBundle(
|
|
141
|
+
version=_int("version"),
|
|
142
|
+
generated_at=_float("generated_at"),
|
|
143
|
+
expires_at=_float("expires_at"),
|
|
144
|
+
source=_str("source"),
|
|
145
|
+
signature=_str("signature"),
|
|
146
|
+
advisories=tuple(parsed_advisories),
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _canonical_payload(bundle: ThreatIntelBundle) -> bytes:
|
|
151
|
+
"""Deterministic JSON payload used for signature computation.
|
|
152
|
+
|
|
153
|
+
Signs only the stable fields — excludes `signature` itself.
|
|
154
|
+
Timestamps are serialized as integers to avoid float precision
|
|
155
|
+
differences across JSON implementations.
|
|
156
|
+
"""
|
|
157
|
+
payload = {
|
|
158
|
+
"version": bundle.version,
|
|
159
|
+
"generated_at": int(bundle.generated_at),
|
|
160
|
+
"expires_at": int(bundle.expires_at),
|
|
161
|
+
"source": bundle.source,
|
|
162
|
+
"advisories": [a.to_dict() for a in bundle.advisories],
|
|
163
|
+
}
|
|
164
|
+
return json.dumps(payload, sort_keys=True, separators=(",", ":")).encode()
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def verify_bundle_signature(bundle: ThreatIntelBundle, public_key_pem: bytes) -> None:
|
|
168
|
+
"""Verify the RSA-PSS signature on a bundle.
|
|
169
|
+
|
|
170
|
+
Raises BundleSignatureError if the signature does not verify.
|
|
171
|
+
"""
|
|
172
|
+
try:
|
|
173
|
+
loaded_key = serialization.load_pem_public_key(public_key_pem)
|
|
174
|
+
except (ValueError, TypeError) as exc:
|
|
175
|
+
raise BundleSignatureError(f"Failed to load public key: {exc}") from exc
|
|
176
|
+
|
|
177
|
+
if not isinstance(loaded_key, RSAPublicKey):
|
|
178
|
+
raise BundleSignatureError("Public key must be RSA")
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
sig_bytes = base64.b64decode(bundle.signature)
|
|
182
|
+
except Exception as exc:
|
|
183
|
+
raise BundleSignatureError(f"Signature is not valid base64: {exc}") from exc
|
|
184
|
+
|
|
185
|
+
payload = _canonical_payload(bundle)
|
|
186
|
+
try:
|
|
187
|
+
loaded_key.verify(
|
|
188
|
+
sig_bytes,
|
|
189
|
+
payload,
|
|
190
|
+
padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH),
|
|
191
|
+
hashes.SHA256(),
|
|
192
|
+
)
|
|
193
|
+
except InvalidSignature as exc:
|
|
194
|
+
raise BundleSignatureError("Bundle signature verification failed") from exc
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def check_bundle_freshness(bundle: ThreatIntelBundle, now: float | None = None) -> None:
|
|
198
|
+
"""Raise BundleExpiredError if the bundle is outside its freshness window.
|
|
199
|
+
|
|
200
|
+
Uses wall-clock time by default; pass `now` in tests for determinism.
|
|
201
|
+
"""
|
|
202
|
+
ts = now if now is not None else time.time()
|
|
203
|
+
if ts > bundle.expires_at + _BUNDLE_CLOCK_SKEW_SECONDS:
|
|
204
|
+
raise BundleExpiredError(f"Bundle expired at {bundle.expires_at:.0f}, current time is {ts:.0f}")
|
|
205
|
+
if ts < bundle.generated_at - _BUNDLE_CLOCK_SKEW_SECONDS:
|
|
206
|
+
raise BundleExpiredError(
|
|
207
|
+
f"Bundle generated_at {bundle.generated_at:.0f} is in the future (current time {ts:.0f})"
|
|
208
|
+
)
|
|
209
|
+
bundle_age = ts - bundle.generated_at
|
|
210
|
+
if bundle_age > _BUNDLE_MAX_AGE_SECONDS:
|
|
211
|
+
raise BundleExpiredError(
|
|
212
|
+
f"Bundle age {bundle_age:.0f}s exceeds maximum allowed age of {_BUNDLE_MAX_AGE_SECONDS}s"
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def check_bundle_rollback(bundle: ThreatIntelBundle, cached_version: int) -> None:
|
|
217
|
+
"""Raise BundleRollbackError if the bundle version regresses.
|
|
218
|
+
|
|
219
|
+
Protects against a server serving an older bundle to downgrade protections.
|
|
220
|
+
"""
|
|
221
|
+
if bundle.version < cached_version:
|
|
222
|
+
raise BundleRollbackError(f"Bundle version {bundle.version} is older than cached version {cached_version}")
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def load_bundle_from_json(raw_json: str) -> ThreatIntelBundle:
|
|
226
|
+
"""Parse a raw JSON string into a ThreatIntelBundle.
|
|
227
|
+
|
|
228
|
+
Raises BundleMalformedError on invalid JSON or schema violations.
|
|
229
|
+
"""
|
|
230
|
+
try:
|
|
231
|
+
data = json.loads(raw_json)
|
|
232
|
+
except json.JSONDecodeError as exc:
|
|
233
|
+
raise BundleMalformedError(f"Bundle JSON is invalid: {exc}") from exc
|
|
234
|
+
|
|
235
|
+
if not isinstance(data, dict):
|
|
236
|
+
raise BundleMalformedError("Bundle root must be a JSON object")
|
|
237
|
+
|
|
238
|
+
return ThreatIntelBundle.from_dict(data)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def advisory_severity_rank(severity: str) -> int:
|
|
242
|
+
"""Numeric rank for severity comparison (higher = more severe)."""
|
|
243
|
+
return {"info": 0, "low": 1, "medium": 2, "high": 3, "critical": 4}.get(severity.lower(), 0)
|
|
@@ -75,6 +75,11 @@ from .store_evidence import (
|
|
|
75
75
|
evidence_index_statements,
|
|
76
76
|
evidence_schema_statement,
|
|
77
77
|
)
|
|
78
|
+
from .store_threat_intel import (
|
|
79
|
+
threat_intel_bundle_schema_statement,
|
|
80
|
+
threat_intel_index_statements,
|
|
81
|
+
threat_intel_matches_schema_statement,
|
|
82
|
+
)
|
|
78
83
|
from .types import CapabilitySet
|
|
79
84
|
|
|
80
85
|
_SYNC_TOKEN_REF = "guard-cloud-token"
|
|
@@ -651,12 +656,16 @@ class GuardStore:
|
|
|
651
656
|
connect_state_schema_statement(),
|
|
652
657
|
approval_schema_statement(),
|
|
653
658
|
evidence_schema_statement(),
|
|
659
|
+
threat_intel_bundle_schema_statement(),
|
|
660
|
+
threat_intel_matches_schema_statement(),
|
|
654
661
|
)
|
|
655
662
|
with self._connect() as connection:
|
|
656
663
|
for statement in statements:
|
|
657
664
|
connection.execute(statement)
|
|
658
665
|
for idx_stmt in evidence_index_statements():
|
|
659
666
|
connection.execute(idx_stmt)
|
|
667
|
+
for idx_stmt in threat_intel_index_statements():
|
|
668
|
+
connection.execute(idx_stmt)
|
|
660
669
|
self._ensure_policy_column(connection, "publisher", "text")
|
|
661
670
|
self._ensure_policy_column(connection, "artifact_hash", "text")
|
|
662
671
|
self._ensure_policy_column(connection, "owner", "text")
|