plugin-scanner 2.0.61__tar.gz → 2.0.62__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.61 → plugin_scanner-2.0.62}/PKG-INFO +1 -1
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/pyproject.toml +1 -1
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/pyproject.toml.bak +1 -1
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/adapters/claude_code.py +42 -3
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/cli/commands.py +77 -2
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/version.py +1 -1
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_claude_adapter.py +57 -3
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_runtime.py +156 -18
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_surface_server.py +3 -15
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/uv.lock +22 -22
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.clusterfuzzlite/Dockerfile +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.clusterfuzzlite/build.sh +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.clusterfuzzlite/project.yaml +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.clusterfuzzlite/requirements-atheris.txt +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.dockerignore +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.github/CODEOWNERS +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.github/ISSUE_TEMPLATE/bug-report.yml +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.github/dependabot.yml +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.github/workflows/ci.yml +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.github/workflows/codeql.yml +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.github/workflows/dependabot-uv-lock.yml +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.github/workflows/fuzz.yml +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.github/workflows/harness-smoke.yml +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.github/workflows/publish.yml +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.github/workflows/scorecard.yml +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.gitignore +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/.pre-commit-hooks.yaml +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/CONTRIBUTING.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/Dockerfile +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/LICENSE +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/README.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/SECURITY.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/index.html +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/package.json +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/pnpm-lock.yaml +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/public/brand/Logo_Whole.png +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/src/app.tsx +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/src/approval-center-layout.tsx +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/src/approval-center-primitives.tsx +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/src/approval-center-utils.ts +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/src/fleet-workspace.tsx +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/src/guard-api.ts +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/src/guard-demo.ts +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/src/guard-types.ts +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/src/main.tsx +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/src/receipts-workspace.tsx +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/src/runtime-overview.tsx +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/src/styles.css +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/src/vite-env.d.ts +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/tsconfig.json +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/dashboard/vite.config.ts +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/docker-requirements.txt +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/docs/guard/approval-audit.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/docs/guard/architecture.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/docs/guard/get-started.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/docs/guard/harness-support.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/docs/guard/local-vs-cloud.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/docs/guard/testing-matrix.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/docs/trust/mcp-trust-draft.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/docs/trust/plugin-trust-draft.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/docs/trust/skill-trust-local.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/fuzzers/manifest_fuzzer.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/requirements.txt +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/schemas/plugin-quality.v1.json +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/schemas/scan-result.v1.json +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/schemas/verify-result.v1.json +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/__init__.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/action_runner.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/argparse_utils.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/checks/__init__.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/checks/best_practices.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/checks/claude.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/checks/code_quality.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/checks/ecosystem_common.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/checks/gemini.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/checks/manifest.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/checks/manifest_support.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/checks/marketplace.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/checks/mcp_security.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/checks/opencode.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/checks/operational_security.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/checks/security.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/checks/skill_security.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/cli.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/cli_ui.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/config.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/ecosystems/__init__.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/ecosystems/base.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/ecosystems/claude.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/ecosystems/codex.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/ecosystems/detect.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/ecosystems/gemini.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/ecosystems/opencode.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/ecosystems/registry.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/ecosystems/types.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/github_reporting.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/__init__.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/adapters/__init__.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/adapters/antigravity.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/adapters/base.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/adapters/codex.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/adapters/copilot.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/adapters/cursor.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/adapters/gemini.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/adapters/hermes.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/adapters/mcp_servers.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/adapters/opencode.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/adapters/opencode_artifacts.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/approvals.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/bridge/__init__.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/capabilities.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/cli/__init__.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/cli/approval_commands.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/cli/bootstrap.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/cli/connect_flow.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/cli/install_commands.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/cli/product.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/cli/prompt.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/cli/render.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/cli/update_commands.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/codex_config.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/config.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/consumer/__init__.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/consumer/service.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/daemon/__init__.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/daemon/client.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/daemon/manager.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/daemon/server.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/daemon/static/assets/guard-dashboard.js +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/daemon/static/assets/index.css +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Whole.png +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/daemon/static/index.html +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/incident.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/launcher.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/mcp_tool_calls.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/models.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/policy/__init__.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/policy/engine.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/protect.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/proxy/__init__.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/proxy/remote.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/proxy/runtime_mcp.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/proxy/stdio.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/receipts/__init__.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/receipts/manager.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/risk.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/runtime/__init__.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/runtime/runner.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/runtime/secret_file_requests.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/runtime/surface_server.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/schemas/__init__.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/schemas/consumer_mode.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/schemas/surface_server.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/shims.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/store.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/store_approvals.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/store_connect.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/types.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/integrations/__init__.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/integrations/cisco_mcp_scanner.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/integrations/cisco_skill_scanner.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/lint_fixes.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/marketplace_support.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/models.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/path_support.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/policy.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/quality_artifact.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/repo_detect.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/reporting.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/rules/__init__.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/rules/registry.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/rules/specs.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/scanner.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/submission.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/suppressions.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/trust_domain_scoring.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/trust_helpers.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/trust_mcp_scoring.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/trust_models.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/trust_plugin_scoring.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/trust_scoring.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/trust_skill_scoring.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/trust_specs.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/verification.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/__init__.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/conftest.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/__init__.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/bad-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/bad-plugin/.mcp.json +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/bad-plugin/secrets.js +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/claude-plugin-good/.claude-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/claude-plugin-good/LICENSE +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/claude-plugin-good/README.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/claude-plugin-good/SECURITY.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/claude-plugin-good/hooks/hooks.json +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/claude-plugin-good/skills/example/SKILL.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/code-quality-bad/evil.js +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/code-quality-bad/inject.js +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/gemini-extension-good/GEMINI.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/gemini-extension-good/LICENSE +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/gemini-extension-good/README.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/gemini-extension-good/SECURITY.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/gemini-extension-good/commands/hello.toml +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/gemini-extension-good/gemini-extension.json +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/good-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/good-plugin/.codexignore +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/good-plugin/LICENSE +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/good-plugin/README.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/good-plugin/SECURITY.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/good-plugin/assets/icon.svg +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/good-plugin/assets/logo.svg +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/good-plugin/assets/screenshot.svg +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/good-plugin/skills/example/SKILL.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/guard-codex-malicious-mcp/.codex/config.toml +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/hermes-plugin-evil/config.yaml +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/hermes-plugin-evil/mcp_servers.json +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/hermes-plugin-evil/skills/security/malicious/SKILL.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/SKILL.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/references/api-setup.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/scripts/deploy.sh +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/hermes-plugin-evil/skills/utils/benign/SKILL.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/malformed-json/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/malicious-skill-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/malicious-skill-plugin/.codexignore +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/malicious-skill-plugin/LICENSE +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/malicious-skill-plugin/README.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/malicious-skill-plugin/SECURITY.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/malicious-skill-plugin/skills/leaky-skill/SKILL.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/mcp-canary-server.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/minimal-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/missing-fields/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/mit-license/LICENSE +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/multi-ecosystem-repo/codex-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/multi-ecosystem-repo/codex-plugin/LICENSE +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/multi-ecosystem-repo/codex-plugin/README.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/multi-ecosystem-repo/codex-plugin/SECURITY.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/multi-ecosystem-repo/gemini-ext/README.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/multi-ecosystem-repo/gemini-ext/gemini-extension.json +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/multi-plugin-repo/.agents/plugins/marketplace.json +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codexignore +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/LICENSE +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/README.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/SECURITY.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/skills/example/SKILL.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/skills/example/SKILL.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/no-version/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/opencode-good/.opencode/commands/hello.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/opencode-good/.opencode/plugins/example.ts +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/opencode-good/LICENSE +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/opencode-good/README.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/opencode-good/SECURITY.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/opencode-good/opencode.jsonc +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/skills-missing-dir/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/skills-no-frontmatter/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/skills-no-frontmatter/skills/bad-skill/SKILL.md +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/with-marketplace/.codex-plugin/plugin.json +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/with-marketplace/marketplace-broken.json +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/fixtures/with-marketplace/marketplace.json +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test-trust-scoring.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test-trust-specs.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_action_runner.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_best_practices.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_cisco_install_surfaces.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_cli.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_code_quality.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_config.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_coverage_remaining.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_ecosystems.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_edge_cases.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_final_coverage.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_approvals.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_bootstrap.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_capabilities.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_cli.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_codex_e2e.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_codex_install.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_codex_proxy.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_config_paths.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_connect_flow.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_consumer_mode.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_copilot_adapter.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_copilot_proxy.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_daemon_manager.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_events.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_launch_env.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_opencode_proxy.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_product_flow.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_protect.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_render.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_risk.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_store_migrations.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_guard_verdicts.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_hermes_adapter.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_integration.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_lint_fixes.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_live_cisco_smoke.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_manifest.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_marketplace.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_mcp_security.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_operational_security.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_policy.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_quality_artifact.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_rule_registry.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_scanner.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_schema_contracts.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_security.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_security_ops.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_skill_security.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_submission.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_trust_scoring.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_trust_specs.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_verification.py +0 -0
- {plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/tests/test_versioning.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plugin-scanner
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.62
|
|
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.62"
|
|
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.62"
|
|
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"
|
|
@@ -44,6 +44,7 @@ def _shell_command(command: tuple[str, ...], *, windows: bool | None = None) ->
|
|
|
44
44
|
def _sync_runtime_hook_groups(hooks: dict[str, object], hook_command: str) -> None:
|
|
45
45
|
for key, matcher, timeout in (
|
|
46
46
|
("PreToolUse", CLAUDE_GUARD_TOOL_MATCHER, CLAUDE_GUARD_TOOL_TIMEOUT_SECONDS),
|
|
47
|
+
("PermissionRequest", CLAUDE_GUARD_TOOL_MATCHER, CLAUDE_GUARD_NOTIFICATION_TIMEOUT_SECONDS),
|
|
47
48
|
("PostToolUse", CLAUDE_GUARD_TOOL_MATCHER, CLAUDE_GUARD_TOOL_TIMEOUT_SECONDS),
|
|
48
49
|
("UserPromptSubmit", None, CLAUDE_GUARD_PROMPT_TIMEOUT_SECONDS),
|
|
49
50
|
("Notification", CLAUDE_GUARD_NOTIFICATION_MATCHER, CLAUDE_GUARD_NOTIFICATION_TIMEOUT_SECONDS),
|
|
@@ -486,6 +487,7 @@ class ClaudeCodeHarnessAdapter(HarnessAdapter):
|
|
|
486
487
|
def _daemon_hook_command_parts(context: HarnessContext) -> tuple[str, ...]:
|
|
487
488
|
fallback_daemon_url = load_guard_daemon_url(context.guard_home) or guard_daemon_url_for_home(context.guard_home)
|
|
488
489
|
state_path = context.guard_home / "daemon-state.json"
|
|
490
|
+
fallback_command = ClaudeCodeHarnessAdapter._hook_command_parts(context)
|
|
489
491
|
query: dict[str, str] = {"guard-home": str(context.guard_home)}
|
|
490
492
|
if context.home_dir.resolve() != Path.home().resolve():
|
|
491
493
|
query["home"] = str(context.home_dir)
|
|
@@ -496,9 +498,11 @@ class ClaudeCodeHarnessAdapter(HarnessAdapter):
|
|
|
496
498
|
"void MARKER;"
|
|
497
499
|
"const fs=require('fs');"
|
|
498
500
|
"const http=require('http');"
|
|
501
|
+
"const cp=require('child_process');"
|
|
499
502
|
"const {URL}=require('url');"
|
|
500
503
|
f"const statePath={str(state_path)!r};"
|
|
501
504
|
f"const fallbackUrl={fallback_daemon_url!r};"
|
|
505
|
+
f"const fallbackCommand={list(fallback_command)!r};"
|
|
502
506
|
f"const query={urlencode(query)!r};"
|
|
503
507
|
"function daemonUrl(){"
|
|
504
508
|
"try{"
|
|
@@ -507,9 +511,41 @@ class ClaudeCodeHarnessAdapter(HarnessAdapter):
|
|
|
507
511
|
"}catch(_error){}"
|
|
508
512
|
"return fallbackUrl;"
|
|
509
513
|
"}"
|
|
514
|
+
"function eventName(data){"
|
|
515
|
+
"try{const payload=JSON.parse(data||'{}');"
|
|
516
|
+
"return String(payload.hook_event_name||payload.event||'PreToolUse');}"
|
|
517
|
+
"catch(_error){return 'PreToolUse';}"
|
|
518
|
+
"}"
|
|
519
|
+
"function degraded(reason,data){"
|
|
520
|
+
"const event=eventName(data);"
|
|
521
|
+
"const message=`HOL Guard could not reach the local daemon (${reason}), so it is using Claude's native "
|
|
522
|
+
"approval prompt as a safety fallback.`;"
|
|
523
|
+
"if(event==='UserPromptSubmit'){return '';}"
|
|
524
|
+
"if(event==='PreToolUse'){return JSON.stringify({systemMessage:'HOL Guard opened this Claude approval "
|
|
525
|
+
"prompt because local daemon evaluation was unavailable.',hookSpecificOutput:{hookEventName:'PreToolUse',"
|
|
526
|
+
"permissionDecision:'ask',permissionDecisionReason:`${message} Choose Yes to allow it once, Yes during "
|
|
527
|
+
"this session to trust the same action for this session, or No to keep it blocked.`}});}"
|
|
528
|
+
"return '{}';"
|
|
529
|
+
"}"
|
|
530
|
+
"function shouldSuppressOutput(data,responseBody){"
|
|
531
|
+
"if(eventName(data)!=='UserPromptSubmit')return false;"
|
|
532
|
+
"const trimmed=(responseBody||'').trim();"
|
|
533
|
+
"return trimmed===''||trimmed==='{}';"
|
|
534
|
+
"}"
|
|
535
|
+
"function runLocalFallback(reason,data){"
|
|
536
|
+
"try{"
|
|
537
|
+
"const result=cp.spawnSync(fallbackCommand[0],fallbackCommand.slice(1),{input:data,encoding:'utf8',"
|
|
538
|
+
"timeout:30000,env:process.env});"
|
|
539
|
+
"if(result.error)return degraded(`${reason}; fallback failed: ${result.error.message}`,data);"
|
|
540
|
+
"if(result.status===0){"
|
|
541
|
+
"if(shouldSuppressOutput(data,result.stdout)){process.exit(0);}"
|
|
542
|
+
"process.stdout.write(result.stdout&&result.stdout.trim()?result.stdout:'{}');"
|
|
543
|
+
"process.exit(0);}"
|
|
544
|
+
"return degraded(`${reason}; fallback exited ${result.status}`,data);"
|
|
545
|
+
"}catch(error){return degraded(`${reason}; fallback crashed: ${error.message}`,data);}"
|
|
546
|
+
"}"
|
|
510
547
|
"function fail(reason){"
|
|
511
|
-
"
|
|
512
|
-
"process.stdout.write(JSON.stringify({decision:'block',reason:message}));"
|
|
548
|
+
"process.stdout.write(runLocalFallback(reason,body.trim()?body:'{}'));"
|
|
513
549
|
"process.exit(0);"
|
|
514
550
|
"}"
|
|
515
551
|
"let body='';"
|
|
@@ -526,7 +562,9 @@ class ClaudeCodeHarnessAdapter(HarnessAdapter):
|
|
|
526
562
|
"response.setEncoding('utf8');"
|
|
527
563
|
"response.on('data',chunk=>{responseBody+=chunk;});"
|
|
528
564
|
"response.on('end',()=>{"
|
|
529
|
-
"if(response.statusCode>=200&&response.statusCode<300){
|
|
565
|
+
"if(response.statusCode>=200&&response.statusCode<300){"
|
|
566
|
+
"if(shouldSuppressOutput(data,responseBody)){process.exit(0);}"
|
|
567
|
+
"process.stdout.write(responseBody);process.exit(0);}"
|
|
530
568
|
"fail(`daemon returned HTTP ${response.statusCode||0}`);"
|
|
531
569
|
"});"
|
|
532
570
|
"});"
|
|
@@ -672,6 +710,7 @@ class ClaudeCodeHarnessAdapter(HarnessAdapter):
|
|
|
672
710
|
for key in (
|
|
673
711
|
"SessionStart",
|
|
674
712
|
"PreToolUse",
|
|
713
|
+
"PermissionRequest",
|
|
675
714
|
"PostToolUse",
|
|
676
715
|
"UserPromptSubmit",
|
|
677
716
|
"Notification",
|
{plugin_scanner-2.0.61 → plugin_scanner-2.0.62}/src/codex_plugin_scanner/guard/cli/commands.py
RENAMED
|
@@ -1187,8 +1187,25 @@ def run_guard_command(
|
|
|
1187
1187
|
guard_home=context.guard_home,
|
|
1188
1188
|
workspace=runtime_workspace,
|
|
1189
1189
|
)
|
|
1190
|
+
if _is_claude_permission_request(args, payload):
|
|
1191
|
+
notice = _peek_claude_permission_notice(store, payload)
|
|
1192
|
+
if notice is None:
|
|
1193
|
+
_emit_claude_permission_request_passthrough(output_stream=output_stream)
|
|
1194
|
+
return 0
|
|
1195
|
+
_mark_claude_pending_permission_prompt_seen(store=store, payload=payload, notice=notice)
|
|
1196
|
+
_emit_native_hook_response(
|
|
1197
|
+
harness=args.harness,
|
|
1198
|
+
policy_action="require-reapproval",
|
|
1199
|
+
event_name="PermissionRequest",
|
|
1200
|
+
reason="HOL Guard is keeping Claude's native permission prompt open for user review.",
|
|
1201
|
+
system_message=_claude_permission_prompt_system_message(payload=payload, notice=notice),
|
|
1202
|
+
additional_context=_claude_permission_prompt_additional_context(notice),
|
|
1203
|
+
output_stream=output_stream,
|
|
1204
|
+
)
|
|
1205
|
+
return 0
|
|
1190
1206
|
if _is_claude_permission_prompt_notification(args, payload):
|
|
1191
1207
|
notice = _load_claude_permission_notice(store, payload)
|
|
1208
|
+
_mark_claude_pending_permission_prompt_seen(store=store, payload=payload, notice=notice)
|
|
1192
1209
|
store.add_event(
|
|
1193
1210
|
"claude/permission_prompt",
|
|
1194
1211
|
{
|
|
@@ -1205,7 +1222,6 @@ def run_guard_command(
|
|
|
1205
1222
|
_emit_native_hook_notification_stderr(
|
|
1206
1223
|
_claude_permission_prompt_terminal_notice(payload=payload, notice=notice)
|
|
1207
1224
|
)
|
|
1208
|
-
return 2
|
|
1209
1225
|
_emit_native_hook_response(
|
|
1210
1226
|
harness=args.harness,
|
|
1211
1227
|
policy_action="allow",
|
|
@@ -1330,6 +1346,14 @@ def run_guard_command(
|
|
|
1330
1346
|
"risk_headline": incident["risk_headline"],
|
|
1331
1347
|
"path_summary": _runtime_requested_path(runtime_artifact),
|
|
1332
1348
|
}
|
|
1349
|
+
if (
|
|
1350
|
+
_canonical_harness_name(args.harness) == "claude-code"
|
|
1351
|
+
and event_name == "UserPromptSubmit"
|
|
1352
|
+
and policy_action == "require-reapproval"
|
|
1353
|
+
and not _prompt_requires_hard_block(runtime_artifact)
|
|
1354
|
+
and (not getattr(args, "json", False) or output_stream is not None)
|
|
1355
|
+
):
|
|
1356
|
+
return 0
|
|
1333
1357
|
if policy_action in {"block", "sandbox-required", "require-reapproval"}:
|
|
1334
1358
|
native_reason = _runtime_artifact_native_reason(runtime_artifact, response_payload)
|
|
1335
1359
|
additional_context = _claude_prompt_additional_context(
|
|
@@ -1704,6 +1728,11 @@ def _should_emit_prequeue_native_hook_response(
|
|
|
1704
1728
|
return output_stream is not None
|
|
1705
1729
|
|
|
1706
1730
|
|
|
1731
|
+
def _emit_claude_permission_request_passthrough(*, output_stream: TextIO | None = None) -> None:
|
|
1732
|
+
if output_stream is not None:
|
|
1733
|
+
output_stream.write("")
|
|
1734
|
+
|
|
1735
|
+
|
|
1707
1736
|
def _claude_permission_notice_state_key(session_id: str, tool_name: str | None = None) -> str:
|
|
1708
1737
|
if tool_name is not None:
|
|
1709
1738
|
return f"claude_permission_notice:{session_id}:{tool_name}"
|
|
@@ -1812,6 +1841,46 @@ def _load_claude_permission_notice(store: GuardStore, payload: dict[str, object]
|
|
|
1812
1841
|
return None
|
|
1813
1842
|
|
|
1814
1843
|
|
|
1844
|
+
def _peek_claude_permission_notice(store: GuardStore, payload: dict[str, object]) -> dict[str, object] | None:
|
|
1845
|
+
session_id = _optional_string(payload.get("session_id"))
|
|
1846
|
+
if session_id is None:
|
|
1847
|
+
return None
|
|
1848
|
+
tool_name = _claude_notification_tool_name(payload)
|
|
1849
|
+
try:
|
|
1850
|
+
persisted = store.get_sync_payload(_claude_permission_notice_state_key(session_id, tool_name))
|
|
1851
|
+
if persisted is None and tool_name is not None:
|
|
1852
|
+
persisted = store.get_sync_payload(_claude_permission_notice_state_key(session_id))
|
|
1853
|
+
except (OSError, sqlite3.Error):
|
|
1854
|
+
return None
|
|
1855
|
+
return persisted if isinstance(persisted, dict) else None
|
|
1856
|
+
|
|
1857
|
+
|
|
1858
|
+
def _mark_claude_pending_permission_prompt_seen(
|
|
1859
|
+
*,
|
|
1860
|
+
store: GuardStore,
|
|
1861
|
+
payload: dict[str, object],
|
|
1862
|
+
notice: dict[str, object] | None,
|
|
1863
|
+
) -> None:
|
|
1864
|
+
session_id = _optional_string(payload.get("session_id"))
|
|
1865
|
+
artifact_id = _optional_string((notice or {}).get("artifact_id"))
|
|
1866
|
+
if session_id is None or artifact_id is None:
|
|
1867
|
+
return
|
|
1868
|
+
pending_key = _claude_pending_permission_state_key(session_id, artifact_id)
|
|
1869
|
+
try:
|
|
1870
|
+
pending = store.get_sync_payload(pending_key)
|
|
1871
|
+
except (OSError, sqlite3.Error):
|
|
1872
|
+
return
|
|
1873
|
+
if not isinstance(pending, dict):
|
|
1874
|
+
return
|
|
1875
|
+
updated = dict(pending)
|
|
1876
|
+
updated["permission_prompt_seen"] = True
|
|
1877
|
+
updated["permission_prompt_seen_at"] = _now()
|
|
1878
|
+
try:
|
|
1879
|
+
store.set_sync_payload(pending_key, updated, _now())
|
|
1880
|
+
except (OSError, sqlite3.Error):
|
|
1881
|
+
return
|
|
1882
|
+
|
|
1883
|
+
|
|
1815
1884
|
def _load_claude_pending_permission(
|
|
1816
1885
|
store: GuardStore,
|
|
1817
1886
|
payload: dict[str, object],
|
|
@@ -1963,6 +2032,8 @@ def _persist_claude_pending_permission_denials(store: GuardStore, payload: dict[
|
|
|
1963
2032
|
continue
|
|
1964
2033
|
if not isinstance(pending, dict):
|
|
1965
2034
|
continue
|
|
2035
|
+
if pending.get("permission_prompt_seen") is not True:
|
|
2036
|
+
continue
|
|
1966
2037
|
artifact_id = _optional_string(pending.get("artifact_id"))
|
|
1967
2038
|
artifact_hash_value = _optional_string(pending.get("artifact_hash"))
|
|
1968
2039
|
if artifact_id is None or artifact_hash_value is None:
|
|
@@ -2019,6 +2090,10 @@ def _is_claude_permission_prompt_notification(args: argparse.Namespace, payload:
|
|
|
2019
2090
|
)
|
|
2020
2091
|
|
|
2021
2092
|
|
|
2093
|
+
def _is_claude_permission_request(args: argparse.Namespace, payload: dict[str, object]) -> bool:
|
|
2094
|
+
return _canonical_harness_name(args.harness) == "claude-code" and _hook_event_name(payload) == "PermissionRequest"
|
|
2095
|
+
|
|
2096
|
+
|
|
2022
2097
|
def _claude_permission_prompt_system_message(
|
|
2023
2098
|
*,
|
|
2024
2099
|
payload: dict[str, object],
|
|
@@ -2377,7 +2452,7 @@ def _emit_native_hook_response(
|
|
|
2377
2452
|
if payload:
|
|
2378
2453
|
_write_json_line(payload, output_stream=output_stream)
|
|
2379
2454
|
return
|
|
2380
|
-
if event_name
|
|
2455
|
+
if event_name in {"Notification", "PermissionRequest"}:
|
|
2381
2456
|
if additional_context:
|
|
2382
2457
|
payload["hookSpecificOutput"] = {
|
|
2383
2458
|
"hookEventName": event_name,
|
|
@@ -47,7 +47,7 @@ def _runtime_hook_handlers(payload: dict[str, object]) -> list[dict[str, object]
|
|
|
47
47
|
hooks = payload["hooks"]
|
|
48
48
|
assert isinstance(hooks, dict)
|
|
49
49
|
handlers: list[dict[str, object]] = []
|
|
50
|
-
for key in ("PreToolUse", "PostToolUse", "UserPromptSubmit", "Notification", "Stop"):
|
|
50
|
+
for key in ("PreToolUse", "PermissionRequest", "PostToolUse", "UserPromptSubmit", "Notification", "Stop"):
|
|
51
51
|
entries = hooks[key]
|
|
52
52
|
assert isinstance(entries, list)
|
|
53
53
|
for entry in entries:
|
|
@@ -144,6 +144,7 @@ def test_claude_install_writes_session_start_and_command_hook_schema_and_is_idem
|
|
|
144
144
|
payload = json.loads(settings_path.read_text(encoding="utf-8"))
|
|
145
145
|
session_start = payload["hooks"]["SessionStart"]
|
|
146
146
|
pre_tool_use = payload["hooks"]["PreToolUse"]
|
|
147
|
+
permission_request = payload["hooks"]["PermissionRequest"]
|
|
147
148
|
post_tool_use = payload["hooks"]["PostToolUse"]
|
|
148
149
|
prompt_submit = payload["hooks"]["UserPromptSubmit"]
|
|
149
150
|
notification = payload["hooks"]["Notification"]
|
|
@@ -157,6 +158,10 @@ def test_claude_install_writes_session_start_and_command_hook_schema_and_is_idem
|
|
|
157
158
|
assert CLAUDE_GUARD_DAEMON_HOOK_MARKER in pre_tool_use[0]["hooks"][0]["command"]
|
|
158
159
|
assert "url" not in pre_tool_use[0]["hooks"][0]
|
|
159
160
|
assert pre_tool_use[0]["hooks"][0]["timeout"] == 30
|
|
161
|
+
assert len(permission_request) == 1
|
|
162
|
+
assert permission_request[0]["matcher"] == "Bash|Read|Write|Edit|MultiEdit|WebFetch|WebSearch|mcp__.*"
|
|
163
|
+
assert permission_request[0]["hooks"][0]["type"] == "command"
|
|
164
|
+
assert permission_request[0]["hooks"][0]["timeout"] == 10
|
|
160
165
|
assert len(post_tool_use) == 1
|
|
161
166
|
assert post_tool_use[0]["hooks"][0]["type"] == "command"
|
|
162
167
|
assert len(prompt_submit) == 1
|
|
@@ -249,6 +254,7 @@ def test_claude_install_replaces_legacy_http_guard_hooks(tmp_path):
|
|
|
249
254
|
"command",
|
|
250
255
|
"command",
|
|
251
256
|
"command",
|
|
257
|
+
"command",
|
|
252
258
|
]
|
|
253
259
|
assert all(CLAUDE_GUARD_DAEMON_HOOK_MARKER in str(handler.get("command", "")) for handler in installed_handlers)
|
|
254
260
|
assert all("url" not in handler for handler in installed_handlers)
|
|
@@ -365,12 +371,60 @@ def test_claude_daemon_hook_command_survives_shell_execution(tmp_path):
|
|
|
365
371
|
check=False,
|
|
366
372
|
)
|
|
367
373
|
|
|
374
|
+
assert result.returncode == 0
|
|
375
|
+
assert result.stderr == ""
|
|
376
|
+
assert result.stdout == ""
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def test_claude_daemon_hook_command_falls_back_without_blocking_prompt_on_daemon_miss(tmp_path):
|
|
380
|
+
context = _build_context(tmp_path)
|
|
381
|
+
adapter = ClaudeCodeHarnessAdapter()
|
|
382
|
+
command = adapter._daemon_hook_command(context)
|
|
383
|
+
|
|
384
|
+
result = subprocess.run(
|
|
385
|
+
["/bin/sh", "-c", command],
|
|
386
|
+
input=json.dumps(
|
|
387
|
+
{
|
|
388
|
+
"hook_event_name": "UserPromptSubmit",
|
|
389
|
+
"prompt": "Use the Read tool to open ./.env and print the full file contents exactly.",
|
|
390
|
+
}
|
|
391
|
+
),
|
|
392
|
+
text=True,
|
|
393
|
+
capture_output=True,
|
|
394
|
+
timeout=5,
|
|
395
|
+
check=False,
|
|
396
|
+
)
|
|
397
|
+
assert result.returncode == 0
|
|
398
|
+
assert result.stderr == ""
|
|
399
|
+
assert result.stdout == ""
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def test_claude_daemon_hook_command_falls_back_to_native_ask_on_daemon_miss(tmp_path):
|
|
403
|
+
context = _build_context(tmp_path)
|
|
404
|
+
adapter = ClaudeCodeHarnessAdapter()
|
|
405
|
+
command = adapter._daemon_hook_command(context)
|
|
406
|
+
|
|
407
|
+
result = subprocess.run(
|
|
408
|
+
["/bin/sh", "-c", command],
|
|
409
|
+
input=json.dumps(
|
|
410
|
+
{
|
|
411
|
+
"hook_event_name": "PreToolUse",
|
|
412
|
+
"tool_name": "Read",
|
|
413
|
+
"tool_input": {"file_path": str(context.workspace_dir / ".env")},
|
|
414
|
+
}
|
|
415
|
+
),
|
|
416
|
+
text=True,
|
|
417
|
+
capture_output=True,
|
|
418
|
+
timeout=5,
|
|
419
|
+
check=False,
|
|
420
|
+
)
|
|
368
421
|
payload = json.loads(result.stdout)
|
|
369
422
|
|
|
370
423
|
assert result.returncode == 0
|
|
371
424
|
assert result.stderr == ""
|
|
372
|
-
assert payload["
|
|
373
|
-
assert "
|
|
425
|
+
assert payload["hookSpecificOutput"]["hookEventName"] == "PreToolUse"
|
|
426
|
+
assert payload["hookSpecificOutput"]["permissionDecision"] == "ask"
|
|
427
|
+
assert "HOL Guard" in payload["hookSpecificOutput"]["permissionDecisionReason"]
|
|
374
428
|
|
|
375
429
|
|
|
376
430
|
def test_claude_install_replaces_prior_session_start_guard_handlers_when_context_changes(tmp_path):
|
|
@@ -4317,6 +4317,21 @@ def test_guard_hook_claude_stop_persists_unapproved_native_prompt_as_denied(tmp_
|
|
|
4317
4317
|
capsys=capsys,
|
|
4318
4318
|
monkeypatch=monkeypatch,
|
|
4319
4319
|
)
|
|
4320
|
+
notification_rc, notification_output = _run_guard_hook(
|
|
4321
|
+
home_dir=home_dir,
|
|
4322
|
+
workspace_dir=workspace_dir,
|
|
4323
|
+
harness="claude-code",
|
|
4324
|
+
event={
|
|
4325
|
+
"session_id": "session-claude-deny",
|
|
4326
|
+
"hook_event_name": "Notification",
|
|
4327
|
+
"notification_type": "permission_prompt",
|
|
4328
|
+
"title": "Permission needed",
|
|
4329
|
+
"message": "Claude needs your permission to use Read",
|
|
4330
|
+
"tool_name": "Read",
|
|
4331
|
+
},
|
|
4332
|
+
capsys=capsys,
|
|
4333
|
+
monkeypatch=monkeypatch,
|
|
4334
|
+
)
|
|
4320
4335
|
stop_rc, stop_output = _run_guard_hook(
|
|
4321
4336
|
home_dir=home_dir,
|
|
4322
4337
|
workspace_dir=workspace_dir,
|
|
@@ -4338,6 +4353,8 @@ def test_guard_hook_claude_stop_persists_unapproved_native_prompt_as_denied(tmp_
|
|
|
4338
4353
|
|
|
4339
4354
|
assert first_rc == 0
|
|
4340
4355
|
assert first_payload["hookSpecificOutput"]["permissionDecision"] == "ask"
|
|
4356
|
+
assert notification_rc == 0
|
|
4357
|
+
assert "HOL Guard intercepted Claude's attempt to use Read" in json.loads(notification_output)["systemMessage"]
|
|
4341
4358
|
assert stop_rc == 0
|
|
4342
4359
|
assert stop_output == ""
|
|
4343
4360
|
assert second_rc == 0
|
|
@@ -4345,6 +4362,59 @@ def test_guard_hook_claude_stop_persists_unapproved_native_prompt_as_denied(tmp_
|
|
|
4345
4362
|
assert "HOL Guard blocked Claude's attempt to use Read" in second_payload["systemMessage"]
|
|
4346
4363
|
|
|
4347
4364
|
|
|
4365
|
+
def test_guard_hook_claude_stop_does_not_persist_denial_without_visible_prompt(
|
|
4366
|
+
tmp_path,
|
|
4367
|
+
capsys,
|
|
4368
|
+
monkeypatch,
|
|
4369
|
+
):
|
|
4370
|
+
home_dir = tmp_path / "home"
|
|
4371
|
+
workspace_dir = tmp_path / "workspace"
|
|
4372
|
+
_build_guard_fixture(home_dir, workspace_dir)
|
|
4373
|
+
first_event = {
|
|
4374
|
+
"session_id": "session-claude-headless",
|
|
4375
|
+
"hook_event_name": "PreToolUse",
|
|
4376
|
+
"tool_name": "Read",
|
|
4377
|
+
"tool_input": {"file_path": str(workspace_dir / ".env")},
|
|
4378
|
+
"source_scope": "project",
|
|
4379
|
+
}
|
|
4380
|
+
monkeypatch.setattr(guard_commands_module, "ensure_guard_daemon", lambda _guard_home: "http://127.0.0.1:4455")
|
|
4381
|
+
|
|
4382
|
+
first_rc, first_output = _run_guard_hook(
|
|
4383
|
+
home_dir=home_dir,
|
|
4384
|
+
workspace_dir=workspace_dir,
|
|
4385
|
+
harness="claude-code",
|
|
4386
|
+
event=first_event,
|
|
4387
|
+
capsys=capsys,
|
|
4388
|
+
monkeypatch=monkeypatch,
|
|
4389
|
+
)
|
|
4390
|
+
stop_rc, stop_output = _run_guard_hook(
|
|
4391
|
+
home_dir=home_dir,
|
|
4392
|
+
workspace_dir=workspace_dir,
|
|
4393
|
+
harness="claude-code",
|
|
4394
|
+
event={"session_id": "session-claude-headless", "hook_event_name": "Stop", "stop_hook_active": False},
|
|
4395
|
+
capsys=capsys,
|
|
4396
|
+
monkeypatch=monkeypatch,
|
|
4397
|
+
)
|
|
4398
|
+
second_rc, second_output = _run_guard_hook(
|
|
4399
|
+
home_dir=home_dir,
|
|
4400
|
+
workspace_dir=workspace_dir,
|
|
4401
|
+
harness="claude-code",
|
|
4402
|
+
event={**first_event, "session_id": "session-claude-headless-next"},
|
|
4403
|
+
capsys=capsys,
|
|
4404
|
+
monkeypatch=monkeypatch,
|
|
4405
|
+
)
|
|
4406
|
+
first_payload = json.loads(first_output)
|
|
4407
|
+
second_payload = json.loads(second_output)
|
|
4408
|
+
|
|
4409
|
+
assert first_rc == 0
|
|
4410
|
+
assert first_payload["hookSpecificOutput"]["permissionDecision"] == "ask"
|
|
4411
|
+
assert stop_rc == 0
|
|
4412
|
+
assert stop_output == ""
|
|
4413
|
+
assert second_rc == 0
|
|
4414
|
+
assert second_payload["hookSpecificOutput"]["permissionDecision"] == "ask"
|
|
4415
|
+
assert "HOL Guard intercepted Claude's attempt to use Read" in second_payload["systemMessage"]
|
|
4416
|
+
|
|
4417
|
+
|
|
4348
4418
|
def test_guard_hook_emits_claude_native_ask_response_for_claude_alias(tmp_path, capsys, monkeypatch):
|
|
4349
4419
|
home_dir = tmp_path / "home"
|
|
4350
4420
|
workspace_dir = tmp_path / "workspace"
|
|
@@ -4521,15 +4591,9 @@ def test_guard_hook_brands_claude_user_prompt_submit_before_native_approval(
|
|
|
4521
4591
|
monkeypatch=monkeypatch,
|
|
4522
4592
|
)
|
|
4523
4593
|
receipts = GuardStore(home_dir).list_receipts()
|
|
4524
|
-
payload = json.loads(output)
|
|
4525
|
-
hook_output = payload["hookSpecificOutput"]
|
|
4526
4594
|
|
|
4527
4595
|
assert rc == 0
|
|
4528
|
-
assert "
|
|
4529
|
-
assert "HOL Guard intercepted this prompt" in payload["systemMessage"]
|
|
4530
|
-
assert hook_output["hookEventName"] == "UserPromptSubmit"
|
|
4531
|
-
assert "HOL Guard intercepted Claude's next attempt to access local secrets" in hook_output["additionalContext"]
|
|
4532
|
-
assert "approval dialog" in hook_output["additionalContext"]
|
|
4596
|
+
assert output == ""
|
|
4533
4597
|
assert any(receipt["artifact_id"].startswith("claude-code:session:prompt") for receipt in receipts)
|
|
4534
4598
|
|
|
4535
4599
|
|
|
@@ -4554,15 +4618,9 @@ def test_guard_hook_brands_generic_claude_user_prompt_submit_before_native_appro
|
|
|
4554
4618
|
capsys=capsys,
|
|
4555
4619
|
monkeypatch=monkeypatch,
|
|
4556
4620
|
)
|
|
4557
|
-
payload = json.loads(output)
|
|
4558
|
-
hook_output = payload["hookSpecificOutput"]
|
|
4559
4621
|
|
|
4560
4622
|
assert rc == 0
|
|
4561
|
-
assert "
|
|
4562
|
-
assert "HOL Guard intercepted this prompt" in payload["systemMessage"]
|
|
4563
|
-
assert hook_output["hookEventName"] == "UserPromptSubmit"
|
|
4564
|
-
assert "HOL Guard intercepted Claude's next sensitive action" in hook_output["additionalContext"]
|
|
4565
|
-
assert "approval dialog" in hook_output["additionalContext"]
|
|
4623
|
+
assert output == ""
|
|
4566
4624
|
|
|
4567
4625
|
|
|
4568
4626
|
def test_guard_hook_emits_json_for_claude_user_prompt_submit_overridable_prompts(
|
|
@@ -4634,7 +4692,11 @@ def test_guard_hook_emits_claude_user_prompt_submit_block_reason_without_continu
|
|
|
4634
4692
|
assert "blocked this prompt" in output["reason"].lower()
|
|
4635
4693
|
|
|
4636
4694
|
|
|
4637
|
-
def
|
|
4695
|
+
def test_guard_hook_hard_blocks_claude_user_prompt_submit_bypass(
|
|
4696
|
+
tmp_path,
|
|
4697
|
+
capsys,
|
|
4698
|
+
monkeypatch,
|
|
4699
|
+
):
|
|
4638
4700
|
home_dir = tmp_path / "home"
|
|
4639
4701
|
workspace_dir = tmp_path / "workspace"
|
|
4640
4702
|
_build_guard_fixture(home_dir, workspace_dir)
|
|
@@ -4783,11 +4845,87 @@ def test_guard_hook_emits_claude_notification_notice_for_permission_prompt(tmp_p
|
|
|
4783
4845
|
|
|
4784
4846
|
assert pre_tool_rc == 0
|
|
4785
4847
|
assert pre_tool_output["hookSpecificOutput"]["permissionDecision"] == "ask"
|
|
4786
|
-
|
|
4787
|
-
|
|
4848
|
+
notification_payload = json.loads(notification_capture.out)
|
|
4849
|
+
|
|
4850
|
+
assert notification_rc == 0
|
|
4788
4851
|
assert "HOL Guard opened this Claude approval prompt for Read." in notification_capture.err
|
|
4789
4852
|
assert "protect your local secrets" in notification_capture.err
|
|
4790
|
-
assert "
|
|
4853
|
+
assert "HOL Guard intercepted Claude's attempt to use Read" in notification_payload["systemMessage"]
|
|
4854
|
+
assert "came from HOL Guard, not from Claude alone" in notification_payload["systemMessage"]
|
|
4855
|
+
assert "Yes during this session" in notification_payload["systemMessage"]
|
|
4856
|
+
assert "No to keep the sensitive action blocked" in notification_payload["systemMessage"]
|
|
4857
|
+
|
|
4858
|
+
|
|
4859
|
+
def test_guard_hook_emits_claude_permission_request_attribution_without_decision(
|
|
4860
|
+
tmp_path,
|
|
4861
|
+
capsys,
|
|
4862
|
+
monkeypatch,
|
|
4863
|
+
):
|
|
4864
|
+
home_dir = tmp_path / "home"
|
|
4865
|
+
workspace_dir = tmp_path / "workspace"
|
|
4866
|
+
_build_guard_fixture(home_dir, workspace_dir)
|
|
4867
|
+
pre_tool_event = {
|
|
4868
|
+
"session_id": "session-claude-permission-request",
|
|
4869
|
+
"hook_event_name": "PreToolUse",
|
|
4870
|
+
"tool_name": "Read",
|
|
4871
|
+
"tool_input": {"file_path": str(workspace_dir / ".env")},
|
|
4872
|
+
"source_scope": "project",
|
|
4873
|
+
}
|
|
4874
|
+
monkeypatch.setattr(guard_commands_module, "ensure_guard_daemon", lambda _guard_home: "http://127.0.0.1:4455")
|
|
4875
|
+
pre_tool_rc, pre_tool_output = _run_guard_hook(
|
|
4876
|
+
home_dir=home_dir,
|
|
4877
|
+
workspace_dir=workspace_dir,
|
|
4878
|
+
harness="claude-code",
|
|
4879
|
+
event=pre_tool_event,
|
|
4880
|
+
capsys=capsys,
|
|
4881
|
+
monkeypatch=monkeypatch,
|
|
4882
|
+
)
|
|
4883
|
+
|
|
4884
|
+
permission_rc, permission_output = _run_guard_hook(
|
|
4885
|
+
home_dir=home_dir,
|
|
4886
|
+
workspace_dir=workspace_dir,
|
|
4887
|
+
harness="claude-code",
|
|
4888
|
+
event={**pre_tool_event, "hook_event_name": "PermissionRequest"},
|
|
4889
|
+
capsys=capsys,
|
|
4890
|
+
monkeypatch=monkeypatch,
|
|
4891
|
+
)
|
|
4892
|
+
permission_payload = json.loads(permission_output)
|
|
4893
|
+
|
|
4894
|
+
assert pre_tool_rc == 0
|
|
4895
|
+
assert json.loads(pre_tool_output)["hookSpecificOutput"]["permissionDecision"] == "ask"
|
|
4896
|
+
assert permission_rc == 0
|
|
4897
|
+
assert "HOL Guard intercepted Claude's attempt to use Read" in permission_payload["systemMessage"]
|
|
4898
|
+
assert "came from HOL Guard, not from Claude alone" in permission_payload["systemMessage"]
|
|
4899
|
+
assert permission_payload["hookSpecificOutput"]["hookEventName"] == "PermissionRequest"
|
|
4900
|
+
assert "decision" not in permission_payload["hookSpecificOutput"]
|
|
4901
|
+
|
|
4902
|
+
|
|
4903
|
+
def test_guard_hook_ignores_unattributed_claude_permission_request(
|
|
4904
|
+
tmp_path,
|
|
4905
|
+
capsys,
|
|
4906
|
+
monkeypatch,
|
|
4907
|
+
):
|
|
4908
|
+
home_dir = tmp_path / "home"
|
|
4909
|
+
workspace_dir = tmp_path / "workspace"
|
|
4910
|
+
_build_guard_fixture(home_dir, workspace_dir)
|
|
4911
|
+
|
|
4912
|
+
rc, output = _run_guard_hook(
|
|
4913
|
+
home_dir=home_dir,
|
|
4914
|
+
workspace_dir=workspace_dir,
|
|
4915
|
+
harness="claude-code",
|
|
4916
|
+
event={
|
|
4917
|
+
"session_id": "session-claude-unattributed-permission-request",
|
|
4918
|
+
"hook_event_name": "PermissionRequest",
|
|
4919
|
+
"tool_name": "Read",
|
|
4920
|
+
"tool_input": {"file_path": str(workspace_dir / "README.md")},
|
|
4921
|
+
"source_scope": "project",
|
|
4922
|
+
},
|
|
4923
|
+
capsys=capsys,
|
|
4924
|
+
monkeypatch=monkeypatch,
|
|
4925
|
+
)
|
|
4926
|
+
|
|
4927
|
+
assert rc == 0
|
|
4928
|
+
assert output == ""
|
|
4791
4929
|
|
|
4792
4930
|
|
|
4793
4931
|
def test_guard_hook_emits_claude_native_ask_for_sensitive_file_reads(
|
|
@@ -251,18 +251,9 @@ class TestGuardSurfaceServer:
|
|
|
251
251
|
finally:
|
|
252
252
|
daemon.stop()
|
|
253
253
|
|
|
254
|
-
assert
|
|
255
|
-
assert "HOL Guard intercepted this prompt" in hook_payload["systemMessage"]
|
|
256
|
-
assert hook_payload["hookSpecificOutput"]["hookEventName"] == "UserPromptSubmit"
|
|
257
|
-
assert (
|
|
258
|
-
"HOL Guard intercepted Claude's next attempt to access local secrets"
|
|
259
|
-
in hook_payload["hookSpecificOutput"]["additionalContext"]
|
|
260
|
-
)
|
|
261
|
-
assert "approval dialog" in hook_payload["hookSpecificOutput"]["additionalContext"]
|
|
254
|
+
assert hook_payload == {}
|
|
262
255
|
|
|
263
|
-
def
|
|
264
|
-
self, tmp_path
|
|
265
|
-
) -> None:
|
|
256
|
+
def test_guard_daemon_claude_hook_endpoint_blocks_guard_bypass_user_prompt_submit(self, tmp_path) -> None:
|
|
266
257
|
home_dir = tmp_path / "home"
|
|
267
258
|
workspace_dir = tmp_path / "workspace"
|
|
268
259
|
workspace_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -291,10 +282,7 @@ class TestGuardSurfaceServer:
|
|
|
291
282
|
daemon.stop()
|
|
292
283
|
|
|
293
284
|
assert hook_payload["decision"] == "block"
|
|
294
|
-
assert (
|
|
295
|
-
hook_payload["reason"]
|
|
296
|
-
== "HOL Guard blocked this prompt because it asks to bypass or disable Guard."
|
|
297
|
-
)
|
|
285
|
+
assert "bypass" in hook_payload["reason"].lower() or "disable" in hook_payload["reason"].lower()
|
|
298
286
|
|
|
299
287
|
def test_guard_daemon_background_start_auto_stops_after_idle_timeout(self, tmp_path) -> None:
|
|
300
288
|
guard_home = tmp_path / "pytest-of-user" / "guard-home"
|