plugin-scanner 2.0.110__tar.gz → 2.0.112__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.110 → plugin_scanner-2.0.112}/PKG-INFO +1 -1
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/pyproject.toml +1 -1
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/pyproject.toml.bak +1 -1
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/cli/commands.py +152 -32
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/cli/render.py +3 -0
- plugin_scanner-2.0.112/src/codex_plugin_scanner/guard/runtime/data_flow.py +384 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/version.py +1 -1
- plugin_scanner-2.0.112/tests/test_guard_data_flow.py +117 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_render.py +23 -5
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_runtime.py +126 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/.clusterfuzzlite/Dockerfile +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/.clusterfuzzlite/build.sh +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/.clusterfuzzlite/project.yaml +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/.clusterfuzzlite/requirements-atheris.txt +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/.dockerignore +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/.github/CODEOWNERS +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/.github/ISSUE_TEMPLATE/bug-report.yml +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/.github/dependabot.yml +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/.github/workflows/ci.yml +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/.github/workflows/codeql.yml +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/.github/workflows/dependabot-uv-lock.yml +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/.github/workflows/fuzz.yml +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/.github/workflows/harness-smoke.yml +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/.github/workflows/publish.yml +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/.github/workflows/scorecard.yml +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/.gitignore +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/.pre-commit-hooks.yaml +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/CONTRIBUTING.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/Dockerfile +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/LICENSE +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/README.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/SECURITY.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/dashboard/index.html +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/dashboard/package.json +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/dashboard/pnpm-lock.yaml +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/dashboard/public/apple-touch-icon.png +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/dashboard/public/brand/Logo_Icon_Dark.png +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/dashboard/public/brand/Logo_Whole.png +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/dashboard/public/favicon-16x16.png +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/dashboard/public/favicon-32x32.png +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/dashboard/public/favicon.ico +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/dashboard/src/app.tsx +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/dashboard/src/approval-center-layout.tsx +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/dashboard/src/approval-center-primitives.tsx +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/dashboard/src/approval-center-utils.ts +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/dashboard/src/fleet-workspace.tsx +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/dashboard/src/guard-api.test.ts +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/dashboard/src/guard-api.ts +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/dashboard/src/guard-demo.ts +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/dashboard/src/guard-types.ts +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/dashboard/src/main.tsx +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/dashboard/src/receipts-workspace.tsx +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/dashboard/src/runtime-overview.tsx +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/dashboard/src/settings-workspace.tsx +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/dashboard/src/styles.css +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/dashboard/src/vite-env.d.ts +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/dashboard/tsconfig.json +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/dashboard/vite.config.ts +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/docker-requirements.txt +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/docs/guard/approval-audit.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/docs/guard/architecture.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/docs/guard/get-started.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/docs/guard/harness-support.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/docs/guard/local-vs-cloud.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/docs/guard/testing-matrix.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/docs/trust/mcp-trust-draft.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/docs/trust/plugin-trust-draft.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/docs/trust/skill-trust-local.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/fuzzers/manifest_fuzzer.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/requirements.txt +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/schemas/plugin-quality.v1.json +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/schemas/scan-result.v1.json +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/schemas/verify-result.v1.json +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/__init__.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/action_runner.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/argparse_utils.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/checks/__init__.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/checks/best_practices.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/checks/claude.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/checks/code_quality.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/checks/ecosystem_common.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/checks/gemini.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/checks/manifest.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/checks/manifest_support.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/checks/marketplace.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/checks/mcp_security.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/checks/opencode.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/checks/operational_security.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/checks/security.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/checks/skill_security.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/cli.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/cli_ui.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/config.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/ecosystems/__init__.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/ecosystems/base.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/ecosystems/claude.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/ecosystems/codex.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/ecosystems/detect.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/ecosystems/gemini.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/ecosystems/opencode.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/ecosystems/registry.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/ecosystems/types.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/github_reporting.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/__init__.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/adapters/__init__.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/adapters/antigravity.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/adapters/base.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/adapters/claude_code.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/adapters/cloud_identity.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/adapters/codex.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/adapters/copilot.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/adapters/cursor.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/adapters/gemini.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/adapters/hermes.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/adapters/mcp_servers.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/adapters/openclaw.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/adapters/openclaw_config.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/adapters/openclaw_support.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/adapters/opencode.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/adapters/opencode_artifacts.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/advisory_model.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/approvals.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/bridge/__init__.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/capabilities.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/cli/__init__.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/cli/approval_commands.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/cli/bootstrap.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/cli/connect_flow.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/cli/install_commands.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/cli/product.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/cli/prompt.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/cli/update_commands.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/codex_config.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/config.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/consumer/__init__.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/consumer/service.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/daemon/__init__.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/daemon/client.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/daemon/manager.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/daemon/server.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/daemon/static/apple-touch-icon.png +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/daemon/static/assets/guard-dashboard.js +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/daemon/static/assets/index.css +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Icon_Dark.png +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Whole.png +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/daemon/static/favicon-16x16.png +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/daemon/static/favicon-32x32.png +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/daemon/static/favicon.ico +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/daemon/static/index.html +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/edge_events.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/incident.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/launcher.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/mcp_tool_calls.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/models.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/policy/__init__.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/policy/engine.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/protect.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/proxy/__init__.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/proxy/remote.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/proxy/runtime_mcp.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/proxy/stdio.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/receipts/__init__.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/receipts/manager.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/redaction.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/risk.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/runtime/__init__.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/runtime/actions.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/runtime/decisions.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/runtime/detectors.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/runtime/runner.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/runtime/secret_file_requests.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/runtime/secret_sensitivity.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/runtime/signals.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/runtime/surface_server.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/schemas/__init__.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/schemas/consumer_mode.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/schemas/guard_event_v1.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/schemas/surface_server.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/shims.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/store.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/store_approvals.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/store_connect.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/types.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/integrations/__init__.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/integrations/cisco_mcp_scanner.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/integrations/cisco_skill_scanner.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/lint_fixes.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/marketplace_support.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/models.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/path_support.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/policy.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/quality_artifact.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/repo_detect.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/reporting.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/rules/__init__.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/rules/registry.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/rules/specs.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/scanner.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/submission.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/suppressions.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/trust_domain_scoring.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/trust_helpers.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/trust_mcp_scoring.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/trust_models.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/trust_plugin_scoring.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/trust_scoring.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/trust_skill_scoring.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/trust_specs.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/verification.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/__init__.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/conftest.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/__init__.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/bad-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/bad-plugin/.mcp.json +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/bad-plugin/secrets.js +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/claude-plugin-good/.claude-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/claude-plugin-good/LICENSE +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/claude-plugin-good/README.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/claude-plugin-good/SECURITY.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/claude-plugin-good/hooks/hooks.json +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/claude-plugin-good/skills/example/SKILL.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/code-quality-bad/evil.js +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/code-quality-bad/inject.js +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/gemini-extension-good/GEMINI.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/gemini-extension-good/LICENSE +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/gemini-extension-good/README.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/gemini-extension-good/SECURITY.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/gemini-extension-good/commands/hello.toml +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/gemini-extension-good/gemini-extension.json +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/good-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/good-plugin/.codexignore +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/good-plugin/LICENSE +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/good-plugin/README.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/good-plugin/SECURITY.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/good-plugin/assets/icon.svg +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/good-plugin/assets/logo.svg +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/good-plugin/assets/screenshot.svg +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/good-plugin/skills/example/SKILL.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/guard-codex-malicious-mcp/.codex/config.toml +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/hermes-plugin-evil/config.yaml +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/hermes-plugin-evil/mcp_servers.json +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/hermes-plugin-evil/skills/security/malicious/SKILL.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/SKILL.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/references/api-setup.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/scripts/deploy.sh +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/hermes-plugin-evil/skills/utils/benign/SKILL.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/malformed-json/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/malicious-skill-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/malicious-skill-plugin/.codexignore +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/malicious-skill-plugin/LICENSE +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/malicious-skill-plugin/README.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/malicious-skill-plugin/SECURITY.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/malicious-skill-plugin/skills/leaky-skill/SKILL.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/mcp-canary-server.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/minimal-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/missing-fields/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/mit-license/LICENSE +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/multi-ecosystem-repo/codex-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/multi-ecosystem-repo/codex-plugin/LICENSE +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/multi-ecosystem-repo/codex-plugin/README.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/multi-ecosystem-repo/codex-plugin/SECURITY.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/multi-ecosystem-repo/gemini-ext/README.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/multi-ecosystem-repo/gemini-ext/gemini-extension.json +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/multi-plugin-repo/.agents/plugins/marketplace.json +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codexignore +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/LICENSE +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/README.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/SECURITY.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/skills/example/SKILL.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/skills/example/SKILL.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/no-version/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/opencode-good/.opencode/commands/hello.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/opencode-good/.opencode/plugins/example.ts +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/opencode-good/LICENSE +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/opencode-good/README.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/opencode-good/SECURITY.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/opencode-good/opencode.jsonc +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/skills-missing-dir/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/skills-no-frontmatter/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/skills-no-frontmatter/skills/bad-skill/SKILL.md +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/with-marketplace/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/with-marketplace/marketplace-broken.json +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/fixtures/with-marketplace/marketplace.json +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test-trust-scoring.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test-trust-specs.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_action_runner.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_best_practices.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_cisco_install_surfaces.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_cli.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_code_quality.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_config.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_coverage_remaining.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_ecosystems.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_edge_cases.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_final_coverage.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_access_graph.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_approvals.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_bootstrap.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_capabilities.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_claude_adapter.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_cli.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_codex_e2e.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_codex_install.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_codex_proxy.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_config_paths.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_connect_flow.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_consumer_mode.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_copilot_adapter.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_copilot_proxy.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_daemon_manager.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_event_schema_v1.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_events.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_launch_env.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_opencode_proxy.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_product_flow.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_protect.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_risk.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_runtime_action_harnesses.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_runtime_actions.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_runtime_decisions.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_runtime_detectors.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_runtime_signals.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_store_migrations.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_surface_server.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_guard_verdicts.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_hermes_adapter.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_integration.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_lint_fixes.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_live_cisco_smoke.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_manifest.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_marketplace.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_mcp_security.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_openclaw_adapter.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_operational_security.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_policy.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_quality_artifact.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_rule_registry.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_scanner.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_schema_contracts.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_security.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_security_ops.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_skill_security.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_submission.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_trust_scoring.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_trust_specs.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_verification.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/tests/test_versioning.py +0 -0
- {plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/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.112
|
|
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.112"
|
|
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.112"
|
|
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.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/cli/commands.py
RENAMED
|
@@ -97,7 +97,12 @@ from ..runtime.secret_file_requests import (
|
|
|
97
97
|
extract_sensitive_tool_action_request,
|
|
98
98
|
is_explicitly_benign_tool_action_request,
|
|
99
99
|
)
|
|
100
|
-
from ..runtime.secret_sensitivity import
|
|
100
|
+
from ..runtime.secret_sensitivity import (
|
|
101
|
+
SecretContentMatch,
|
|
102
|
+
SecretPathMatch,
|
|
103
|
+
classify_secret_content,
|
|
104
|
+
classify_secret_path,
|
|
105
|
+
)
|
|
101
106
|
from ..runtime.surface_server import GuardSurfaceRuntime
|
|
102
107
|
from ..store import GuardStore
|
|
103
108
|
from .approval_commands import add_approval_parser, run_approval_command
|
|
@@ -3236,6 +3241,12 @@ def _runtime_artifact_native_reason(artifact: GuardArtifact, response_payload: d
|
|
|
3236
3241
|
trimmed_summary = risk_summary.strip()
|
|
3237
3242
|
if len(trimmed_summary) > 180:
|
|
3238
3243
|
trimmed_summary = f"{trimmed_summary[:177].rstrip()}..."
|
|
3244
|
+
action_class = artifact.metadata.get("action_class")
|
|
3245
|
+
if (
|
|
3246
|
+
action_class == "credential exfiltration shell command"
|
|
3247
|
+
and "credential-looking output" not in trimmed_summary.lower()
|
|
3248
|
+
):
|
|
3249
|
+
trimmed_summary = f"{trimmed_summary} Guard also detected credential-looking output."
|
|
3239
3250
|
return f"HOL Guard flagged this request: {trimmed_summary}"
|
|
3240
3251
|
return "HOL Guard flagged this request for review."
|
|
3241
3252
|
|
|
@@ -4020,7 +4031,10 @@ def _codex_post_tool_output_artifact(
|
|
|
4020
4031
|
command_text = _codex_post_tool_command_text(payload)
|
|
4021
4032
|
if not command_text:
|
|
4022
4033
|
command_text = tool_name
|
|
4023
|
-
|
|
4034
|
+
local_source_matches = _codex_sensitive_local_source_matches(command_text, cwd=cwd)
|
|
4035
|
+
references_local_content = bool(local_source_matches) or _codex_command_may_read_local_content(
|
|
4036
|
+
command_text, cwd=cwd
|
|
4037
|
+
)
|
|
4024
4038
|
content_matches = classify_secret_content(response_text)
|
|
4025
4039
|
if not content_matches and references_local_content:
|
|
4026
4040
|
content_matches = classify_secret_content(response_text, suppress_samples=False)
|
|
@@ -4043,10 +4057,38 @@ def _codex_post_tool_output_artifact(
|
|
|
4043
4057
|
sort_keys=True,
|
|
4044
4058
|
).encode("utf-8")
|
|
4045
4059
|
).hexdigest()
|
|
4060
|
+
local_secret_source = _codex_local_secret_source_label(
|
|
4061
|
+
local_source_matches,
|
|
4062
|
+
command_text=command_text,
|
|
4063
|
+
)
|
|
4046
4064
|
runtime_default_action = "require-reapproval" if references_local_content else "warn"
|
|
4047
4065
|
runtime_request_signals = ["tool output contains credential-looking material"]
|
|
4048
4066
|
if references_local_content:
|
|
4049
|
-
|
|
4067
|
+
source_signal = "command references local secrets"
|
|
4068
|
+
if local_secret_source is not None:
|
|
4069
|
+
source_signal = f"command references local secrets from {local_secret_source}"
|
|
4070
|
+
runtime_request_signals.append(source_signal)
|
|
4071
|
+
request_summary = _codex_tool_output_request_summary(
|
|
4072
|
+
tool_name=tool_name,
|
|
4073
|
+
command_text=command_text,
|
|
4074
|
+
local_secret_source=local_secret_source,
|
|
4075
|
+
)
|
|
4076
|
+
runtime_request_summary = _codex_tool_output_runtime_summary(local_secret_source)
|
|
4077
|
+
metadata: dict[str, object] = {
|
|
4078
|
+
"tool_name": tool_name,
|
|
4079
|
+
"command_text": command_text,
|
|
4080
|
+
"action_class": "credential exfiltration shell command",
|
|
4081
|
+
"guard_default_action": runtime_default_action,
|
|
4082
|
+
"request_summary": request_summary,
|
|
4083
|
+
"runtime_request_signals": runtime_request_signals,
|
|
4084
|
+
"runtime_request_summary": runtime_request_summary,
|
|
4085
|
+
"runtime_request_reason": (
|
|
4086
|
+
"Guard inspects supported Codex tool output before Codex uses it, so accidental secret reads can be "
|
|
4087
|
+
"stopped even when the filename was not obviously sensitive."
|
|
4088
|
+
),
|
|
4089
|
+
}
|
|
4090
|
+
if local_secret_source is not None:
|
|
4091
|
+
metadata["secret_source_family"] = local_secret_source
|
|
4050
4092
|
return GuardArtifact(
|
|
4051
4093
|
artifact_id=f"codex:{source_scope}:tool-output:{fingerprint}",
|
|
4052
4094
|
name=f"{tool_name} credential-looking output",
|
|
@@ -4054,42 +4096,40 @@ def _codex_post_tool_output_artifact(
|
|
|
4054
4096
|
artifact_type="tool_action_request",
|
|
4055
4097
|
source_scope=source_scope,
|
|
4056
4098
|
config_path=config_path,
|
|
4057
|
-
metadata=
|
|
4058
|
-
"tool_name": tool_name,
|
|
4059
|
-
"command_text": command_text,
|
|
4060
|
-
"action_class": "credential exfiltration shell command",
|
|
4061
|
-
"guard_default_action": runtime_default_action,
|
|
4062
|
-
"request_summary": (
|
|
4063
|
-
f"Codex tool `{tool_name}` produced credential-looking output while running `{command_text}`."
|
|
4064
|
-
),
|
|
4065
|
-
"runtime_request_signals": runtime_request_signals,
|
|
4066
|
-
"runtime_request_summary": (
|
|
4067
|
-
"Requests a sensitive native tool action: credential-looking output reached Codex."
|
|
4068
|
-
),
|
|
4069
|
-
"runtime_request_reason": (
|
|
4070
|
-
"Guard inspects supported Codex tool output before Codex uses it, so accidental secret reads can be "
|
|
4071
|
-
"stopped even when the filename was not obviously sensitive."
|
|
4072
|
-
),
|
|
4073
|
-
},
|
|
4099
|
+
metadata=metadata,
|
|
4074
4100
|
)
|
|
4075
4101
|
|
|
4076
4102
|
|
|
4077
4103
|
def _codex_command_references_sensitive_local_source(command_text: str, *, cwd: Path | None) -> bool:
|
|
4078
|
-
|
|
4079
|
-
|
|
4104
|
+
return bool(_codex_sensitive_local_source_matches(command_text, cwd=cwd))
|
|
4105
|
+
|
|
4106
|
+
|
|
4107
|
+
def _codex_sensitive_local_source_matches(command_text: str, *, cwd: Path | None) -> list[SecretPathMatch]:
|
|
4108
|
+
matches = _codex_sensitive_path_matches_in_text(command_text, cwd=cwd)
|
|
4080
4109
|
try:
|
|
4081
4110
|
parts = shlex.split(command_text)
|
|
4082
4111
|
except ValueError:
|
|
4083
|
-
return
|
|
4112
|
+
return matches
|
|
4084
4113
|
for part in parts:
|
|
4085
4114
|
stripped = part.strip()
|
|
4086
|
-
if not stripped or stripped.startswith("-"):
|
|
4115
|
+
if not stripped or stripped.startswith("-") or _codex_token_is_url(stripped):
|
|
4087
4116
|
continue
|
|
4088
|
-
|
|
4117
|
+
path_match = classify_secret_path(stripped, cwd=cwd)
|
|
4118
|
+
if path_match is not None:
|
|
4119
|
+
matches.append(path_match)
|
|
4120
|
+
return _dedupe_codex_secret_path_matches(matches)
|
|
4121
|
+
|
|
4122
|
+
|
|
4123
|
+
def _dedupe_codex_secret_path_matches(matches: list[SecretPathMatch]) -> list[SecretPathMatch]:
|
|
4124
|
+
deduped: list[SecretPathMatch] = []
|
|
4125
|
+
seen: set[tuple[str, str]] = set()
|
|
4126
|
+
for match in matches:
|
|
4127
|
+
key = (match.family, match.requested_path or match.path)
|
|
4128
|
+
if key in seen:
|
|
4089
4129
|
continue
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
return
|
|
4130
|
+
seen.add(key)
|
|
4131
|
+
deduped.append(match)
|
|
4132
|
+
return deduped
|
|
4093
4133
|
|
|
4094
4134
|
|
|
4095
4135
|
def _codex_token_is_url(token: str) -> bool:
|
|
@@ -4098,12 +4138,18 @@ def _codex_token_is_url(token: str) -> bool:
|
|
|
4098
4138
|
|
|
4099
4139
|
|
|
4100
4140
|
def _codex_text_contains_sensitive_path_token(text: str, *, cwd: Path | None) -> bool:
|
|
4141
|
+
return bool(_codex_sensitive_path_matches_in_text(text, cwd=cwd))
|
|
4142
|
+
|
|
4143
|
+
|
|
4144
|
+
def _codex_sensitive_path_matches_in_text(text: str, *, cwd: Path | None) -> list[SecretPathMatch]:
|
|
4145
|
+
matches: list[SecretPathMatch] = []
|
|
4101
4146
|
for match in _PROMPT_PATH_TOKEN_PATTERN.finditer(text):
|
|
4102
4147
|
if _codex_path_token_is_url_path(text, match.start()):
|
|
4103
4148
|
continue
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4149
|
+
path_match = classify_secret_path(match.group(0), cwd=cwd)
|
|
4150
|
+
if path_match is not None:
|
|
4151
|
+
matches.append(path_match)
|
|
4152
|
+
return matches
|
|
4107
4153
|
|
|
4108
4154
|
|
|
4109
4155
|
def _codex_path_token_is_url_path(text: str, start: int) -> bool:
|
|
@@ -4150,7 +4196,10 @@ def _codex_pipeline_segment_may_read_local_content(segment: str, *, index: int,
|
|
|
4150
4196
|
if not parts:
|
|
4151
4197
|
return False
|
|
4152
4198
|
if index == 0:
|
|
4153
|
-
return
|
|
4199
|
+
return _codex_command_parts_are_environment_dump(parts) or _codex_command_parts_may_read_local_content(
|
|
4200
|
+
parts,
|
|
4201
|
+
cwd=cwd,
|
|
4202
|
+
)
|
|
4154
4203
|
return _codex_command_is_read_only_source_search(segment, cwd=cwd) or _codex_command_is_read_only_source_view(
|
|
4155
4204
|
segment, cwd=cwd
|
|
4156
4205
|
)
|
|
@@ -4207,6 +4256,65 @@ def _codex_command_parts_are_git_grep(parts: list[str]) -> bool:
|
|
|
4207
4256
|
return bool(parts) and Path(parts[0]).name.lower() == "git" and _git_grep_search_args(parts[1:]) is not None
|
|
4208
4257
|
|
|
4209
4258
|
|
|
4259
|
+
def _codex_command_reads_environment_pipeline(command_text: str) -> bool:
|
|
4260
|
+
pipeline_segments = _split_codex_safe_read_only_pipeline(command_text)
|
|
4261
|
+
if pipeline_segments is None:
|
|
4262
|
+
return False
|
|
4263
|
+
try:
|
|
4264
|
+
first_parts = _codex_shell_split(pipeline_segments[0])
|
|
4265
|
+
except ValueError:
|
|
4266
|
+
return False
|
|
4267
|
+
return _codex_command_parts_are_environment_dump(first_parts)
|
|
4268
|
+
|
|
4269
|
+
|
|
4270
|
+
def _codex_command_parts_are_environment_dump(parts: list[str]) -> bool:
|
|
4271
|
+
if not parts:
|
|
4272
|
+
return False
|
|
4273
|
+
executable = Path(parts[0]).name.lower()
|
|
4274
|
+
if executable == "printenv":
|
|
4275
|
+
return True
|
|
4276
|
+
if executable != "env":
|
|
4277
|
+
return False
|
|
4278
|
+
if _codex_env_args_clear_environment(parts[1:]):
|
|
4279
|
+
return False
|
|
4280
|
+
return not _codex_strip_env_wrapper(parts[1:])
|
|
4281
|
+
|
|
4282
|
+
|
|
4283
|
+
def _codex_local_secret_source_label(
|
|
4284
|
+
matches: list[SecretPathMatch],
|
|
4285
|
+
*,
|
|
4286
|
+
command_text: str,
|
|
4287
|
+
) -> str | None:
|
|
4288
|
+
families: list[str] = []
|
|
4289
|
+
for match in matches:
|
|
4290
|
+
if match.family not in families:
|
|
4291
|
+
families.append(match.family)
|
|
4292
|
+
if families:
|
|
4293
|
+
if len(families) == 1:
|
|
4294
|
+
return families[0]
|
|
4295
|
+
return f"{families[0]} and other local secret files"
|
|
4296
|
+
if _codex_command_reads_environment_pipeline(command_text):
|
|
4297
|
+
return "environment variables"
|
|
4298
|
+
return None
|
|
4299
|
+
|
|
4300
|
+
|
|
4301
|
+
def _codex_tool_output_request_summary(
|
|
4302
|
+
*,
|
|
4303
|
+
tool_name: str,
|
|
4304
|
+
command_text: str,
|
|
4305
|
+
local_secret_source: str | None,
|
|
4306
|
+
) -> str:
|
|
4307
|
+
if local_secret_source is not None:
|
|
4308
|
+
return f"Codex tool `{tool_name}` read local secrets from {local_secret_source} while running `{command_text}`."
|
|
4309
|
+
return f"Codex tool `{tool_name}` produced credential-looking output while running `{command_text}`."
|
|
4310
|
+
|
|
4311
|
+
|
|
4312
|
+
def _codex_tool_output_runtime_summary(local_secret_source: str | None) -> str:
|
|
4313
|
+
if local_secret_source is not None:
|
|
4314
|
+
return f"Local secrets from {local_secret_source} reached Codex tool output."
|
|
4315
|
+
return "Requests a sensitive native tool action: credential-looking output reached Codex."
|
|
4316
|
+
|
|
4317
|
+
|
|
4210
4318
|
def _codex_unwrapped_command_parts(parts: list[str]) -> list[str]:
|
|
4211
4319
|
remaining = parts
|
|
4212
4320
|
while remaining:
|
|
@@ -4255,6 +4363,18 @@ def _codex_strip_env_wrapper(parts: list[str]) -> list[str]:
|
|
|
4255
4363
|
return []
|
|
4256
4364
|
|
|
4257
4365
|
|
|
4366
|
+
def _codex_env_args_clear_environment(parts: list[str]) -> bool:
|
|
4367
|
+
for part in parts:
|
|
4368
|
+
if part == "--":
|
|
4369
|
+
return False
|
|
4370
|
+
if part in {"-i", "--ignore-environment"}:
|
|
4371
|
+
return True
|
|
4372
|
+
if part.startswith("-") or ("=" in part and not part.startswith("=")):
|
|
4373
|
+
continue
|
|
4374
|
+
return False
|
|
4375
|
+
return False
|
|
4376
|
+
|
|
4377
|
+
|
|
4258
4378
|
def _codex_shell_split(command_text: str) -> list[str]:
|
|
4259
4379
|
lexer = shlex.shlex(command_text, posix=True, punctuation_chars=True)
|
|
4260
4380
|
lexer.whitespace_split = True
|
{plugin_scanner-2.0.110 → plugin_scanner-2.0.112}/src/codex_plugin_scanner/guard/cli/render.py
RENAMED
|
@@ -50,6 +50,7 @@ _SENSITIVE_STRING_PATTERNS: tuple[tuple[re.Pattern[str], str], ...] = (
|
|
|
50
50
|
),
|
|
51
51
|
(re.compile(r"(?i)(authorization:\s*)(bearer\s+)?[^\s,;]+"), r"\1*****"),
|
|
52
52
|
(re.compile(r"(?i)(api[-_ ]?key:\s*)[^\s,;]+"), r"\1*****"),
|
|
53
|
+
(re.compile(r"\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\b"), "*****"),
|
|
53
54
|
(re.compile(r"(?i)(bearer\s+)[^\s,;]+"), r"\1*****"),
|
|
54
55
|
(re.compile(r"(?im)\b(?:_authToken|npm[_ -]?token)\s*[:=]\s*[^\s]+"), "npm token redacted"),
|
|
55
56
|
(re.compile(r"\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis|amqp)://[^\s]+", re.IGNORECASE), "*****"),
|
|
@@ -963,6 +964,8 @@ def _render_hook(console: Console, payload: dict[str, object]) -> None:
|
|
|
963
964
|
body.add_row("Recorded", _bool_label(bool(payload.get("recorded"))))
|
|
964
965
|
body.add_row("Artifact", str(payload.get("artifact_name") or payload.get("artifact_id") or "unknown"))
|
|
965
966
|
body.add_row("Decision", _action_text(str(payload.get("policy_action", "warn"))))
|
|
967
|
+
if payload.get("risk_summary"):
|
|
968
|
+
body.add_row("Why", str(payload.get("risk_summary")))
|
|
966
969
|
if payload.get("path_summary"):
|
|
967
970
|
body.add_row("Path", str(payload.get("path_summary")))
|
|
968
971
|
if payload.get("approval_center_url"):
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
"""Local data-flow source and sink helpers for Guard runtime detectors."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from collections.abc import Iterable
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from itertools import pairwise
|
|
9
|
+
from typing import Literal
|
|
10
|
+
|
|
11
|
+
DataSourceType = Literal[
|
|
12
|
+
"secret_file",
|
|
13
|
+
"env",
|
|
14
|
+
"clipboard",
|
|
15
|
+
"keychain",
|
|
16
|
+
"command_output",
|
|
17
|
+
"prompt",
|
|
18
|
+
"generated_file",
|
|
19
|
+
]
|
|
20
|
+
DataSinkType = Literal[
|
|
21
|
+
"http_post",
|
|
22
|
+
"http_get_query",
|
|
23
|
+
"dns",
|
|
24
|
+
"webhook",
|
|
25
|
+
"paste",
|
|
26
|
+
"git_remote",
|
|
27
|
+
"package_publish",
|
|
28
|
+
"clipboard",
|
|
29
|
+
"local_log",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
_VALID_SOURCE_TYPES = frozenset(
|
|
33
|
+
{
|
|
34
|
+
"secret_file",
|
|
35
|
+
"env",
|
|
36
|
+
"clipboard",
|
|
37
|
+
"keychain",
|
|
38
|
+
"command_output",
|
|
39
|
+
"prompt",
|
|
40
|
+
"generated_file",
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
_VALID_SINK_TYPES = frozenset(
|
|
44
|
+
{
|
|
45
|
+
"http_post",
|
|
46
|
+
"http_get_query",
|
|
47
|
+
"dns",
|
|
48
|
+
"webhook",
|
|
49
|
+
"paste",
|
|
50
|
+
"git_remote",
|
|
51
|
+
"package_publish",
|
|
52
|
+
"clipboard",
|
|
53
|
+
"local_log",
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
_HTTP_METHODS = frozenset({"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"})
|
|
57
|
+
_INPUT_REDIRECT_PATTERN = re.compile(r"(?<![<])(?:\d*)<\s*(?![<&])(?P<target>\"[^\"]+\"|'[^']+'|[^ \t\r\n;&|<>]+)")
|
|
58
|
+
_URL_PATTERN = re.compile(r"https?://[^\s\"'<>)}\]]+", re.IGNORECASE)
|
|
59
|
+
_CURL_METHOD_PATTERN = re.compile(
|
|
60
|
+
r"(?i)(?:^|[\s;&|])(?:curl|curl\.exe)\b[^\r\n;&|]*?"
|
|
61
|
+
r"(?:--request(?:=|\s+)|-X\s*)['\"]?(?P<method>[a-z]+)['\"]?\b"
|
|
62
|
+
)
|
|
63
|
+
_FETCH_METHOD_PATTERN = re.compile(r"(?i)\bmethod\s*:\s*['\"](?P<method>[a-z]+)['\"]")
|
|
64
|
+
_REQUESTS_METHOD_PATTERN = re.compile(r"(?i)\brequests\.(?P<method>get|post|put|patch|delete|head|options)\s*\(")
|
|
65
|
+
_CURL_DATA_PATTERN = re.compile(
|
|
66
|
+
r"(?i)(?:^|[\s;&|])(?:curl|curl\.exe)\b[^\r\n;&|]*(?:\s-d\b|\s--data(?:-raw|-binary|-urlencode)?\b)"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass(frozen=True, slots=True)
|
|
71
|
+
class DataSource:
|
|
72
|
+
"""Redacted local data source referenced by a runtime action."""
|
|
73
|
+
|
|
74
|
+
source_type: DataSourceType
|
|
75
|
+
value: str
|
|
76
|
+
description: str
|
|
77
|
+
evidence: str | None = None
|
|
78
|
+
|
|
79
|
+
def __post_init__(self) -> None:
|
|
80
|
+
if self.source_type not in _VALID_SOURCE_TYPES:
|
|
81
|
+
raise ValueError("source_type must be a known Guard data source type")
|
|
82
|
+
if not self.value.strip():
|
|
83
|
+
raise ValueError("value must be a non-empty redacted source identifier")
|
|
84
|
+
if not self.description.strip():
|
|
85
|
+
raise ValueError("description must be a non-empty source description")
|
|
86
|
+
|
|
87
|
+
def to_dict(self) -> dict[str, object]:
|
|
88
|
+
return {
|
|
89
|
+
"source_type": self.source_type,
|
|
90
|
+
"value": self.value,
|
|
91
|
+
"description": self.description,
|
|
92
|
+
"evidence": self.evidence,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass(frozen=True, slots=True)
|
|
97
|
+
class DataSink:
|
|
98
|
+
"""Redacted destination where local data may leave the trusted context."""
|
|
99
|
+
|
|
100
|
+
sink_type: DataSinkType
|
|
101
|
+
value: str
|
|
102
|
+
description: str
|
|
103
|
+
method: str | None = None
|
|
104
|
+
evidence: str | None = None
|
|
105
|
+
|
|
106
|
+
def __post_init__(self) -> None:
|
|
107
|
+
if self.sink_type not in _VALID_SINK_TYPES:
|
|
108
|
+
raise ValueError("sink_type must be a known Guard data sink type")
|
|
109
|
+
if not self.value.strip():
|
|
110
|
+
raise ValueError("value must be a non-empty redacted sink identifier")
|
|
111
|
+
if not self.description.strip():
|
|
112
|
+
raise ValueError("description must be a non-empty sink description")
|
|
113
|
+
if self.method is not None:
|
|
114
|
+
if not isinstance(self.method, str):
|
|
115
|
+
raise ValueError("method must be a known HTTP method")
|
|
116
|
+
normalized = self.method.upper()
|
|
117
|
+
if normalized not in _HTTP_METHODS:
|
|
118
|
+
raise ValueError("method must be a known HTTP method")
|
|
119
|
+
object.__setattr__(self, "method", normalized)
|
|
120
|
+
|
|
121
|
+
def to_dict(self) -> dict[str, object]:
|
|
122
|
+
return {
|
|
123
|
+
"sink_type": self.sink_type,
|
|
124
|
+
"value": self.value,
|
|
125
|
+
"description": self.description,
|
|
126
|
+
"method": self.method,
|
|
127
|
+
"evidence": self.evidence,
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@dataclass(frozen=True, slots=True)
|
|
132
|
+
class ShellPipe:
|
|
133
|
+
"""Top-level shell pipe edge between adjacent command segments."""
|
|
134
|
+
|
|
135
|
+
left: str
|
|
136
|
+
right: str
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def extract_input_redirects(command: str) -> tuple[str, ...]:
|
|
140
|
+
"""Return file targets read through shell input redirects."""
|
|
141
|
+
|
|
142
|
+
targets: list[str] = []
|
|
143
|
+
for segment in _split_top_level_commands(command):
|
|
144
|
+
for match in _INPUT_REDIRECT_PATTERN.finditer(segment):
|
|
145
|
+
target = _strip_shell_quotes(match.group("target"))
|
|
146
|
+
if target and not target.startswith(("(", "&")):
|
|
147
|
+
targets.append(target)
|
|
148
|
+
return _dedupe(targets)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def extract_command_substitutions(command: str) -> tuple[str, ...]:
|
|
152
|
+
"""Return commands inside top-level `$()` and backtick substitutions."""
|
|
153
|
+
|
|
154
|
+
substitutions: list[str] = []
|
|
155
|
+
index = 0
|
|
156
|
+
quote: str | None = None
|
|
157
|
+
while index < len(command):
|
|
158
|
+
char = command[index]
|
|
159
|
+
if char == "\\":
|
|
160
|
+
index += 2
|
|
161
|
+
continue
|
|
162
|
+
if char == "'" and quote is None:
|
|
163
|
+
quote = "'"
|
|
164
|
+
index += 1
|
|
165
|
+
continue
|
|
166
|
+
if char == "'" and quote == "'":
|
|
167
|
+
quote = None
|
|
168
|
+
index += 1
|
|
169
|
+
continue
|
|
170
|
+
if char == '"' and quote is None:
|
|
171
|
+
quote = '"'
|
|
172
|
+
index += 1
|
|
173
|
+
continue
|
|
174
|
+
if char == '"' and quote == '"':
|
|
175
|
+
quote = None
|
|
176
|
+
index += 1
|
|
177
|
+
continue
|
|
178
|
+
if quote != "'" and command.startswith("$(", index):
|
|
179
|
+
extracted, end_index = _extract_parenthesized(command, index + 2)
|
|
180
|
+
if extracted.strip():
|
|
181
|
+
substitutions.append(extracted.strip())
|
|
182
|
+
index = end_index + 1
|
|
183
|
+
continue
|
|
184
|
+
if quote != "'" and char == "`":
|
|
185
|
+
extracted, end_index = _extract_backtick(command, index + 1)
|
|
186
|
+
if extracted.strip():
|
|
187
|
+
substitutions.append(extracted.strip())
|
|
188
|
+
index = end_index + 1
|
|
189
|
+
continue
|
|
190
|
+
index += 1
|
|
191
|
+
return tuple(substitutions)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def extract_pipes(command: str) -> tuple[ShellPipe, ...]:
|
|
195
|
+
"""Return adjacent top-level pipe edges, ignoring logical OR and quoted pipes."""
|
|
196
|
+
|
|
197
|
+
pipes: list[ShellPipe] = []
|
|
198
|
+
for segment in _split_top_level_commands(command):
|
|
199
|
+
parts = _split_top_level_pipes(segment)
|
|
200
|
+
if len(parts) < 2:
|
|
201
|
+
continue
|
|
202
|
+
for left, right in pairwise(parts):
|
|
203
|
+
stripped_left = left.strip()
|
|
204
|
+
stripped_right = right.strip()
|
|
205
|
+
if stripped_left and stripped_right:
|
|
206
|
+
pipes.append(ShellPipe(left=stripped_left, right=stripped_right))
|
|
207
|
+
return tuple(pipes)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def extract_http_methods(command: str) -> tuple[str, ...]:
|
|
211
|
+
"""Return explicit or strongly implied HTTP methods referenced by shell text."""
|
|
212
|
+
|
|
213
|
+
methods: list[str] = []
|
|
214
|
+
for pattern in (_CURL_METHOD_PATTERN, _FETCH_METHOD_PATTERN, _REQUESTS_METHOD_PATTERN):
|
|
215
|
+
for match in pattern.finditer(command):
|
|
216
|
+
_append_http_method(methods, match.group("method"))
|
|
217
|
+
if _CURL_DATA_PATTERN.search(command):
|
|
218
|
+
_append_http_method(methods, "POST")
|
|
219
|
+
return _dedupe(methods)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def extract_urls(command: str) -> tuple[str, ...]:
|
|
223
|
+
"""Return HTTP(S) URLs while preserving first-seen order."""
|
|
224
|
+
|
|
225
|
+
urls = [_strip_url_suffix(match.group(0)) for match in _URL_PATTERN.finditer(command)]
|
|
226
|
+
return _dedupe(url for url in urls if url)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _append_http_method(methods: list[str], method: str) -> None:
|
|
230
|
+
normalized = method.upper()
|
|
231
|
+
if normalized in _HTTP_METHODS:
|
|
232
|
+
methods.append(normalized)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _dedupe(values: Iterable[str]) -> tuple[str, ...]:
|
|
236
|
+
seen: set[str] = set()
|
|
237
|
+
result: list[str] = []
|
|
238
|
+
for value in values:
|
|
239
|
+
if value in seen:
|
|
240
|
+
continue
|
|
241
|
+
seen.add(value)
|
|
242
|
+
result.append(value)
|
|
243
|
+
return tuple(result)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _strip_shell_quotes(value: str) -> str:
|
|
247
|
+
stripped = value.strip()
|
|
248
|
+
if len(stripped) >= 2 and stripped[0] == stripped[-1] and stripped[0] in {"'", '"'}:
|
|
249
|
+
return stripped[1:-1]
|
|
250
|
+
return stripped
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _strip_url_suffix(value: str) -> str:
|
|
254
|
+
return value.rstrip(".,;")
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _split_top_level_commands(command: str) -> tuple[str, ...]:
|
|
258
|
+
parts: list[str] = []
|
|
259
|
+
start = 0
|
|
260
|
+
index = 0
|
|
261
|
+
state = _ShellScanState()
|
|
262
|
+
while index < len(command):
|
|
263
|
+
next_index = state.advance(command, index)
|
|
264
|
+
if next_index != index + 1:
|
|
265
|
+
index = next_index
|
|
266
|
+
continue
|
|
267
|
+
if state.is_top_level and command[index] == ";":
|
|
268
|
+
_append_segment(parts, command[start:index])
|
|
269
|
+
start = index + 1
|
|
270
|
+
elif state.is_top_level and (command.startswith("&&", index) or command.startswith("||", index)):
|
|
271
|
+
_append_segment(parts, command[start:index])
|
|
272
|
+
start = index + 2
|
|
273
|
+
index += 1
|
|
274
|
+
index += 1
|
|
275
|
+
_append_segment(parts, command[start:])
|
|
276
|
+
return tuple(parts)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _split_top_level_pipes(command: str) -> tuple[str, ...]:
|
|
280
|
+
parts: list[str] = []
|
|
281
|
+
start = 0
|
|
282
|
+
index = 0
|
|
283
|
+
state = _ShellScanState()
|
|
284
|
+
while index < len(command):
|
|
285
|
+
next_index = state.advance(command, index)
|
|
286
|
+
if next_index != index + 1:
|
|
287
|
+
index = next_index
|
|
288
|
+
continue
|
|
289
|
+
if state.is_top_level and command[index] == "|":
|
|
290
|
+
previous_is_pipe = index > 0 and command[index - 1] == "|"
|
|
291
|
+
next_is_pipe = index + 1 < len(command) and command[index + 1] == "|"
|
|
292
|
+
if not previous_is_pipe and not next_is_pipe:
|
|
293
|
+
_append_segment(parts, command[start:index])
|
|
294
|
+
start = index + 1
|
|
295
|
+
index += 1
|
|
296
|
+
_append_segment(parts, command[start:])
|
|
297
|
+
return tuple(parts)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _append_segment(parts: list[str], value: str) -> None:
|
|
301
|
+
stripped = value.strip()
|
|
302
|
+
if stripped:
|
|
303
|
+
parts.append(stripped)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _extract_parenthesized(command: str, start: int) -> tuple[str, int]:
|
|
307
|
+
depth = 1
|
|
308
|
+
index = start
|
|
309
|
+
quote: str | None = None
|
|
310
|
+
while index < len(command):
|
|
311
|
+
char = command[index]
|
|
312
|
+
if char == "\\":
|
|
313
|
+
index += 2
|
|
314
|
+
continue
|
|
315
|
+
if char in {"'", '"'}:
|
|
316
|
+
if quote is None:
|
|
317
|
+
quote = char
|
|
318
|
+
elif quote == char:
|
|
319
|
+
quote = None
|
|
320
|
+
index += 1
|
|
321
|
+
continue
|
|
322
|
+
if quote is None and char == "(":
|
|
323
|
+
depth += 1
|
|
324
|
+
elif quote is None and char == ")":
|
|
325
|
+
depth -= 1
|
|
326
|
+
if depth == 0:
|
|
327
|
+
return command[start:index], index
|
|
328
|
+
index += 1
|
|
329
|
+
return command[start:], len(command)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def _extract_backtick(command: str, start: int) -> tuple[str, int]:
|
|
333
|
+
index = start
|
|
334
|
+
while index < len(command):
|
|
335
|
+
char = command[index]
|
|
336
|
+
if char == "\\":
|
|
337
|
+
index += 2
|
|
338
|
+
continue
|
|
339
|
+
if char == "`":
|
|
340
|
+
return command[start:index], index
|
|
341
|
+
index += 1
|
|
342
|
+
return command[start:], len(command)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
class _ShellScanState:
|
|
346
|
+
def __init__(self) -> None:
|
|
347
|
+
self.quote: str | None = None
|
|
348
|
+
self.subshell_depth = 0
|
|
349
|
+
self.in_backtick = False
|
|
350
|
+
|
|
351
|
+
@property
|
|
352
|
+
def is_top_level(self) -> bool:
|
|
353
|
+
return self.quote is None and self.subshell_depth == 0 and not self.in_backtick
|
|
354
|
+
|
|
355
|
+
def advance(self, command: str, index: int) -> int:
|
|
356
|
+
char = command[index]
|
|
357
|
+
if char == "\\":
|
|
358
|
+
return min(len(command), index + 2)
|
|
359
|
+
if char == "`" and self.quote != "'":
|
|
360
|
+
self.in_backtick = not self.in_backtick
|
|
361
|
+
return index + 1
|
|
362
|
+
if self.in_backtick:
|
|
363
|
+
return index + 1
|
|
364
|
+
if self.quote == "'":
|
|
365
|
+
if char == "'":
|
|
366
|
+
self.quote = None
|
|
367
|
+
return index + 1
|
|
368
|
+
if char == "'" and self.quote is None:
|
|
369
|
+
self.quote = "'"
|
|
370
|
+
return index + 1
|
|
371
|
+
if char == '"':
|
|
372
|
+
if self.quote == '"':
|
|
373
|
+
self.quote = None
|
|
374
|
+
elif self.quote is None:
|
|
375
|
+
self.quote = '"'
|
|
376
|
+
return index + 1
|
|
377
|
+
if self.quote != "'" and command.startswith("$(", index):
|
|
378
|
+
_extracted, end_index = _extract_parenthesized(command, index + 2)
|
|
379
|
+
return min(len(command), end_index + 1)
|
|
380
|
+
if self.quote is None and char == "(":
|
|
381
|
+
self.subshell_depth += 1
|
|
382
|
+
elif self.quote is None and char == ")" and self.subshell_depth > 0:
|
|
383
|
+
self.subshell_depth -= 1
|
|
384
|
+
return index + 1
|