interlockd 0.1.0__tar.gz → 0.1.2__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.
- interlockd-0.1.2/.github/ISSUE_TEMPLATE/agent.yml +20 -0
- interlockd-0.1.2/.github/ISSUE_TEMPLATE/bug.yml +29 -0
- interlockd-0.1.2/.github/ISSUE_TEMPLATE/config.yml +5 -0
- interlockd-0.1.2/.github/ISSUE_TEMPLATE/decision.yml +34 -0
- interlockd-0.1.2/.github/dependabot.yml +9 -0
- interlockd-0.1.2/.github/pull_request_template.md +10 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/.github/workflows/ci.yml +18 -6
- interlockd-0.1.2/.github/workflows/release.yml +95 -0
- interlockd-0.1.2/CHANGELOG.md +58 -0
- interlockd-0.1.2/CODE_OF_CONDUCT.md +10 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/CONTRIBUTING.md +7 -1
- interlockd-0.1.2/NOTICE +7 -0
- interlockd-0.1.0/README.md → interlockd-0.1.2/PKG-INFO +64 -9
- interlockd-0.1.0/PKG-INFO → interlockd-0.1.2/README.md +20 -39
- {interlockd-0.1.0 → interlockd-0.1.2}/SECURITY.md +11 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/action.yml +5 -4
- interlockd-0.1.2/benchmarks/agentdojo_template.py +49 -0
- interlockd-0.1.2/benchmarks/injecagent.py +89 -0
- interlockd-0.1.2/benchmarks/nl2bash.py +45 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/corpus/_corpus.yaml +1 -1
- {interlockd-0.1.0 → interlockd-0.1.2}/corpus/archetypes/_corpus.yaml +1 -1
- interlockd-0.1.2/demo/demo.sh +23 -0
- interlockd-0.1.2/demo/demo.tape +8 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/docs/ADAPTERS.md +9 -3
- interlockd-0.1.2/docs/BENCHMARKS.md +85 -0
- interlockd-0.1.2/docs/GETTING-STARTED.md +119 -0
- interlockd-0.1.2/docs/PERFORMANCE.md +33 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/docs/THREAT-MODEL.md +3 -2
- {interlockd-0.1.0 → interlockd-0.1.2}/docs/USER-GUIDE.md +78 -11
- {interlockd-0.1.0 → interlockd-0.1.2}/docs/mandate-example.cedar +4 -0
- interlockd-0.1.2/examples/agents/README.md +17 -0
- interlockd-0.1.2/examples/agents/claude_agent_sdk_agent.py +18 -0
- interlockd-0.1.2/examples/agents/crewai_crew.py +21 -0
- interlockd-0.1.2/examples/agents/google_adk_agent.py +15 -0
- interlockd-0.1.2/examples/agents/langgraph_agent.py +19 -0
- interlockd-0.1.2/examples/agents/openai_agents.py +17 -0
- interlockd-0.1.2/examples/agents/semantic_kernel_agent.py +24 -0
- interlockd-0.1.2/examples/agents/strands_agent.py +17 -0
- interlockd-0.1.2/examples/github-workflow.yml +16 -0
- interlockd-0.1.2/examples/policies/ci.yaml +11 -0
- interlockd-0.1.2/examples/policies/mcp-locked-down.yaml +15 -0
- interlockd-0.1.2/examples/policies/strict-team.yaml +29 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/__init__.py +1 -1
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/api.py +14 -1
- interlockd-0.1.2/interlockd/bench.py +113 -0
- interlockd-0.1.2/interlockd/capture.py +183 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/cli.py +224 -9
- interlockd-0.1.2/interlockd/control.py +69 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/data/packs/shell.jsonl +3 -1
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/det/describe.py +7 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/det/engine.py +6 -1
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/det/mandate.py +1 -1
- interlockd-0.1.2/interlockd/det/oauth.py +126 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/det/packs.py +6 -1
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/insights.py +111 -24
- interlockd-0.1.2/interlockd/integrations/claude_agent_sdk.py +84 -0
- interlockd-0.1.2/interlockd/integrations/crewai.py +79 -0
- interlockd-0.1.2/interlockd/integrations/google_adk.py +62 -0
- interlockd-0.1.2/interlockd/integrations/semantic_kernel.py +61 -0
- interlockd-0.1.2/interlockd/judge_setup.py +120 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/judges/local_probe.py +22 -8
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/mcp_http.py +121 -6
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/normalize/claude_code.py +1 -1
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/onboard.py +2 -2
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/pipeline.py +39 -6
- interlockd-0.1.2/interlockd/plugins.py +129 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/policy/__init__.py +8 -0
- interlockd-0.1.2/interlockd/policy/managed.py +131 -0
- interlockd-0.1.2/interlockd/policy/workspace.py +268 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/policy_lint.py +1 -1
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/shims/claude-code/interlock-hook.sh +1 -1
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/shims/kiro/interlock-hook.mjs +1 -1
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/shims/kiro/interlock-hook.sh +1 -1
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/store/spool.py +35 -10
- interlockd-0.1.2/interlockd/watch.py +78 -0
- interlockd-0.1.2/packaging/homebrew/Formula/interlock.rb +57 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/packaging/homebrew/README.md +5 -1
- {interlockd-0.1.0 → interlockd-0.1.2}/pyproject.toml +14 -3
- interlockd-0.1.2/sdk/js/README.md +48 -0
- interlockd-0.1.2/sdk/js/package.json +14 -0
- interlockd-0.1.2/sdk/js/src/index.d.ts +38 -0
- interlockd-0.1.2/sdk/js/src/index.js +159 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/conftest.py +7 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_api.py +4 -1
- interlockd-0.1.2/tests/test_bench.py +17 -0
- interlockd-0.1.2/tests/test_capture.py +59 -0
- interlockd-0.1.2/tests/test_claude_agent_sdk.py +77 -0
- interlockd-0.1.2/tests/test_control.py +64 -0
- interlockd-0.1.2/tests/test_crewai.py +63 -0
- interlockd-0.1.2/tests/test_examples.py +27 -0
- interlockd-0.1.2/tests/test_google_adk.py +92 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_insights.py +9 -0
- interlockd-0.1.2/tests/test_js_sdk.py +106 -0
- interlockd-0.1.2/tests/test_judge_setup.py +33 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_local_probe.py +26 -0
- interlockd-0.1.2/tests/test_managed_policy.py +129 -0
- interlockd-0.1.2/tests/test_mandate.py +32 -0
- interlockd-0.1.2/tests/test_mcp_oauth.py +184 -0
- interlockd-0.1.2/tests/test_plugins.py +108 -0
- interlockd-0.1.2/tests/test_repo_policy.py +126 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_sandbox.py +26 -0
- interlockd-0.1.2/tests/test_semantic_kernel.py +72 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_spool.py +11 -0
- interlockd-0.1.2/tests/test_version.py +36 -0
- interlockd-0.1.2/tests/test_watch.py +56 -0
- interlockd-0.1.0/.github/workflows/release.yml +0 -54
- interlockd-0.1.0/CHANGELOG.md +0 -28
- {interlockd-0.1.0 → interlockd-0.1.2}/.gitignore +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/LICENSE +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/adapters/README.md +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/adapters/antigravity/PAYLOADS.md +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/adapters/claude-code/PAYLOADS.md +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/adapters/claude-code/README.md +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/adapters/codex/PAYLOADS.md +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/adapters/codex/README.md +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/adapters/copilot-cli/PAYLOADS.md +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/adapters/cursor/PAYLOADS.md +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/adapters/gemini-cli/PAYLOADS.md +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/adapters/kiro/PAYLOADS.md +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/adapters/opencode/PAYLOADS.md +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/adapters/windsurf/PAYLOADS.md +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/corpus/README.md +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/corpus/archetypes/exfil_and_secrets.jsonl +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/corpus/archetypes/git_and_history.jsonl +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/corpus/archetypes/injection_persistence_supplychain.jsonl +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/corpus/archetypes/judge_confirmation.jsonl +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/corpus/archetypes/judge_exfil.jsonl +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/corpus/archetypes/judge_injection.jsonl +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/corpus/archetypes/judge_intent.jsonl +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/corpus/archetypes/slow_hijack.jsonl +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/corpus/external/README.md +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/corpus/external/samples/agentdojo.sample.json +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/corpus/external/samples/agentdrift.sample.json +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/corpus/external/samples/injecagent.sample.json +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/corpus/ogr-seed-v0/README.md +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/corpus/ogr-seed-v0/seed-v0.jsonl +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/docs/policy-example.yaml +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/docs/reports/M3-judge-calibration.md +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/docs/reports/ogr-seed-v0-results.jsonl +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/docs/reports/ogr-seed-v0-results.md +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/__main__.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/aibom.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/auth.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/calibration.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/compose.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/config.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/corpus.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/data/packs/_corpus.yaml +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/data/packs/egress.jsonl +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/data/packs/fileops.jsonl +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/data/packs/git.jsonl +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/data/packs/packages.jsonl +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/data/packs/persistence.jsonl +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/data/packs/secrets.jsonl +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/det/__init__.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/det/flow.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/det/mcp.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/det/memory.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/det/shellparse.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/egress.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/gateway.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/ids.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/integrations/__init__.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/integrations/client.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/integrations/langchain.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/integrations/openai_agents.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/integrations/strands.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/judges/__init__.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/judges/guardrails.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/judges/projector.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/judges/strands_decider.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/ledger.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/mcp_proxy.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/mcp_server.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/models.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/normalize/__init__.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/normalize/cursor.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/normalize/gemini_cli.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/normalize/kiro.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/normalize/more.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/ogr.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/otel.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/paths.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/render.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/sandbox.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/scan.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/schema/verdict.schema.json +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/selftest.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/service.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/shims/opencode/interlock.js +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/store/__init__.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/store/drainer.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/store/dynamodb.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/store/memory.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/testkit.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/tiers.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/transcript/__init__.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/transcript/claude_code.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/interlockd/transcript/kiro.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/packaging/homebrew/make_formula.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/fake_mcp_server.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/fixtures/claude-code/post-bash-inject.json +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/fixtures/claude-code/post-read-inject.json +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/fixtures/claude-code/pre-bash-block.json +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/fixtures/claude-code/pre-bash-inject.json +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/fixtures/codex/post-bash-benign.json +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/fixtures/codex/post-bash-inject.json +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/fixtures/codex/pre-bash-benign.json +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/fixtures/codex/pre-bash-block.json +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/fixtures/copilot-cli/post-bash-inject.json +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/fixtures/copilot-cli/pre-bash-benign.json +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/fixtures/copilot-cli/pre-bash-block.json +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/fixtures/cursor/post-read-benign.json +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/fixtures/cursor/post-shell-benign.json +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/fixtures/cursor/pre-read-benign.json +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/fixtures/cursor/pre-shell-benign.json +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/fixtures/cursor/pre-shell-block.json +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_aibom.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_audit_chain.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_calibration.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_claude_code.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_corpus.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_cursor_gemini.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_dynamodb.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_egress.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_flow.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_gateway.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_homebrew_formula.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_interlock_test.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_judge.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_langchain.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_mcp_http.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_mcp_policy.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_mcp_proxy.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_mcp_server.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_memory.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_more_adapters.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_normalize_kiro.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_ogr.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_onboard.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_openai_agents.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_opencode.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_otel.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_packs.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_pipeline.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_policy_lint.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_scan.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_selftest.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_shim_sh.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_strands.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_tiers.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_transcript.py +0 -0
- {interlockd-0.1.0 → interlockd-0.1.2}/tests/test_unix_socket.py +0 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: Support for an agent or framework
|
|
2
|
+
description: Ask for (or offer to build) an adapter for a coding agent, agent framework, or MCP client
|
|
3
|
+
labels: [adapter]
|
|
4
|
+
body:
|
|
5
|
+
- type: input
|
|
6
|
+
id: agent
|
|
7
|
+
attributes: { label: Agent or framework, placeholder: "name, version, link" }
|
|
8
|
+
validations: { required: true }
|
|
9
|
+
- type: textarea
|
|
10
|
+
id: hooks
|
|
11
|
+
attributes:
|
|
12
|
+
label: Its extension point
|
|
13
|
+
description: Hooks, plugins, callbacks, or MCP only? Links to the docs help. Adapters are verified against payloads captured from the real tool, see docs/ADAPTERS.md.
|
|
14
|
+
- type: checkboxes
|
|
15
|
+
id: help
|
|
16
|
+
attributes:
|
|
17
|
+
label: Can you help?
|
|
18
|
+
options:
|
|
19
|
+
- label: I can capture real hook payloads from this tool
|
|
20
|
+
- label: I'd like to build the adapter
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: Bug
|
|
2
|
+
description: Something doesn't work as documented
|
|
3
|
+
labels: [bug]
|
|
4
|
+
body:
|
|
5
|
+
- type: markdown
|
|
6
|
+
attributes:
|
|
7
|
+
value: |
|
|
8
|
+
Found a way past a rule? Please use **private vulnerability reporting** instead (see the link on the previous page).
|
|
9
|
+
Audit records contain tool arguments: remove anything secret before pasting.
|
|
10
|
+
- type: textarea
|
|
11
|
+
id: what
|
|
12
|
+
attributes: { label: "What happened, and what did you expect?" }
|
|
13
|
+
validations: { required: true }
|
|
14
|
+
- type: textarea
|
|
15
|
+
id: repro
|
|
16
|
+
attributes: { label: "Steps to reproduce" }
|
|
17
|
+
validations: { required: true }
|
|
18
|
+
- type: textarea
|
|
19
|
+
id: doctor
|
|
20
|
+
attributes:
|
|
21
|
+
label: Output of `interlock --version` and `interlock doctor`
|
|
22
|
+
render: text
|
|
23
|
+
validations: { required: true }
|
|
24
|
+
- type: input
|
|
25
|
+
id: agent
|
|
26
|
+
attributes: { label: Agent and version, placeholder: "Claude Code 2.x, Codex 0.x, Cursor CLI ..." }
|
|
27
|
+
- type: input
|
|
28
|
+
id: os
|
|
29
|
+
attributes: { label: OS, placeholder: "macOS 15 arm64, Ubuntu 24.04 ..." }
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
blank_issues_enabled: false
|
|
2
|
+
contact_links:
|
|
3
|
+
- name: Report a vulnerability or a bypass (private)
|
|
4
|
+
url: https://github.com/interlockd/interlock/security/advisories/new
|
|
5
|
+
about: A way past a default rule, an injected result that reaches the agent, or tampering doctor misses. Please don't open a public issue.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: Wrong decision (false positive or false negative)
|
|
2
|
+
description: Interlock blocked or asked about something safe, or allowed something it should have stopped
|
|
3
|
+
labels: [decision]
|
|
4
|
+
body:
|
|
5
|
+
- type: markdown
|
|
6
|
+
attributes:
|
|
7
|
+
value: |
|
|
8
|
+
If an attack got through a **default** rule, please report it privately instead (Security → Report a vulnerability).
|
|
9
|
+
`interlock explain` shows the rule ids and reason for the latest stop. Remove secrets before pasting.
|
|
10
|
+
- type: dropdown
|
|
11
|
+
id: kind
|
|
12
|
+
attributes:
|
|
13
|
+
label: Kind
|
|
14
|
+
options: ["False positive: a safe action was blocked or asked", "False negative: a harmful action was allowed"]
|
|
15
|
+
validations: { required: true }
|
|
16
|
+
- type: textarea
|
|
17
|
+
id: action
|
|
18
|
+
attributes: { label: "The tool call (command or tool and arguments)", render: text }
|
|
19
|
+
validations: { required: true }
|
|
20
|
+
- type: textarea
|
|
21
|
+
id: explain
|
|
22
|
+
attributes: { label: "Output of `interlock explain` (for a stop)", render: text }
|
|
23
|
+
- type: input
|
|
24
|
+
id: expected
|
|
25
|
+
attributes: { label: "What should the decision be? (allow / ask / block)" }
|
|
26
|
+
validations: { required: true }
|
|
27
|
+
- type: textarea
|
|
28
|
+
id: why
|
|
29
|
+
attributes: { label: "Why? Context that makes it safe, or harmful" }
|
|
30
|
+
- type: dropdown
|
|
31
|
+
id: policy
|
|
32
|
+
attributes:
|
|
33
|
+
label: Policy
|
|
34
|
+
options: ["Default policy", "Custom policy (paste the relevant part below)"]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
## What and why
|
|
2
|
+
|
|
3
|
+
## How it was verified
|
|
4
|
+
|
|
5
|
+
- [ ] Tests (`pytest -q`, `interlock test`)
|
|
6
|
+
- [ ] Live against the real agent (which one, which version)
|
|
7
|
+
|
|
8
|
+
## For a rule change
|
|
9
|
+
|
|
10
|
+
- [ ] Positive **and** hard-negative cases in `interlockd/data/packs/<pack>.jsonl`
|
|
@@ -15,8 +15,8 @@ jobs:
|
|
|
15
15
|
matrix:
|
|
16
16
|
os: [ubuntu-latest, macos-latest]
|
|
17
17
|
steps:
|
|
18
|
-
- uses: actions/checkout@
|
|
19
|
-
- uses: astral-sh/setup-uv@
|
|
18
|
+
- uses: actions/checkout@v7
|
|
19
|
+
- uses: astral-sh/setup-uv@v7
|
|
20
20
|
- name: Install
|
|
21
21
|
run: |
|
|
22
22
|
uv venv --python 3.12
|
|
@@ -29,10 +29,22 @@ jobs:
|
|
|
29
29
|
run: .venv/bin/interlock test
|
|
30
30
|
- name: Evaluation corpus gate
|
|
31
31
|
run: .venv/bin/interlock test --corpus corpus/archetypes
|
|
32
|
-
- name:
|
|
32
|
+
- name: Linux sandbox (bubblewrap)
|
|
33
|
+
if: runner.os == 'Linux'
|
|
33
34
|
run: |
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
sudo apt-get install -y -qq bubblewrap
|
|
36
|
+
# Ubuntu 24.04 restricts unprivileged user namespaces through AppArmor; bubblewrap needs them
|
|
37
|
+
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 || true
|
|
38
|
+
.venv/bin/pytest -q tests/test_sandbox.py -rs
|
|
39
|
+
- name: Framework integrations against a real daemon
|
|
40
|
+
run: |
|
|
41
|
+
uv pip install --python .venv/bin/python strands-agents langchain-core openai-agents google-adk claude-agent-sdk semantic-kernel
|
|
42
|
+
.venv/bin/pytest -q tests/test_strands.py tests/test_langchain.py tests/test_openai_agents.py tests/test_google_adk.py tests/test_claude_agent_sdk.py tests/test_semantic_kernel.py
|
|
43
|
+
- name: CrewAI integration (own environment)
|
|
44
|
+
run: |
|
|
45
|
+
uv venv /tmp/crewai --python 3.12
|
|
46
|
+
uv pip install --python /tmp/crewai/bin/python -e ".[dev]" crewai
|
|
47
|
+
/tmp/crewai/bin/pytest -q tests/test_crewai.py
|
|
36
48
|
- name: Wheel smoke test
|
|
37
49
|
run: |
|
|
38
50
|
uv build --wheel -o dist
|
|
@@ -43,7 +55,7 @@ jobs:
|
|
|
43
55
|
action:
|
|
44
56
|
runs-on: ubuntu-latest
|
|
45
57
|
steps:
|
|
46
|
-
- uses: actions/checkout@
|
|
58
|
+
- uses: actions/checkout@v7
|
|
47
59
|
- name: Plant a risky MCP config to prove the scan sees it
|
|
48
60
|
run: |
|
|
49
61
|
mkdir -p .vscode
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
# Publish to PyPI when a version tag is pushed (git tag v0.1.0 && git push origin v0.1.0).
|
|
4
|
+
# Uses PyPI trusted publishing: no API token is stored. One-time setup on pypi.org:
|
|
5
|
+
# project "interlockd" → Publishing → add GitHub publisher: owner interlockd, repo interlock,
|
|
6
|
+
# workflow release.yml, environment pypi.
|
|
7
|
+
|
|
8
|
+
on:
|
|
9
|
+
push:
|
|
10
|
+
tags: ["v*"]
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
build:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
permissions:
|
|
19
|
+
contents: read
|
|
20
|
+
id-token: write # signs the build provenance (Sigstore)
|
|
21
|
+
attestations: write
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v7
|
|
24
|
+
- uses: astral-sh/setup-uv@v7
|
|
25
|
+
- name: Tag matches package version
|
|
26
|
+
run: |
|
|
27
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
28
|
+
pkg=$(python3 -c "import tomllib;print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
|
|
29
|
+
mod=$(python3 -c "import re;print(re.search(r'__version__ = \"(.+)\"', open('interlockd/__init__.py').read()).group(1))")
|
|
30
|
+
echo "tag=$tag pyproject=$pkg module=$mod"
|
|
31
|
+
[ "$tag" = "$pkg" ] && [ "$tag" = "$mod" ]
|
|
32
|
+
- name: Test
|
|
33
|
+
run: |
|
|
34
|
+
uv venv --python 3.12 && uv pip install -e ".[dev]" --python .venv/bin/python
|
|
35
|
+
.venv/bin/ruff check . && .venv/bin/pytest -q && .venv/bin/interlock test
|
|
36
|
+
- name: Build
|
|
37
|
+
run: uv build
|
|
38
|
+
- name: Smoke-test the wheel
|
|
39
|
+
run: |
|
|
40
|
+
uv venv /tmp/w --python 3.12 && uv pip install --python /tmp/w/bin/python dist/*.whl
|
|
41
|
+
cd /tmp && /tmp/w/bin/interlock --help >/dev/null && /tmp/w/bin/interlock test
|
|
42
|
+
- name: Build provenance (verify with `gh attestation verify <file> --repo interlockd/interlock`)
|
|
43
|
+
uses: actions/attest-build-provenance@v2
|
|
44
|
+
with: { subject-path: "dist/*" }
|
|
45
|
+
- uses: actions/upload-artifact@v7
|
|
46
|
+
with: { name: dist, path: dist/ }
|
|
47
|
+
|
|
48
|
+
publish:
|
|
49
|
+
needs: build
|
|
50
|
+
runs-on: ubuntu-latest
|
|
51
|
+
environment: pypi
|
|
52
|
+
permissions:
|
|
53
|
+
id-token: write # trusted publishing
|
|
54
|
+
contents: write # GitHub release
|
|
55
|
+
steps:
|
|
56
|
+
- uses: actions/download-artifact@v8
|
|
57
|
+
with: { name: dist, path: dist/ }
|
|
58
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
59
|
+
- name: GitHub release
|
|
60
|
+
env: { GH_TOKEN: "${{ github.token }}" }
|
|
61
|
+
run: |
|
|
62
|
+
# a release drafted by hand on GitHub (e.g. to publish the Action to the Marketplace) keeps its notes
|
|
63
|
+
if gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
|
64
|
+
gh release upload "$GITHUB_REF_NAME" dist/* --repo "$GITHUB_REPOSITORY" --clobber
|
|
65
|
+
else
|
|
66
|
+
gh release create "$GITHUB_REF_NAME" dist/* --repo "$GITHUB_REPOSITORY" --title "$GITHUB_REF_NAME" --generate-notes
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
homebrew:
|
|
70
|
+
# Regenerates Formula/interlock.rb from the release on PyPI. With a HOMEBREW_TAP_TOKEN secret (a fine-grained token
|
|
71
|
+
# with contents: write on interlockd/homebrew-tap) it commits the formula there; without one it is kept as an artifact.
|
|
72
|
+
needs: publish
|
|
73
|
+
runs-on: ubuntu-latest
|
|
74
|
+
env: { TAP_TOKEN: "${{ secrets.HOMEBREW_TAP_TOKEN }}" }
|
|
75
|
+
steps:
|
|
76
|
+
- uses: actions/checkout@v7
|
|
77
|
+
- name: Generate the formula (waits for PyPI to serve the new version)
|
|
78
|
+
run: |
|
|
79
|
+
v="${GITHUB_REF_NAME#v}"
|
|
80
|
+
for i in 1 2 3 4 5 6 7 8 9 10; do
|
|
81
|
+
python3 packaging/homebrew/make_formula.py "$v" > interlock.rb && break
|
|
82
|
+
sleep 30
|
|
83
|
+
done
|
|
84
|
+
ruby -c interlock.rb
|
|
85
|
+
- uses: actions/upload-artifact@v7
|
|
86
|
+
with: { name: homebrew-formula, path: interlock.rb }
|
|
87
|
+
- name: Commit to interlockd/homebrew-tap
|
|
88
|
+
if: env.TAP_TOKEN != ''
|
|
89
|
+
run: |
|
|
90
|
+
git clone "https://x-access-token:${TAP_TOKEN}@github.com/interlockd/homebrew-tap.git" tap
|
|
91
|
+
mkdir -p tap/Formula && cp interlock.rb tap/Formula/interlock.rb
|
|
92
|
+
cd tap
|
|
93
|
+
git config user.name "interlock release"; git config user.email "release@users.noreply.github.com"
|
|
94
|
+
git add Formula/interlock.rb
|
|
95
|
+
git diff --cached --quiet || { git commit -m "interlock ${GITHUB_REF_NAME#v}" && git push origin HEAD:main; } # works on an empty tap too
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.2 (2026-10-24)
|
|
4
|
+
|
|
5
|
+
- Managed policy: `<interlock home>/managed-policy.yaml` is applied last and can only tighten decisions (it can switch enforce on, never loosen anything). It is reloaded without a restart; a file that doesn't parse is ignored and reported by `interlock policy lint`. For device management and centrally managed machines.
|
|
6
|
+
- Plugins: installed packages that register under the `interlockd.plugins` entry-point group can add commands and run background work beside `interlock serve`. Plugins never sit in the decision path; a plugin that fails is skipped with a warning. `interlock plugins` lists them; `INTERLOCK_PLUGINS=0` turns them off.
|
|
7
|
+
- Platforms stated: macOS and Linux; on Windows, `interlock` explains how to use WSL2 instead of crashing on import. Homebrew: `brew install interlockd/tap/interlock`.
|
|
8
|
+
- `interlock judge install | status | remove`: run the optional judge model (Strands Decider 2B) locally in one step. The daemon starts it in the background instead of delaying startup.
|
|
9
|
+
- Contributing: pull requests are imported into the development repository with author credit (`CONTRIBUTING.md`); a code of conduct; `demo/demo.sh` (and a VHS tape) for a self-contained recording.
|
|
10
|
+
- `@interlockd/sdk` for JavaScript and TypeScript agents (`sdk/js`, no dependencies): `guardTools` for the Vercel AI SDK, Mastra and any `{ execute }` tool map, and `guardOptions` for the Claude Agent SDK for TypeScript. Both verified with the real packages.
|
|
11
|
+
- Injection detector, measured on public benchmarks (`docs/BENCHMARKS.md`, scripts in `benchmarks/`): catches AgentDojo's leaderboard template (0% -> 100%), InjecAgent's enhanced attacks (37% -> 97% on the harm set), typo'd "ignore your previous ..." and `###(system_message)` markers. False positives on real documentation fell from 2.2% to 0.25% ("bypass the X policy" and "silently skipped" no longer count).
|
|
12
|
+
- `shred <file>` is an irreversible delete (asked or recorded like `rm -rf`), no longer blocked as a disk wipe; `shred /dev/...` still is.
|
|
13
|
+
- Releases carry signed build provenance (`gh attestation verify`), and the release workflow regenerates the Homebrew formula and commits it to `interlockd/homebrew-tap` when a tap token is configured.
|
|
14
|
+
- `interlock capture`: record the real hook payloads an agent sends (with Interlock's replies), check that blocks held, and export redacted fixtures. `--scratch` sets up a verification project. Verified live with Claude Code.
|
|
15
|
+
- Cedar mandate: shell egress (`curl`, `wget`, ...) now binds to `net.egress` with its hosts, like fetch tools (it was reported unjudged). With `mandate.mode: enforce`, a mandate without a `net.egress` permit now refuses shell egress; `docs/mandate-example.cedar` shows a host allowlist. Enforce mode verified live through the hook shim.
|
|
16
|
+
- MCP OAuth checks in the HTTP gateway: a JWT issued for another service is never passed to an MCP server (audience binding), scopes in `mcp.oauth.deny_scopes` are refused, administrative scopes are flagged, and each server's authorization servers are pinned on first use so a change is refused until `interlock mcp-pins --accept`.
|
|
17
|
+
|
|
18
|
+
## 0.1.1 (2026-10-18)
|
|
19
|
+
|
|
20
|
+
- Google ADK and CrewAI integrations.
|
|
21
|
+
- Semantic Kernel integration (`guard_kernel`): a function-invocation filter, tested through a real Kernel.
|
|
22
|
+
- Claude Agent SDK integration (`guard_options`): in-process PreToolUse / PostToolUse hooks, verified live (a refused force-push, a withheld injected README).
|
|
23
|
+
- `interlock pause` / `resume` kill switch.
|
|
24
|
+
- Linux sandbox (bubblewrap) verified in CI.
|
|
25
|
+
- Repository policies: a committed `<repo>/.interlock/policy.yaml` applies to every call inside the repository, re-read on change. It can only tighten your policy unless you list the repository in `trust.trusted_workspaces`; `interlock policy lint --workspace <repo>` shows what was not applied.
|
|
26
|
+
- Hook watch: the daemon re-checks the hooks it installed every minute and records a removed hook or a modified shim (and the repair) in the audit chain; `interlock report` lists them. Shims left by an upgrade are refreshed instead of flagged.
|
|
27
|
+
- `interlock bench`: decision, probe, HTTP and hook-shim latency on your machine; see `docs/PERFORMANCE.md`.
|
|
28
|
+
- Faster audit writes: the hash chain's head is cached between appends instead of re-read; decision p50 4.2 ms -> 1.2 ms.
|
|
29
|
+
- `interlock doctor` reports when a newer release is on PyPI (`INTERLOCK_NO_UPDATE_CHECK=1` turns it off).
|
|
30
|
+
- Docs: the hooks apply `fail_mode.default` while the daemon is down (the per-category entries had been described as applying too).
|
|
31
|
+
|
|
32
|
+
## 0.1.0 (2026-10-17)
|
|
33
|
+
|
|
34
|
+
First public release.
|
|
35
|
+
|
|
36
|
+
- `interlock init` / `uninstall`: one-command setup for Claude Code, Codex, Kiro, Cursor, and GitHub Copilot CLI (verified live), plus Gemini CLI, Windsurf, Antigravity CLI, and OpenCode (built from their documented hooks and plugin API); background service (launchd, systemd user, or detached process); shadow mode by default.
|
|
37
|
+
- Pre-action gate: 48 deterministic rules in 6 packs (shell, git, egress, persistence, secrets, packages) with a shell decomposer.
|
|
38
|
+
- Post-action injection probe that runs locally (no cloud), withholding injected results in Claude Code.
|
|
39
|
+
- Custom agents: `interlockd.integrations.strands.InterlockHooks`, `interlockd.integrations.langchain.guard_tools`, and `interlockd.integrations.openai_agents.guard_agent_tools`.
|
|
40
|
+
- MCP gateway: `interlock gateway wrap | unwrap | status` routes stdio MCP servers through `interlock mcp-proxy` and remote (Streamable HTTP) servers through the daemon, which pins tool definitions (rug-pull detection), hides poisoned tools, gates tool calls with the same rules as the agent's own shell, and withholds injected results.
|
|
41
|
+
- `interlock mcp`: Interlock as an MCP server with read-only advisory tools (`check_action`, `explain_decision`, `recent_decisions`, `scan_mcp_servers`, `interlock_status`, `suggest_rule_override`); `interlock init --mcp` registers it.
|
|
42
|
+
- Codex: injected tool results are withheld (`decision: block`, measured live).
|
|
43
|
+
- Memory-poisoning protection: writes to agent instruction files (CLAUDE.md, AGENTS.md, ...) and MCP memory tools are recorded, and asked when the content reads as injected or the session took in untrusted content.
|
|
44
|
+
- `interlock pause` / `resume`: a kill switch that refuses every agent action (all or named agents, optionally timed), in any mode.
|
|
45
|
+
- `interlock egress-proxy`: decides every outbound host (metadata endpoints refused; `--strict` allowlist); `interlock sandbox --egress` makes it the agent's only way out.
|
|
46
|
+
- `interlock sandbox -- <agent>`: OS-level sandbox (macOS seatbelt; Linux bubblewrap) refusing persistence and guard-rail writes, with optional secret-read and network limits.
|
|
47
|
+
- Packaging: published on PyPI as `interlockd` (the command is still `interlock`) via a trusted-publishing release workflow, and a Homebrew formula generator for `interlockd/homebrew-tap`.
|
|
48
|
+
- `interlock replay`: re-decide recorded calls with your current policy and see what would change.
|
|
49
|
+
- `interlock policy lint`: unknown rule ids (a typo in an override silently does nothing), weakened critical rules, catch-all allows.
|
|
50
|
+
- `interlock selftest`: attack shapes through the live daemon in each agent's hook format; run by `init`.
|
|
51
|
+
- `interlock explain`: why a call was stopped, in plain language, with the override snippet.
|
|
52
|
+
- OpenTelemetry: one span per decision to any OTLP/HTTP collector (`otlp_endpoint` in config), and `interlock otel export` to backfill.
|
|
53
|
+
- `interlock aibom`: CycloneDX 1.6 AI bill of materials for the agent stack (agents, models, MCP servers with package URLs and pinning, remote endpoints).
|
|
54
|
+
- Hardening: the daemon also listens on a 0600 Unix socket that the hooks prefer; `interlock token --rotate`.
|
|
55
|
+
- Tamper-evident audit log: hash-chained records, `interlock audit verify` and `audit head`.
|
|
56
|
+
- `interlock scan`: static audit of MCP servers and agent settings across Claude Code, Claude Desktop, Codex, Cursor, VS Code, Kiro, Gemini CLI, and Windsurf.
|
|
57
|
+
- `interlock report`, `doctor`, `status`, `log`, `enforce` / `shadow`, `test`.
|
|
58
|
+
- Optional: Cedar mandate, open-weights judge, Amazon Bedrock Guardrails as an extra detector, DynamoDB audit store.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Code of conduct
|
|
2
|
+
|
|
3
|
+
Interlock follows the [Contributor Covenant, version 2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct/).
|
|
4
|
+
|
|
5
|
+
In short: be respectful and constructive, assume good faith, and keep discussion about the work. Harassment, personal
|
|
6
|
+
attacks, and discriminatory language are not acceptable in issues, pull requests, discussions, or any other project
|
|
7
|
+
space.
|
|
8
|
+
|
|
9
|
+
To report a problem, email **conduct@interlockd.dev**. Reports are handled confidentially. The maintainers may remove content and restrict participation, as described in the Covenant's
|
|
10
|
+
enforcement guidelines.
|
|
@@ -17,11 +17,17 @@ CI runs all four on every pull request.
|
|
|
17
17
|
## What to contribute
|
|
18
18
|
|
|
19
19
|
- **A rule.** Register it in `interlockd/det/packs.py`, implement the check, and add positive *and* negative cases to `interlockd/data/packs/<pack>.jsonl`. A rule without hard negatives will not be merged: false positives are what make people uninstall a security tool.
|
|
20
|
-
- **An adapter for a new agent.** Read [docs/ADAPTERS.md](docs/ADAPTERS.md) first. A normalizer (`interlockd/normalize/`), a shim (`interlockd/shims/`), detection in `interlockd/onboard.py`, and **payloads captured from the real tool** as fixtures in `tests/fixtures/<agent>/`, plus a `adapters/<agent>/PAYLOADS.md` describing what you measured. Documented-but-unmeasured contracts have been wrong before.
|
|
20
|
+
- **An adapter for a new agent.** Read [docs/ADAPTERS.md](docs/ADAPTERS.md) first. A normalizer (`interlockd/normalize/`), a shim (`interlockd/shims/`), detection in `interlockd/onboard.py`, and **payloads captured from the real tool** (`interlock capture`, see docs/ADAPTERS.md §4) as fixtures in `tests/fixtures/<agent>/`, plus a `adapters/<agent>/PAYLOADS.md` describing what you measured. Documented-but-unmeasured contracts have been wrong before.
|
|
21
21
|
- **Corpus cases.** Real attack shapes and real benign commands that a rule gets wrong.
|
|
22
22
|
|
|
23
23
|
## Pull requests
|
|
24
24
|
|
|
25
|
+
Open pull requests against `main` here as usual. This repository is published from the maintainers' development
|
|
26
|
+
repository, so a merged change reaches `main` through the next published commit, with you credited as author (and
|
|
27
|
+
`Co-authored-by` on the published commit); your PR is closed with a link to it rather than merged with the button.
|
|
28
|
+
|
|
25
29
|
- Keep changes focused; include tests.
|
|
26
30
|
- Describe what you verified and how (live against the tool, or tests only).
|
|
27
31
|
- By contributing you agree your contribution is licensed under Apache-2.0.
|
|
32
|
+
|
|
33
|
+
By taking part you agree to the [code of conduct](CODE_OF_CONDUCT.md).
|
interlockd-0.1.2/NOTICE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Interlock
|
|
2
|
+
Copyright 2026 the Interlock authors
|
|
3
|
+
|
|
4
|
+
This product is licensed under the Apache License, Version 2.0 (see LICENSE).
|
|
5
|
+
|
|
6
|
+
Benchmark scripts in benchmarks/ download third-party datasets at run time; those datasets are not part of this
|
|
7
|
+
repository and keep their own licenses: InjecAgent (MIT), AgentDojo (MIT), NL2Bash (GPL-3.0).
|
|
@@ -1,3 +1,47 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: interlockd
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Interlock daemon: a pre/post-action interlock for coding agents
|
|
5
|
+
License: Apache-2.0
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
License-File: NOTICE
|
|
8
|
+
Classifier: Environment :: Console
|
|
9
|
+
Classifier: Operating System :: MacOS
|
|
10
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Topic :: Security
|
|
13
|
+
Requires-Python: >=3.12
|
|
14
|
+
Requires-Dist: pydantic<3,>=2.9
|
|
15
|
+
Requires-Dist: pyyaml<7,>=6.0
|
|
16
|
+
Provides-Extra: aws
|
|
17
|
+
Requires-Dist: boto3<2,>=1.35; extra == 'aws'
|
|
18
|
+
Provides-Extra: claude-agent-sdk
|
|
19
|
+
Requires-Dist: claude-agent-sdk>=0.2; extra == 'claude-agent-sdk'
|
|
20
|
+
Provides-Extra: crewai
|
|
21
|
+
Requires-Dist: crewai>=1.10; extra == 'crewai'
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: boto3<2,>=1.35; extra == 'dev'
|
|
24
|
+
Requires-Dist: cedarpy<5,>=4.7; extra == 'dev'
|
|
25
|
+
Requires-Dist: moto[dynamodb]<6,>=5.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest<10,>=8.3; extra == 'dev'
|
|
27
|
+
Requires-Dist: ruff<1,>=0.6; extra == 'dev'
|
|
28
|
+
Provides-Extra: google-adk
|
|
29
|
+
Requires-Dist: google-adk>=2.0; extra == 'google-adk'
|
|
30
|
+
Provides-Extra: langchain
|
|
31
|
+
Requires-Dist: langchain-core>=1.0; extra == 'langchain'
|
|
32
|
+
Provides-Extra: mandate
|
|
33
|
+
Requires-Dist: cedarpy<5,>=4.7; extra == 'mandate'
|
|
34
|
+
Provides-Extra: openai-agents
|
|
35
|
+
Requires-Dist: openai-agents>=0.20; extra == 'openai-agents'
|
|
36
|
+
Provides-Extra: promptguard
|
|
37
|
+
Requires-Dist: torch>=2.3; extra == 'promptguard'
|
|
38
|
+
Requires-Dist: transformers<6,>=4.44; extra == 'promptguard'
|
|
39
|
+
Provides-Extra: semantic-kernel
|
|
40
|
+
Requires-Dist: semantic-kernel>=1.30; extra == 'semantic-kernel'
|
|
41
|
+
Provides-Extra: strands
|
|
42
|
+
Requires-Dist: strands-agents>=1.40; extra == 'strands'
|
|
43
|
+
Description-Content-Type: text/markdown
|
|
44
|
+
|
|
1
45
|
# Interlock
|
|
2
46
|
|
|
3
47
|
**The safety layer between your AI agent and your codebase.**
|
|
@@ -8,10 +52,15 @@ It runs locally. No account, no cloud, no model download required.
|
|
|
8
52
|
|
|
9
53
|
|
|
10
54
|
```sh
|
|
11
|
-
pipx install interlockd # or: uv tool install interlockd
|
|
55
|
+
pipx install interlockd # or: uv tool install interlockd, or: brew install interlockd/tap/interlock
|
|
12
56
|
interlock init # finds your agents, installs hooks, starts the daemon (shadow mode)
|
|
13
57
|
```
|
|
14
58
|
|
|
59
|
+
Runs on macOS and Linux (Python 3.12+). On Windows, use it inside WSL2 and run your agents from the WSL terminal;
|
|
60
|
+
native Windows support is planned.
|
|
61
|
+
|
|
62
|
+
New here? [docs/GETTING-STARTED.md](docs/GETTING-STARTED.md) walks through shadow mode, enforcing, MCP servers, your own agents and CI in about ten minutes.
|
|
63
|
+
|
|
15
64
|
```
|
|
16
65
|
What the gate decides (enforce mode, nothing executed):
|
|
17
66
|
block git push --force origin main force-push to a protected branch
|
|
@@ -31,14 +80,15 @@ What the gate decides (enforce mode, nothing executed):
|
|
|
31
80
|
| Cursor (CLI) | ✅ verified live: blocks before execution |
|
|
32
81
|
| GitHub Copilot CLI | ✅ verified live: blocks before execution, withholds injected tool results |
|
|
33
82
|
| Gemini CLI, Windsurf, Antigravity CLI, OpenCode | built from their documented hooks/plugins; live verification in progress |
|
|
34
|
-
| Your own agents (Strands, LangChain / LangGraph, OpenAI Agents SDK) | ✅ in-process: `
|
|
35
|
-
|
|
|
36
|
-
|
|
|
83
|
+
| Your own agents (Claude Agent SDK, Strands, LangChain / LangGraph, OpenAI Agents SDK, Google ADK, CrewAI, Semantic Kernel) | ✅ in-process: `guard_options(options)`, `InterlockHooks()`, `guard_tools(tools)`, `guard_agent_tools(agent)`, `guard_agent(agent)`, `crewai.install()`, `guard_kernel(kernel)` |
|
|
84
|
+
| JavaScript / TypeScript agents (Vercel AI SDK, Claude Agent SDK for TS, Mastra) | ✅ `@interlockd/sdk`: `guardTools(tools)`, `guardOptions(options)` ([sdk/js](sdk/js)) |
|
|
85
|
+
| Any MCP client | ✅ the MCP gateway (`interlock gateway wrap`) gates every stdio and remote (HTTP) MCP server, hooks or not |
|
|
86
|
+
| Cline, Copilot in VS Code, and more | hook adapters planned; meanwhile the MCP gateway and `interlock sandbox` cover them |
|
|
37
87
|
|
|
38
88
|
## How it works
|
|
39
89
|
|
|
40
90
|
1. **Hooks.** `interlock init` adds a small hook to each agent you have installed. The hook forwards each proposed tool call (and each tool result) to a local daemon.
|
|
41
|
-
2. **Deterministic rules first.** 48 rules in 6 packs (shell, git, egress, persistence, secrets, packages) decide in about
|
|
91
|
+
2. **Deterministic rules first.** 48 rules in 6 packs (shell, git, egress, persistence, secrets, packages) decide in about a millisecond ([measured](docs/PERFORMANCE.md)). The shell decomposer sees through chains, pipes, subshells, heredocs, interpreter one-liners and base64.
|
|
42
92
|
3. **Injection probe on results.** A local detector flags tool output that tries to instruct the agent (including text hidden in HTML comments or invisible Unicode) and withholds it where the agent supports that.
|
|
43
93
|
4. **Optional judge.** An open-weights decision model can answer "did the user actually ask for this?" for ambiguous actions. Off by default.
|
|
44
94
|
5. **Everything recorded.** Every decision lands in a local append-only log with its reason.
|
|
@@ -68,7 +118,7 @@ Policy lives in `~/.interlock/policy.yaml` (user) and `<repo>/.interlock/policy.
|
|
|
68
118
|
|
|
69
119
|
## Honest limits
|
|
70
120
|
|
|
71
|
-
The full threat model is in [docs/THREAT-MODEL.md](docs/THREAT-MODEL.md).
|
|
121
|
+
The full threat model is in [docs/THREAT-MODEL.md](docs/THREAT-MODEL.md). Latency on your own machine: `interlock bench` ([docs/PERFORMANCE.md](docs/PERFORMANCE.md)). Detection and false-positive rates on public datasets (InjecAgent, AgentDojo, NL2Bash), including what it misses: [docs/BENCHMARKS.md](docs/BENCHMARKS.md).
|
|
72
122
|
|
|
73
123
|
|
|
74
124
|
- Interlock sees what the agent's hooks show it. A process the agent starts outside the hook path, or a second unguarded agent, is not gated. Network and sandbox enforcement are on the roadmap.
|
|
@@ -78,8 +128,8 @@ The full threat model is in [docs/THREAT-MODEL.md](docs/THREAT-MODEL.md).
|
|
|
78
128
|
## In CI
|
|
79
129
|
|
|
80
130
|
```yaml
|
|
81
|
-
- uses: actions/checkout@
|
|
82
|
-
- uses: interlockd/interlock@v0.1.
|
|
131
|
+
- uses: actions/checkout@v7
|
|
132
|
+
- uses: interlockd/interlock@v0.1.2 # scan MCP/agent configs, test .interlock/policy.yaml
|
|
83
133
|
with: { fail-on: high }
|
|
84
134
|
- uses: github/codeql-action/upload-sarif@v3
|
|
85
135
|
if: always()
|
|
@@ -92,4 +142,9 @@ Adapters for new agents, rules, and corpus cases are all welcome. See [CONTRIBUT
|
|
|
92
142
|
|
|
93
143
|
## License
|
|
94
144
|
|
|
95
|
-
Apache-2.0.
|
|
145
|
+
Everything in this repository is Apache-2.0, and stays that way: we will not relicense it. That covers enforcement,
|
|
146
|
+
every rule and adapter, the injection detector, the MCP gateway, policy testing, and the local audit log, and it runs
|
|
147
|
+
fully offline with no license check.
|
|
148
|
+
|
|
149
|
+
A hosted service for teams (fleet-wide policy, dashboards, retention) may come later as a separate product. It will
|
|
150
|
+
not make anything here less safe or move it behind a paywall. See `NOTICE`.
|
|
@@ -1,33 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.5
|
|
2
|
-
Name: interlockd
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: Interlock daemon: a pre/post-action interlock for coding agents
|
|
5
|
-
License: Apache-2.0
|
|
6
|
-
License-File: LICENSE
|
|
7
|
-
Requires-Python: >=3.12
|
|
8
|
-
Requires-Dist: pydantic<3,>=2.9
|
|
9
|
-
Requires-Dist: pyyaml<7,>=6.0
|
|
10
|
-
Provides-Extra: aws
|
|
11
|
-
Requires-Dist: boto3<2,>=1.35; extra == 'aws'
|
|
12
|
-
Provides-Extra: dev
|
|
13
|
-
Requires-Dist: boto3<2,>=1.35; extra == 'dev'
|
|
14
|
-
Requires-Dist: cedarpy<5,>=4.7; extra == 'dev'
|
|
15
|
-
Requires-Dist: moto[dynamodb]<6,>=5.0; extra == 'dev'
|
|
16
|
-
Requires-Dist: pytest<9,>=8.3; extra == 'dev'
|
|
17
|
-
Requires-Dist: ruff<1,>=0.6; extra == 'dev'
|
|
18
|
-
Provides-Extra: langchain
|
|
19
|
-
Requires-Dist: langchain-core>=1.0; extra == 'langchain'
|
|
20
|
-
Provides-Extra: mandate
|
|
21
|
-
Requires-Dist: cedarpy<5,>=4.7; extra == 'mandate'
|
|
22
|
-
Provides-Extra: openai-agents
|
|
23
|
-
Requires-Dist: openai-agents>=0.20; extra == 'openai-agents'
|
|
24
|
-
Provides-Extra: promptguard
|
|
25
|
-
Requires-Dist: torch>=2.3; extra == 'promptguard'
|
|
26
|
-
Requires-Dist: transformers<5,>=4.44; extra == 'promptguard'
|
|
27
|
-
Provides-Extra: strands
|
|
28
|
-
Requires-Dist: strands-agents>=1.40; extra == 'strands'
|
|
29
|
-
Description-Content-Type: text/markdown
|
|
30
|
-
|
|
31
1
|
# Interlock
|
|
32
2
|
|
|
33
3
|
**The safety layer between your AI agent and your codebase.**
|
|
@@ -38,10 +8,15 @@ It runs locally. No account, no cloud, no model download required.
|
|
|
38
8
|
|
|
39
9
|
|
|
40
10
|
```sh
|
|
41
|
-
pipx install interlockd # or: uv tool install interlockd
|
|
11
|
+
pipx install interlockd # or: uv tool install interlockd, or: brew install interlockd/tap/interlock
|
|
42
12
|
interlock init # finds your agents, installs hooks, starts the daemon (shadow mode)
|
|
43
13
|
```
|
|
44
14
|
|
|
15
|
+
Runs on macOS and Linux (Python 3.12+). On Windows, use it inside WSL2 and run your agents from the WSL terminal;
|
|
16
|
+
native Windows support is planned.
|
|
17
|
+
|
|
18
|
+
New here? [docs/GETTING-STARTED.md](docs/GETTING-STARTED.md) walks through shadow mode, enforcing, MCP servers, your own agents and CI in about ten minutes.
|
|
19
|
+
|
|
45
20
|
```
|
|
46
21
|
What the gate decides (enforce mode, nothing executed):
|
|
47
22
|
block git push --force origin main force-push to a protected branch
|
|
@@ -61,14 +36,15 @@ What the gate decides (enforce mode, nothing executed):
|
|
|
61
36
|
| Cursor (CLI) | ✅ verified live: blocks before execution |
|
|
62
37
|
| GitHub Copilot CLI | ✅ verified live: blocks before execution, withholds injected tool results |
|
|
63
38
|
| Gemini CLI, Windsurf, Antigravity CLI, OpenCode | built from their documented hooks/plugins; live verification in progress |
|
|
64
|
-
| Your own agents (Strands, LangChain / LangGraph, OpenAI Agents SDK) | ✅ in-process: `
|
|
65
|
-
|
|
|
66
|
-
|
|
|
39
|
+
| Your own agents (Claude Agent SDK, Strands, LangChain / LangGraph, OpenAI Agents SDK, Google ADK, CrewAI, Semantic Kernel) | ✅ in-process: `guard_options(options)`, `InterlockHooks()`, `guard_tools(tools)`, `guard_agent_tools(agent)`, `guard_agent(agent)`, `crewai.install()`, `guard_kernel(kernel)` |
|
|
40
|
+
| JavaScript / TypeScript agents (Vercel AI SDK, Claude Agent SDK for TS, Mastra) | ✅ `@interlockd/sdk`: `guardTools(tools)`, `guardOptions(options)` ([sdk/js](sdk/js)) |
|
|
41
|
+
| Any MCP client | ✅ the MCP gateway (`interlock gateway wrap`) gates every stdio and remote (HTTP) MCP server, hooks or not |
|
|
42
|
+
| Cline, Copilot in VS Code, and more | hook adapters planned; meanwhile the MCP gateway and `interlock sandbox` cover them |
|
|
67
43
|
|
|
68
44
|
## How it works
|
|
69
45
|
|
|
70
46
|
1. **Hooks.** `interlock init` adds a small hook to each agent you have installed. The hook forwards each proposed tool call (and each tool result) to a local daemon.
|
|
71
|
-
2. **Deterministic rules first.** 48 rules in 6 packs (shell, git, egress, persistence, secrets, packages) decide in about
|
|
47
|
+
2. **Deterministic rules first.** 48 rules in 6 packs (shell, git, egress, persistence, secrets, packages) decide in about a millisecond ([measured](docs/PERFORMANCE.md)). The shell decomposer sees through chains, pipes, subshells, heredocs, interpreter one-liners and base64.
|
|
72
48
|
3. **Injection probe on results.** A local detector flags tool output that tries to instruct the agent (including text hidden in HTML comments or invisible Unicode) and withholds it where the agent supports that.
|
|
73
49
|
4. **Optional judge.** An open-weights decision model can answer "did the user actually ask for this?" for ambiguous actions. Off by default.
|
|
74
50
|
5. **Everything recorded.** Every decision lands in a local append-only log with its reason.
|
|
@@ -98,7 +74,7 @@ Policy lives in `~/.interlock/policy.yaml` (user) and `<repo>/.interlock/policy.
|
|
|
98
74
|
|
|
99
75
|
## Honest limits
|
|
100
76
|
|
|
101
|
-
The full threat model is in [docs/THREAT-MODEL.md](docs/THREAT-MODEL.md).
|
|
77
|
+
The full threat model is in [docs/THREAT-MODEL.md](docs/THREAT-MODEL.md). Latency on your own machine: `interlock bench` ([docs/PERFORMANCE.md](docs/PERFORMANCE.md)). Detection and false-positive rates on public datasets (InjecAgent, AgentDojo, NL2Bash), including what it misses: [docs/BENCHMARKS.md](docs/BENCHMARKS.md).
|
|
102
78
|
|
|
103
79
|
|
|
104
80
|
- Interlock sees what the agent's hooks show it. A process the agent starts outside the hook path, or a second unguarded agent, is not gated. Network and sandbox enforcement are on the roadmap.
|
|
@@ -108,8 +84,8 @@ The full threat model is in [docs/THREAT-MODEL.md](docs/THREAT-MODEL.md).
|
|
|
108
84
|
## In CI
|
|
109
85
|
|
|
110
86
|
```yaml
|
|
111
|
-
- uses: actions/checkout@
|
|
112
|
-
- uses: interlockd/interlock@v0.1.
|
|
87
|
+
- uses: actions/checkout@v7
|
|
88
|
+
- uses: interlockd/interlock@v0.1.2 # scan MCP/agent configs, test .interlock/policy.yaml
|
|
113
89
|
with: { fail-on: high }
|
|
114
90
|
- uses: github/codeql-action/upload-sarif@v3
|
|
115
91
|
if: always()
|
|
@@ -122,4 +98,9 @@ Adapters for new agents, rules, and corpus cases are all welcome. See [CONTRIBUT
|
|
|
122
98
|
|
|
123
99
|
## License
|
|
124
100
|
|
|
125
|
-
Apache-2.0.
|
|
101
|
+
Everything in this repository is Apache-2.0, and stays that way: we will not relicense it. That covers enforcement,
|
|
102
|
+
every rule and adapter, the injection detector, the MCP gateway, policy testing, and the local audit log, and it runs
|
|
103
|
+
fully offline with no license check.
|
|
104
|
+
|
|
105
|
+
A hosted service for teams (fleet-wide policy, dashboards, retention) may come later as a separate product. It will
|
|
106
|
+
not make anything here less safe or move it behind a paywall. See `NOTICE`.
|
|
@@ -21,3 +21,14 @@ The threat model (assets, adversaries, where Interlock sees, and its known limit
|
|
|
21
21
|
|
|
22
22
|
- Actions that never pass through an agent's hooks (documented limit).
|
|
23
23
|
- Findings that require an already-compromised user account.
|
|
24
|
+
|
|
25
|
+
## Verifying a release
|
|
26
|
+
|
|
27
|
+
Releases are built by GitHub Actions from the tagged commit and published to PyPI with trusted publishing (no stored
|
|
28
|
+
token). From 0.1.2, every wheel and sdist also carries a signed build provenance attestation (Sigstore):
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
pip download interlockd==<version> --no-deps -d /tmp/il && gh attestation verify /tmp/il/* --repo interlockd/interlock
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
PyPI shows the same provenance on each file's page ("Verified details").
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
name: Interlock
|
|
2
|
-
description:
|
|
1
|
+
name: Interlock Agent Security
|
|
2
|
+
description: Scan MCP servers, agent configs and AGENTS.md / CLAUDE.md for risks, and test your agent security policy in CI.
|
|
3
3
|
author: interlockd
|
|
4
4
|
branding: { icon: shield, color: blue }
|
|
5
5
|
|
|
@@ -31,7 +31,7 @@ outputs:
|
|
|
31
31
|
runs:
|
|
32
32
|
using: composite
|
|
33
33
|
steps:
|
|
34
|
-
- uses: actions/setup-python@
|
|
34
|
+
- uses: actions/setup-python@v7
|
|
35
35
|
with: { python-version: "3.12" }
|
|
36
36
|
- name: Install Interlock
|
|
37
37
|
shell: bash
|
|
@@ -45,7 +45,8 @@ runs:
|
|
|
45
45
|
run: |
|
|
46
46
|
# an empty HOME keeps the scan to this repository's own configs
|
|
47
47
|
export HOME="$(mktemp -d)"
|
|
48
|
-
|
|
48
|
+
args=(scan --project "$GITHUB_WORKSPACE" --fail-on "${{ inputs.fail-on }}" --sarif "$GITHUB_WORKSPACE/${{ inputs.sarif }}")
|
|
49
|
+
interlock "${args[@]}"
|
|
49
50
|
- name: Test the policy
|
|
50
51
|
if: inputs.test-policy == 'true'
|
|
51
52
|
shell: bash
|