@aporthq/aport-agent-guardrails 1.0.8
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.
- package/LICENSE +217 -0
- package/README.md +481 -0
- package/bin/agent-guardrails +133 -0
- package/bin/aport-create-passport.sh +444 -0
- package/bin/aport-cursor-hook.sh +90 -0
- package/bin/aport-guardrail-api.sh +108 -0
- package/bin/aport-guardrail-bash.sh +394 -0
- package/bin/aport-guardrail-v2.sh +5 -0
- package/bin/aport-guardrail.sh +5 -0
- package/bin/aport-resolve-paths.sh +71 -0
- package/bin/aport-status.sh +276 -0
- package/bin/frameworks/crewai.sh +49 -0
- package/bin/frameworks/cursor.sh +95 -0
- package/bin/frameworks/langchain.sh +48 -0
- package/bin/frameworks/n8n.sh +36 -0
- package/bin/frameworks/openclaw.sh +19 -0
- package/bin/lib/allowlist.sh +18 -0
- package/bin/lib/common.sh +28 -0
- package/bin/lib/config.sh +46 -0
- package/bin/lib/constants.sh +232 -0
- package/bin/lib/detect.sh +65 -0
- package/bin/lib/error.sh +269 -0
- package/bin/lib/passport.sh +19 -0
- package/bin/lib/templates/.gitkeep +1 -0
- package/bin/lib/templates/config.yaml +6 -0
- package/bin/lib/validation.sh +206 -0
- package/bin/openclaw +660 -0
- package/docs/ADDING_A_FRAMEWORK.md +87 -0
- package/docs/AGENTS.md.example +40 -0
- package/docs/CODE_REVIEW.md +192 -0
- package/docs/DEPLOYMENT_READINESS.md +81 -0
- package/docs/FAQ_SECURITY_SCANNERS.md +373 -0
- package/docs/FRAMEWORK_ROADMAP.md +41 -0
- package/docs/HOSTED_PASSPORT_SETUP.md +362 -0
- package/docs/IMPLEMENTING_YOUR_OWN_EVALUATOR.md +433 -0
- package/docs/OPENCLAW_COMPATIBILITY.md +73 -0
- package/docs/OPENCLAW_LOCAL_INTEGRATION.md +596 -0
- package/docs/OPENCLAW_TOOLS_AND_POLICIES.md +54 -0
- package/docs/QUICKSTART.md +470 -0
- package/docs/QUICKSTART_OPENCLAW_PLUGIN.md +470 -0
- package/docs/README.md +28 -0
- package/docs/RELEASE.md +87 -0
- package/docs/REPO_LAYOUT.md +47 -0
- package/docs/SKILLS_ECOSYSTEM_ANALYSIS_FEB17.md +1260 -0
- package/docs/TOOL_POLICY_MAPPING.md +46 -0
- package/docs/UPGRADE.md +46 -0
- package/docs/VERIFICATION_METHODS.md +97 -0
- package/docs/assets/README.md +8 -0
- package/docs/assets/porter.svg +54 -0
- package/docs/development/ERROR_CODES.md +616 -0
- package/docs/frameworks/GITHUB_ISSUE_PROPOSALS.md +1105 -0
- package/docs/frameworks/crewai.md +114 -0
- package/docs/frameworks/cursor.md +159 -0
- package/docs/frameworks/langchain.md +72 -0
- package/docs/frameworks/n8n.md +40 -0
- package/docs/frameworks/openclaw.md +40 -0
- package/docs/launch/ADD_APORT_AWESOME_LISTS_INSTRUCTIONS.md +146 -0
- package/docs/launch/ANNOUNCEMENT_GUIDE.md +266 -0
- package/docs/launch/AWESOME_REPOS.md +53 -0
- package/docs/launch/CURSOR_VSCODE_HOOKS_RESEARCH.md +77 -0
- package/docs/launch/DEMO_TERMINAL_OUTPUT.txt +48 -0
- package/docs/launch/DRY_AND_PLAN_CHECKLIST.md +47 -0
- package/docs/launch/EVIDENCE_README.md +61 -0
- package/docs/launch/EVIDENCE_TERMINAL_CAPTURE.txt +10 -0
- package/docs/launch/FRAMEWORK_SUPPORT_PLAN.md +1640 -0
- package/docs/launch/LAUNCH_READINESS_CHECKLIST.md +237 -0
- package/docs/launch/LAUNCH_STRATEGY_SUMMARY.md +464 -0
- package/docs/launch/OPENCLAW_FEEDBACK_AND_FIXES.md +85 -0
- package/docs/launch/POST_1_VALENTINE_IMPROVED.md +233 -0
- package/docs/launch/POST_2_GUARDRAIL_IMPROVED.md +369 -0
- package/docs/launch/PRE_LAUNCH_FIXES.md +766 -0
- package/docs/launch/QUICK_LAUNCH_CHECKLIST.md +400 -0
- package/docs/launch/READINESS_SUMMARY.md +262 -0
- package/docs/launch/README.md +68 -0
- package/docs/launch/USER_STORIES.md +327 -0
- package/docs/launch/scripts/add-aport-awesome-pr.sh +69 -0
- package/docs/operations/MONITORING.md +588 -0
- package/docs/reviews/2026-02-18-staff-review.md +268 -0
- package/extensions/openclaw-aport/README.md +415 -0
- package/extensions/openclaw-aport/index.js +625 -0
- package/extensions/openclaw-aport/openclaw-aport.js +7 -0
- package/extensions/openclaw-aport/openclaw.plugin.json +46 -0
- package/extensions/openclaw-aport/package.json +36 -0
- package/extensions/openclaw-aport/test.js +307 -0
- package/external/aport-policies/README.md +363 -0
- package/external/aport-policies/agent.session.create.v1/README.md +345 -0
- package/external/aport-policies/agent.session.create.v1/policy.json +162 -0
- package/external/aport-policies/agent.tool.register.v1/README.md +361 -0
- package/external/aport-policies/agent.tool.register.v1/policy.json +172 -0
- package/external/aport-policies/code.release.publish.v1/README.md +51 -0
- package/external/aport-policies/code.release.publish.v1/policy.json +121 -0
- package/external/aport-policies/code.repository.merge.v1/README.md +287 -0
- package/external/aport-policies/code.repository.merge.v1/express.example.js +332 -0
- package/external/aport-policies/code.repository.merge.v1/fastapi.example.py +370 -0
- package/external/aport-policies/code.repository.merge.v1/policy.json +162 -0
- package/external/aport-policies/data.export.create.v1/README.md +226 -0
- package/external/aport-policies/data.export.create.v1/express.example.js +172 -0
- package/external/aport-policies/data.export.create.v1/fastapi.example.py +165 -0
- package/external/aport-policies/data.export.create.v1/policy.json +133 -0
- package/external/aport-policies/data.report.ingest.v1/README.md +134 -0
- package/external/aport-policies/data.report.ingest.v1/express.example.js +105 -0
- package/external/aport-policies/data.report.ingest.v1/minimal-example.js +68 -0
- package/external/aport-policies/data.report.ingest.v1/policy.json +174 -0
- package/external/aport-policies/finance.crypto.trade.v1/README.md +146 -0
- package/external/aport-policies/finance.crypto.trade.v1/express.example.js +109 -0
- package/external/aport-policies/finance.crypto.trade.v1/minimal-example.js +65 -0
- package/external/aport-policies/finance.crypto.trade.v1/policy.json +176 -0
- package/external/aport-policies/finance.payment.charge.v1/README.md +326 -0
- package/external/aport-policies/finance.payment.charge.v1/express.example.js +250 -0
- package/external/aport-policies/finance.payment.charge.v1/fastapi.example.py +227 -0
- package/external/aport-policies/finance.payment.charge.v1/minimal-example.js +64 -0
- package/external/aport-policies/finance.payment.charge.v1/policy.json +224 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/contexts.jsonl +12 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/expected.jsonl +12 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/passport.instance.json +42 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/passport.template.json +40 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/payments-charge-policy.test.js +817 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/test_payments_charge_policy.py +486 -0
- package/external/aport-policies/finance.payment.payout.v1/README.md +78 -0
- package/external/aport-policies/finance.payment.payout.v1/policy.json +181 -0
- package/external/aport-policies/finance.payment.refund.v1/README.md +275 -0
- package/external/aport-policies/finance.payment.refund.v1/express.example.js +167 -0
- package/external/aport-policies/finance.payment.refund.v1/fastapi.example.py +136 -0
- package/external/aport-policies/finance.payment.refund.v1/minimal-example.js +183 -0
- package/external/aport-policies/finance.payment.refund.v1/policy.json +216 -0
- package/external/aport-policies/finance.payment.refund.v1/tests/refunds-policy.test.js +924 -0
- package/external/aport-policies/finance.payment.refund.v1/tests/test_refunds_policy.py +778 -0
- package/external/aport-policies/finance.transaction.execute.v1/README.md +309 -0
- package/external/aport-policies/finance.transaction.execute.v1/express.example.js +261 -0
- package/external/aport-policies/finance.transaction.execute.v1/fastapi.example.py +231 -0
- package/external/aport-policies/finance.transaction.execute.v1/minimal-example.js +78 -0
- package/external/aport-policies/finance.transaction.execute.v1/policy.json +189 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/contexts.jsonl +12 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/expected.jsonl +12 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/passport.instance.json +42 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/passport.template.json +42 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/test_transactions_policy.py +214 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/transactions-policy.test.js +306 -0
- package/external/aport-policies/governance.data.access.v1/README.md +292 -0
- package/external/aport-policies/governance.data.access.v1/express.example.js +321 -0
- package/external/aport-policies/governance.data.access.v1/fastapi.example.py +279 -0
- package/external/aport-policies/governance.data.access.v1/minimal-example.js +65 -0
- package/external/aport-policies/governance.data.access.v1/policy.json +208 -0
- package/external/aport-policies/governance.data.access.v1/tests/contexts.jsonl +12 -0
- package/external/aport-policies/governance.data.access.v1/tests/data-access-policy.test.js +308 -0
- package/external/aport-policies/governance.data.access.v1/tests/expected.jsonl +12 -0
- package/external/aport-policies/governance.data.access.v1/tests/passport.instance.json +56 -0
- package/external/aport-policies/governance.data.access.v1/tests/passport.template.json +56 -0
- package/external/aport-policies/governance.data.access.v1/tests/test_data_access_policy.py +214 -0
- package/external/aport-policies/legal.contract.review.v1/README.md +109 -0
- package/external/aport-policies/legal.contract.review.v1/policy.json +378 -0
- package/external/aport-policies/legal.contract.review.v1/tests/legal-contract-review-policy.test.js +609 -0
- package/external/aport-policies/legal.contract.review.v1/tests/passport.template.json +49 -0
- package/external/aport-policies/mcp.tool.execute.v1/README.md +301 -0
- package/external/aport-policies/mcp.tool.execute.v1/policy.json +141 -0
- package/external/aport-policies/messaging.message.send.v1/README.md +230 -0
- package/external/aport-policies/messaging.message.send.v1/express.example.js +183 -0
- package/external/aport-policies/messaging.message.send.v1/fastapi.example.py +193 -0
- package/external/aport-policies/messaging.message.send.v1/policy.json +144 -0
- package/external/aport-policies/policy-template.json +107 -0
- package/external/aport-policies/system.command.execute.v1/README.md +275 -0
- package/external/aport-policies/system.command.execute.v1/policy.json +146 -0
- package/external/aport-spec/CONTRIBUTING.md +273 -0
- package/external/aport-spec/LICENSE +21 -0
- package/external/aport-spec/README.md +168 -0
- package/external/aport-spec/conformance/README.md +294 -0
- package/external/aport-spec/conformance/cases/data.export.v1/contexts/allow_users.json +6 -0
- package/external/aport-spec/conformance/cases/data.export.v1/contexts/deny_pii.json +6 -0
- package/external/aport-spec/conformance/cases/data.export.v1/expected/allow_users.decision.json +19 -0
- package/external/aport-spec/conformance/cases/data.export.v1/expected/deny_pii.decision.json +19 -0
- package/external/aport-spec/conformance/cases/data.export.v1/passports/template.json +29 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/allow_50usd.json +9 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/deny_150usd.json +9 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/deny_currency.json +9 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/allow_50usd.decision.json +19 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/deny_150usd.decision.json +19 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/deny_currency.decision.json +19 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/passports/template.json +42 -0
- package/external/aport-spec/conformance/package.json +44 -0
- package/external/aport-spec/conformance/pnpm-lock.yaml +642 -0
- package/external/aport-spec/conformance/src/cases.ts +371 -0
- package/external/aport-spec/conformance/src/ed25519.ts +167 -0
- package/external/aport-spec/conformance/src/jcs.ts +85 -0
- package/external/aport-spec/conformance/src/runner.ts +533 -0
- package/external/aport-spec/conformance/src/validators.ts +185 -0
- package/external/aport-spec/conformance/test-runner.js +315 -0
- package/external/aport-spec/conformance/tsconfig.json +21 -0
- package/external/aport-spec/error-schema.json +192 -0
- package/external/aport-spec/index.json +12 -0
- package/external/aport-spec/integrations/clawmoat/README.md +12 -0
- package/external/aport-spec/integrations/shield/README.md +245 -0
- package/external/aport-spec/integrations/shield/adapters/index.js +116 -0
- package/external/aport-spec/integrations/shield/adapters/system-command-execute.js +133 -0
- package/external/aport-spec/integrations/shield/test/README.md +58 -0
- package/external/aport-spec/integrations/shield/test/shield.md +40 -0
- package/external/aport-spec/integrations/shield/test/test-shield-to-verify.js +274 -0
- package/external/aport-spec/metrics-schema.json +504 -0
- package/external/aport-spec/oap/CHANGELOG.md +54 -0
- package/external/aport-spec/oap/VERSION.md +40 -0
- package/external/aport-spec/oap/capability-registry.md +229 -0
- package/external/aport-spec/oap/conformance.md +257 -0
- package/external/aport-spec/oap/decision-schema.json +114 -0
- package/external/aport-spec/oap/examples/context.refund.usd.50.json +9 -0
- package/external/aport-spec/oap/examples/decision.allow.sample.json +20 -0
- package/external/aport-spec/oap/examples/decision.deny.sample.json +23 -0
- package/external/aport-spec/oap/examples/passport.instance.v1.json +50 -0
- package/external/aport-spec/oap/examples/passport.template.v1.json +71 -0
- package/external/aport-spec/oap/oap-spec.md +426 -0
- package/external/aport-spec/oap/passport-schema.json +396 -0
- package/external/aport-spec/oap/security.md +213 -0
- package/external/aport-spec/oap/vc/context-oap-v1.jsonld +137 -0
- package/external/aport-spec/oap/vc/examples/oap-decision-vc.json +37 -0
- package/external/aport-spec/oap/vc/examples/oap-passport-vc.json +68 -0
- package/external/aport-spec/oap/vc/tools/INTEGRATION.md +375 -0
- package/external/aport-spec/oap/vc/tools/README.md +278 -0
- package/external/aport-spec/oap/vc/tools/examples/decision-to-vc.js +66 -0
- package/external/aport-spec/oap/vc/tools/examples/passport-to-vc.js +83 -0
- package/external/aport-spec/oap/vc/tools/examples/vc-to-decision.js +77 -0
- package/external/aport-spec/oap/vc/tools/examples/vc-to-passport.js +94 -0
- package/external/aport-spec/oap/vc/tools/package.json +38 -0
- package/external/aport-spec/oap/vc/tools/pnpm-lock.yaml +472 -0
- package/external/aport-spec/oap/vc/tools/src/cli.ts +226 -0
- package/external/aport-spec/oap/vc/tools/src/crypto-utils.ts +427 -0
- package/external/aport-spec/oap/vc/tools/src/index.ts +653 -0
- package/external/aport-spec/oap/vc/tools/src/test.ts +148 -0
- package/external/aport-spec/oap/vc/tools/src/vp.ts +382 -0
- package/external/aport-spec/oap/vc/tools/test-simple.js +214 -0
- package/external/aport-spec/oap/vc/tools/tsconfig.json +19 -0
- package/external/aport-spec/oap/vc/vc-mapping.md +443 -0
- package/external/aport-spec/passport-schema.json +586 -0
- package/external/aport-spec/rate-limiting.md +136 -0
- package/external/aport-spec/transport-profile.md +325 -0
- package/external/aport-spec/webhook-spec.md +314 -0
- package/package.json +70 -0
- package/skills/aport-agent-guardrail/SKILL.md +314 -0
- package/src/evaluator.js +252 -0
- package/src/server/index.js +72 -0
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
# OpenClaw Local Integration Guide
|
|
2
|
+
|
|
3
|
+
**Get OpenClaw secure with APort in under 5 minutes - No cloud required!**
|
|
4
|
+
|
|
5
|
+
This guide shows you how to integrate APort's policy enforcement with OpenClaw using **local passport and policy files**. Perfect for individual developers who want pre-action authorization without cloud dependencies.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 🎯 What You'll Get
|
|
10
|
+
|
|
11
|
+
✅ **Pre-action verification** - Commands and MCP tools checked before execution
|
|
12
|
+
✅ **Graduated controls** - Set limits (max commands, blocked patterns, etc.)
|
|
13
|
+
✅ **Security patterns** - Built-in protection against injection, path traversal, etc.
|
|
14
|
+
✅ **Audit logging** - All decisions logged locally
|
|
15
|
+
✅ **Kill switch** - Emergency stop via local file
|
|
16
|
+
|
|
17
|
+
**No cloud API needed** - Everything works offline!
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 🚀 Quick Start (5 Minutes)
|
|
22
|
+
|
|
23
|
+
### Step 1: Start APort API Server
|
|
24
|
+
|
|
25
|
+
**Use the agent-passport API server** - it runs locally and provides the full evaluation engine.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# Start agent-passport server (from agent-passport repo)
|
|
29
|
+
cd /path/to/agent-passport
|
|
30
|
+
npm run dev
|
|
31
|
+
# Server runs on https://api.aport.io (or your self-hosted API)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Note:** The evaluation engine runs in the `agent-passport` server. This guardrail repo provides examples and SDKs that call the API.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
### Step 2: Create Local Passport
|
|
39
|
+
|
|
40
|
+
Create `~/.openclaw/passport.json`:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"agent_id": "ap_openclaw_local_001",
|
|
45
|
+
"name": "OpenClaw Local Agent",
|
|
46
|
+
"controller_type": "person",
|
|
47
|
+
"description": "Local OpenClaw agent with APort policy enforcement",
|
|
48
|
+
"owner_id": "user@example.com",
|
|
49
|
+
"owner": "Your Name",
|
|
50
|
+
"role": "Developer",
|
|
51
|
+
"capabilities": [
|
|
52
|
+
{ "id": "system.command.execute", "description": "System command execution" },
|
|
53
|
+
{ "id": "mcp.tool.execute", "description": "MCP tool execution" },
|
|
54
|
+
{ "id": "agent.session.create", "description": "Agent session creation" },
|
|
55
|
+
{ "id": "agent.tool.register", "description": "Tool registration" }
|
|
56
|
+
],
|
|
57
|
+
"limits": {
|
|
58
|
+
"allowed_commands": ["npm", "git", "node", "python", "make", "curl"],
|
|
59
|
+
"max_execution_time": 300,
|
|
60
|
+
"blocked_patterns": ["rm -rf", "sudo"],
|
|
61
|
+
"allowed_servers": ["https://mcp.github.com", "https://mcp.openai.com"],
|
|
62
|
+
"max_calls_per_minute": 60,
|
|
63
|
+
"allowed_tools": ["github.pull_requests.create", "github.issues.create"],
|
|
64
|
+
"max_sessions_per_day": 10,
|
|
65
|
+
"max_tools_per_session": 50
|
|
66
|
+
},
|
|
67
|
+
"regions": ["US"],
|
|
68
|
+
"status": "active",
|
|
69
|
+
"assurance_level": "L2",
|
|
70
|
+
"contact": "user@example.com",
|
|
71
|
+
"version": "1.0.0",
|
|
72
|
+
"created_at": "2026-02-08T00:00:00Z",
|
|
73
|
+
"expires_at": "2026-03-08T00:00:00Z"
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
### Step 3: Create Policy Files
|
|
80
|
+
|
|
81
|
+
Copy the 4 OpenClaw policies to `~/.openclaw/policies/`:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
mkdir -p ~/.openclaw/policies
|
|
85
|
+
|
|
86
|
+
# Copy from agent-passport repo
|
|
87
|
+
cp /Users/uchi/Downloads/projects/agent-passport/policies/system.command.execute.v1/policy.json ~/.openclaw/policies/
|
|
88
|
+
cp /Users/uchi/Downloads/projects/agent-passport/policies/mcp.tool.execute.v1/policy.json ~/.openclaw/policies/
|
|
89
|
+
cp /Users/uchi/Downloads/projects/agent-passport/policies/agent.session.create.v1/policy.json ~/.openclaw/policies/
|
|
90
|
+
cp /Users/uchi/Downloads/projects/agent-passport/policies/agent.tool.register.v1/policy.json ~/.openclaw/policies/
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
### Step 4: Verify API Server is Running
|
|
96
|
+
|
|
97
|
+
Make sure the agent-passport server is running:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# Check if server is running
|
|
101
|
+
curl https://api.aport.io/health || echo "API not reachable - check APORT_API_URL or network"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The API server provides the full evaluation engine - no need to copy code!
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 📋 Integration Examples
|
|
109
|
+
|
|
110
|
+
### Example 1: Command Execution Verification
|
|
111
|
+
|
|
112
|
+
**Before executing any command in OpenClaw:**
|
|
113
|
+
|
|
114
|
+
**Option A: Using Local Server (Recommended)**
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
import subprocess
|
|
118
|
+
import json
|
|
119
|
+
import os
|
|
120
|
+
import requests
|
|
121
|
+
|
|
122
|
+
def verify_command(command, args=None):
|
|
123
|
+
"""Verify command execution against APort policy via local server"""
|
|
124
|
+
passport_file = os.path.expanduser("~/.openclaw/passport.json")
|
|
125
|
+
api_base = os.getenv("APORT_API_BASE", "https://api.aport.io")
|
|
126
|
+
|
|
127
|
+
# Load agent_id from passport
|
|
128
|
+
with open(passport_file) as f:
|
|
129
|
+
passport = json.load(f)
|
|
130
|
+
agent_id = passport["agent_id"]
|
|
131
|
+
|
|
132
|
+
# Build context
|
|
133
|
+
context = {
|
|
134
|
+
"agent_id": agent_id,
|
|
135
|
+
"policy_id": "system.command.execute.v1",
|
|
136
|
+
"command": command,
|
|
137
|
+
"args": args or []
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
# Call verification API (local server)
|
|
141
|
+
response = requests.post(
|
|
142
|
+
f"{api_base}/api/verify/policy/system.command.execute.v1",
|
|
143
|
+
json={"context": context}
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
decision = response.json().get("decision") or response.json()
|
|
147
|
+
|
|
148
|
+
if not decision.get("allow"):
|
|
149
|
+
reasons = decision.get("reasons", [])
|
|
150
|
+
reason_text = ", ".join([r.get("message", "") for r in reasons])
|
|
151
|
+
raise PermissionError(f"Policy denied: {reason_text}")
|
|
152
|
+
|
|
153
|
+
return decision
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Option B: Using Evaluation Engine Directly (Advanced)**
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
import sys
|
|
160
|
+
import os
|
|
161
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src', 'evaluator'))
|
|
162
|
+
|
|
163
|
+
from generic_evaluator import evaluateGenericPolicyV2
|
|
164
|
+
import json
|
|
165
|
+
|
|
166
|
+
# Mock env for local use (no KV/D1 needed for basic evaluation)
|
|
167
|
+
class MockEnv:
|
|
168
|
+
pass
|
|
169
|
+
|
|
170
|
+
def verify_command(command, args=None):
|
|
171
|
+
"""Verify command execution using evaluation engine directly"""
|
|
172
|
+
passport_file = os.path.expanduser("~/.openclaw/passport.json")
|
|
173
|
+
|
|
174
|
+
# Load passport
|
|
175
|
+
with open(passport_file) as f:
|
|
176
|
+
passport = json.load(f)
|
|
177
|
+
|
|
178
|
+
# Build context
|
|
179
|
+
context = {
|
|
180
|
+
"command": command,
|
|
181
|
+
"args": args or []
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
# Evaluate policy directly (no API call needed)
|
|
185
|
+
decision = await evaluateGenericPolicyV2(
|
|
186
|
+
MockEnv(), # Mock env - no DB access needed for basic checks
|
|
187
|
+
"system.command.execute.v1",
|
|
188
|
+
passport,
|
|
189
|
+
context
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
if not decision.get("allow"):
|
|
193
|
+
reasons = decision.get("reasons", [])
|
|
194
|
+
reason_text = ", ".join([r.get("message", "") for r in reasons])
|
|
195
|
+
raise PermissionError(f"Policy denied: {reason_text}")
|
|
196
|
+
|
|
197
|
+
return decision
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
# Usage in OpenClaw
|
|
201
|
+
def execute_command(command, args=None):
|
|
202
|
+
# Pre-action verification
|
|
203
|
+
verify_command(command, args)
|
|
204
|
+
|
|
205
|
+
# Execute command (if allowed)
|
|
206
|
+
subprocess.run([command] + (args or []))
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**Test it:**
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
# This should be ALLOWED
|
|
213
|
+
execute_command("npm", ["install"])
|
|
214
|
+
|
|
215
|
+
# This should be DENIED (blocked pattern)
|
|
216
|
+
try:
|
|
217
|
+
execute_command("rm", ["-rf", "/"])
|
|
218
|
+
except PermissionError as e:
|
|
219
|
+
print(f"Blocked: {e}")
|
|
220
|
+
|
|
221
|
+
# This should be DENIED (not in allowlist)
|
|
222
|
+
try:
|
|
223
|
+
execute_command("sudo", ["apt", "update"])
|
|
224
|
+
except PermissionError as e:
|
|
225
|
+
print(f"Blocked: {e}")
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
### Example 2: MCP Tool Verification
|
|
231
|
+
|
|
232
|
+
**Before calling any MCP tool:**
|
|
233
|
+
|
|
234
|
+
```python
|
|
235
|
+
def verify_mcp_tool(server, tool, parameters):
|
|
236
|
+
"""Verify MCP tool execution against APort policy"""
|
|
237
|
+
passport_file = os.path.expanduser("~/.openclaw/passport.json")
|
|
238
|
+
api_base = os.getenv("APORT_API_BASE", "https://api.aport.io")
|
|
239
|
+
|
|
240
|
+
# Load agent_id from passport
|
|
241
|
+
with open(passport_file) as f:
|
|
242
|
+
passport = json.load(f)
|
|
243
|
+
agent_id = passport["agent_id"]
|
|
244
|
+
|
|
245
|
+
# Build context
|
|
246
|
+
context = {
|
|
247
|
+
"agent_id": agent_id,
|
|
248
|
+
"policy_id": "mcp.tool.execute.v1",
|
|
249
|
+
"server": server,
|
|
250
|
+
"tool": tool,
|
|
251
|
+
"parameters": parameters
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
# Call verification API
|
|
255
|
+
import requests
|
|
256
|
+
response = requests.post(
|
|
257
|
+
f"{api_base}/api/verify/policy/mcp.tool.execute.v1",
|
|
258
|
+
json={"context": context}
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
decision = response.json().get("decision") or response.json()
|
|
262
|
+
|
|
263
|
+
if not decision.get("allow"):
|
|
264
|
+
reasons = decision.get("reasons", [])
|
|
265
|
+
reason_text = ", ".join([r.get("message", "") for r in reasons])
|
|
266
|
+
raise PermissionError(f"Policy denied: {reason_text}")
|
|
267
|
+
|
|
268
|
+
return decision
|
|
269
|
+
|
|
270
|
+
# Usage in OpenClaw MCP integration
|
|
271
|
+
def call_mcp_tool(server, tool, parameters):
|
|
272
|
+
# Pre-action verification
|
|
273
|
+
verify_mcp_tool(server, tool, parameters)
|
|
274
|
+
|
|
275
|
+
# Call MCP tool (if allowed)
|
|
276
|
+
# ... your MCP client code here ...
|
|
277
|
+
pass
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Test it:**
|
|
281
|
+
|
|
282
|
+
```python
|
|
283
|
+
# This should be ALLOWED (server in allowlist, tool allowed)
|
|
284
|
+
call_mcp_tool(
|
|
285
|
+
"https://mcp.github.com",
|
|
286
|
+
"github.pull_requests.create",
|
|
287
|
+
{"repo": "test/repo", "title": "Test PR"}
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
# This should be DENIED (server not in allowlist)
|
|
291
|
+
try:
|
|
292
|
+
call_mcp_tool(
|
|
293
|
+
"https://evil-server.com",
|
|
294
|
+
"malicious.tool",
|
|
295
|
+
{}
|
|
296
|
+
)
|
|
297
|
+
except PermissionError as e:
|
|
298
|
+
print(f"Blocked: {e}")
|
|
299
|
+
|
|
300
|
+
# This should be DENIED (tool not allowed)
|
|
301
|
+
try:
|
|
302
|
+
call_mcp_tool(
|
|
303
|
+
"https://mcp.github.com",
|
|
304
|
+
"github.repos.delete",
|
|
305
|
+
{"repo": "test/repo"}
|
|
306
|
+
)
|
|
307
|
+
except PermissionError as e:
|
|
308
|
+
print(f"Blocked: {e}")
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
### Example 3: Complete OpenClaw Integration
|
|
314
|
+
|
|
315
|
+
**Wrap OpenClaw's tool execution:**
|
|
316
|
+
|
|
317
|
+
```python
|
|
318
|
+
# .openclaw/extensions/aport.py
|
|
319
|
+
|
|
320
|
+
import os
|
|
321
|
+
import json
|
|
322
|
+
import requests
|
|
323
|
+
from typing import Dict, Any, Optional
|
|
324
|
+
|
|
325
|
+
class APortGuardrail:
|
|
326
|
+
"""APort policy enforcement for OpenClaw"""
|
|
327
|
+
|
|
328
|
+
def __init__(self, passport_file: Optional[str] = None, api_base: Optional[str] = None):
|
|
329
|
+
self.passport_file = passport_file or os.path.expanduser("~/.openclaw/passport.json")
|
|
330
|
+
self.api_base = api_base or os.getenv("APORT_API_BASE", "https://api.aport.io")
|
|
331
|
+
self._agent_id = None
|
|
332
|
+
|
|
333
|
+
@property
|
|
334
|
+
def agent_id(self) -> str:
|
|
335
|
+
"""Lazy load agent_id from passport"""
|
|
336
|
+
if self._agent_id is None:
|
|
337
|
+
with open(self.passport_file) as f:
|
|
338
|
+
passport = json.load(f)
|
|
339
|
+
self._agent_id = passport["agent_id"]
|
|
340
|
+
return self._agent_id
|
|
341
|
+
|
|
342
|
+
def verify(self, policy_id: str, context: Dict[str, Any]) -> Dict[str, Any]:
|
|
343
|
+
"""Verify action against policy"""
|
|
344
|
+
full_context = {
|
|
345
|
+
"agent_id": self.agent_id,
|
|
346
|
+
"policy_id": policy_id,
|
|
347
|
+
**context
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
response = requests.post(
|
|
351
|
+
f"{self.api_base}/api/verify/policy/{policy_id}",
|
|
352
|
+
json={"context": full_context}
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
decision = response.json().get("decision") or response.json()
|
|
356
|
+
|
|
357
|
+
if not decision.get("allow"):
|
|
358
|
+
reasons = decision.get("reasons", [])
|
|
359
|
+
reason_text = ", ".join([r.get("message", "") for r in reasons])
|
|
360
|
+
raise PermissionError(f"Policy denied: {reason_text}")
|
|
361
|
+
|
|
362
|
+
return decision
|
|
363
|
+
|
|
364
|
+
def verify_command(self, command: str, args: list = None) -> Dict[str, Any]:
|
|
365
|
+
"""Verify command execution"""
|
|
366
|
+
return self.verify("system.command.execute.v1", {
|
|
367
|
+
"command": command,
|
|
368
|
+
"args": args or []
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
def verify_mcp_tool(self, server: str, tool: str, parameters: Dict[str, Any]) -> Dict[str, Any]:
|
|
372
|
+
"""Verify MCP tool execution"""
|
|
373
|
+
return self.verify("mcp.tool.execute.v1", {
|
|
374
|
+
"server": server,
|
|
375
|
+
"tool": tool,
|
|
376
|
+
"parameters": parameters
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
def verify_session_create(self, session_config: Dict[str, Any]) -> Dict[str, Any]:
|
|
380
|
+
"""Verify agent session creation"""
|
|
381
|
+
return self.verify("agent.session.create.v1", session_config)
|
|
382
|
+
|
|
383
|
+
def verify_tool_register(self, tool_config: Dict[str, Any]) -> Dict[str, Any]:
|
|
384
|
+
"""Verify tool registration"""
|
|
385
|
+
return self.verify("agent.tool.register.v1", tool_config)
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
# Usage in OpenClaw agent
|
|
389
|
+
guardrail = APortGuardrail()
|
|
390
|
+
|
|
391
|
+
# Before executing command
|
|
392
|
+
try:
|
|
393
|
+
guardrail.verify_command("npm", ["install"])
|
|
394
|
+
# Execute command...
|
|
395
|
+
except PermissionError as e:
|
|
396
|
+
print(f"Command blocked: {e}")
|
|
397
|
+
|
|
398
|
+
# Before calling MCP tool
|
|
399
|
+
try:
|
|
400
|
+
guardrail.verify_mcp_tool(
|
|
401
|
+
"https://mcp.github.com",
|
|
402
|
+
"github.pull_requests.create",
|
|
403
|
+
{"repo": "test/repo"}
|
|
404
|
+
)
|
|
405
|
+
# Call MCP tool...
|
|
406
|
+
except PermissionError as e:
|
|
407
|
+
print(f"MCP tool blocked: {e}")
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## 🔒 Security Features
|
|
413
|
+
|
|
414
|
+
### Built-in Security Patterns
|
|
415
|
+
|
|
416
|
+
The `system.command.execute.v1` policy includes **40+ built-in security patterns** that are **always enforced**, even if not in your `blocked_patterns`:
|
|
417
|
+
|
|
418
|
+
✅ **Command Injection** - Blocks `;`, `|`, `&`, `` ` ``, `$()`, `&&`, `||`
|
|
419
|
+
✅ **Script Execution** - Blocks `bash -c`, `python -c`, `node -e`
|
|
420
|
+
✅ **Path Traversal** - Blocks `../`, `..\`
|
|
421
|
+
✅ **Privilege Escalation** - Blocks `sudo`, `su`, `doas`
|
|
422
|
+
✅ **Dangerous Operations** - Blocks `rm -rf`, `format`, `dd if=`
|
|
423
|
+
✅ **Environment Files** - Blocks `.env` file access
|
|
424
|
+
✅ **Config Files** - Blocks `nginx.conf`, `apache2.conf` access
|
|
425
|
+
✅ **Credentials** - Blocks AWS credentials, SSH keys, etc.
|
|
426
|
+
✅ **Network Exfiltration** - Blocks `curl`, `wget` with URLs
|
|
427
|
+
✅ **And 30+ more patterns...**
|
|
428
|
+
|
|
429
|
+
**These cannot be bypassed** - even if you add commands to `allowed_commands`, dangerous patterns are still blocked!
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## 📊 Policy Mapping
|
|
434
|
+
|
|
435
|
+
| OpenClaw Action | Policy Pack | Policy File |
|
|
436
|
+
|----------------|-------------|-------------|
|
|
437
|
+
| `exec.run("npm install")` | `system.command.execute.v1` | `system.command.execute.v1/policy.json` |
|
|
438
|
+
| `mcp.call("github.pull_requests.create")` | `mcp.tool.execute.v1` | `mcp.tool.execute.v1/policy.json` |
|
|
439
|
+
| `agent.create_session()` | `agent.session.create.v1` | `agent.session.create.v1/policy.json` |
|
|
440
|
+
| `agent.register_tool()` | `agent.tool.register.v1` | `agent.tool.register.v1/policy.json` |
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
## 🧪 Testing
|
|
445
|
+
|
|
446
|
+
### Test Command Verification
|
|
447
|
+
|
|
448
|
+
```bash
|
|
449
|
+
# Should ALLOW
|
|
450
|
+
bin/aport-guardrail.sh system.command.execute.v1 '{
|
|
451
|
+
"command": "npm",
|
|
452
|
+
"args": ["install"]
|
|
453
|
+
}'
|
|
454
|
+
|
|
455
|
+
# Should DENY (blocked pattern)
|
|
456
|
+
bin/aport-guardrail.sh system.command.execute.v1 '{
|
|
457
|
+
"command": "rm",
|
|
458
|
+
"args": ["-rf", "/"]
|
|
459
|
+
}'
|
|
460
|
+
|
|
461
|
+
# Should DENY (not in allowlist)
|
|
462
|
+
bin/aport-guardrail.sh system.command.execute.v1 '{
|
|
463
|
+
"command": "sudo",
|
|
464
|
+
"args": ["apt", "update"]
|
|
465
|
+
}'
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### Test MCP Tool Verification
|
|
469
|
+
|
|
470
|
+
```bash
|
|
471
|
+
# Should ALLOW
|
|
472
|
+
bin/aport-guardrail.sh mcp.tool.execute.v1 '{
|
|
473
|
+
"server": "https://mcp.github.com",
|
|
474
|
+
"tool": "github.pull_requests.create",
|
|
475
|
+
"parameters": {"repo": "test/repo"}
|
|
476
|
+
}'
|
|
477
|
+
|
|
478
|
+
# Should DENY (server not in allowlist)
|
|
479
|
+
bin/aport-guardrail.sh mcp.tool.execute.v1 '{
|
|
480
|
+
"server": "https://evil-server.com",
|
|
481
|
+
"tool": "malicious.tool",
|
|
482
|
+
"parameters": {}
|
|
483
|
+
}'
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## 🚨 Suspend agent (kill switch = passport status)
|
|
489
|
+
|
|
490
|
+
**Passport is the source of truth.** To suspend the agent, set passport `status` to `suspended`:
|
|
491
|
+
|
|
492
|
+
```bash
|
|
493
|
+
# Suspend: set passport status to suspended
|
|
494
|
+
jq '.status = "suspended"' ~/.openclaw/aport/passport.json > /tmp/passport.tmp && mv /tmp/passport.tmp ~/.openclaw/aport/passport.json
|
|
495
|
+
|
|
496
|
+
# All verifications will now DENY with oap.passport_suspended
|
|
497
|
+
bin/aport-guardrail.sh system.command.execute '{"command":"npm install"}'
|
|
498
|
+
# Exit 1, decision has allow: false, reasons[0].code: oap.passport_suspended
|
|
499
|
+
|
|
500
|
+
# Resume: set status back to active
|
|
501
|
+
jq '.status = "active"' ~/.openclaw/aport/passport.json > /tmp/passport.tmp && mv /tmp/passport.tmp ~/.openclaw/aport/passport.json
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
---
|
|
505
|
+
|
|
506
|
+
## 📝 Audit Logging
|
|
507
|
+
|
|
508
|
+
All decisions are logged to `~/.openclaw/audit.log`:
|
|
509
|
+
|
|
510
|
+
```
|
|
511
|
+
2026-02-08T10:00:00Z | system.command.execute.v1 | ALLOW | npm install | decision_id=abc123
|
|
512
|
+
2026-02-08T10:01:00Z | system.command.execute.v1 | DENY | rm -rf / | reason=blocked_pattern
|
|
513
|
+
2026-02-08T10:02:00Z | mcp.tool.execute.v1 | ALLOW | github.pull_requests.create | decision_id=def456
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
View recent activity:
|
|
517
|
+
```bash
|
|
518
|
+
tail -f ~/.openclaw/audit.log
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
---
|
|
522
|
+
|
|
523
|
+
## 🔄 Upgrading to Cloud (Optional)
|
|
524
|
+
|
|
525
|
+
When you're ready for team collaboration and global kill switch:
|
|
526
|
+
|
|
527
|
+
1. **Sign up** at https://aport.io
|
|
528
|
+
2. **Get API key** from dashboard
|
|
529
|
+
3. **Update passport** with `agent_id` from cloud registry
|
|
530
|
+
4. **Set environment variable:**
|
|
531
|
+
```bash
|
|
532
|
+
export APORT_API_BASE=https://api.aport.io
|
|
533
|
+
export APORT_API_KEY=ap_live_xxxxx
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
**Benefits:**
|
|
537
|
+
- ✅ Global kill switch (<15 seconds)
|
|
538
|
+
- ✅ Multi-machine sync
|
|
539
|
+
- ✅ Ed25519 signed receipts
|
|
540
|
+
- ✅ Team collaboration
|
|
541
|
+
- ✅ Analytics dashboard
|
|
542
|
+
|
|
543
|
+
---
|
|
544
|
+
|
|
545
|
+
## 🆘 Troubleshooting
|
|
546
|
+
|
|
547
|
+
### Problem: "Passport not found"
|
|
548
|
+
```bash
|
|
549
|
+
# Create passport
|
|
550
|
+
cat > ~/.openclaw/passport.json <<EOF
|
|
551
|
+
{
|
|
552
|
+
"agent_id": "ap_openclaw_local_001",
|
|
553
|
+
"status": "active",
|
|
554
|
+
...
|
|
555
|
+
}
|
|
556
|
+
EOF
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### Problem: "API server not running"
|
|
560
|
+
```bash
|
|
561
|
+
# Start local API server
|
|
562
|
+
cd /Users/uchi/Downloads/projects/agent-passport
|
|
563
|
+
npm run dev
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
### Problem: "All commands denied"
|
|
567
|
+
```bash
|
|
568
|
+
# Check passport status (source of truth)
|
|
569
|
+
jq '.status' ~/.openclaw/aport/passport.json
|
|
570
|
+
# Should be "active"; if "suspended" or "revoked", set back to "active" to resume
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
---
|
|
574
|
+
|
|
575
|
+
## 📚 Next Steps
|
|
576
|
+
|
|
577
|
+
1. ✅ **Customize limits** - Edit `~/.openclaw/passport.json` limits
|
|
578
|
+
2. ✅ **Add more policies** - Copy additional policies from agent-passport repo
|
|
579
|
+
3. ✅ **Integrate with OpenClaw** - Add verification to your agent code
|
|
580
|
+
4. ✅ **Monitor audit logs** - Set up alerts for policy violations
|
|
581
|
+
5. ✅ **Upgrade to cloud** - When ready for team features
|
|
582
|
+
|
|
583
|
+
---
|
|
584
|
+
|
|
585
|
+
## 🎉 You're Done!
|
|
586
|
+
|
|
587
|
+
OpenClaw is now secure with APort policy enforcement. All commands and MCP tools are verified before execution, with built-in protection against injection, path traversal, and other attacks.
|
|
588
|
+
|
|
589
|
+
**Questions?** Check out:
|
|
590
|
+
- [QuickStart: OpenClaw Plugin](QUICKSTART_OPENCLAW_PLUGIN.md)
|
|
591
|
+
- [Tool / Policy Mapping](TOOL_POLICY_MAPPING.md)
|
|
592
|
+
- [GitHub Issues](https://github.com/aporthq/aport-agent-guardrails/issues)
|
|
593
|
+
|
|
594
|
+
---
|
|
595
|
+
|
|
596
|
+
**Made with ❤️ by Uchi (https://github.com/uchibeke/)**
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# OpenClaw tools, policies, and passport
|
|
2
|
+
|
|
3
|
+
How the APort plugin maps OpenClaw tools to policies and where limits live (passport vs policy).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## How it works
|
|
8
|
+
|
|
9
|
+
1. **Tool → policy** is defined in the **plugin** (`mapToolToPolicy` in `extensions/openclaw-aport/index.js`). OpenClaw tool names (e.g. `exec`, `message`, `read`) are mapped to APort policy IDs (e.g. `system.command.execute.v1`, `messaging.message.send.v1`).
|
|
10
|
+
2. **Passport** holds **capabilities** (what the agent is allowed to do at a high level) and **limits** (per-policy constraints). There is **no global “tool allowlist”** in the passport for OpenClaw tool names. Control is per policy:
|
|
11
|
+
- **exec** → policy **system.command.execute.v1** → passport **limits.system.command.execute** (e.g. `allowed_commands`, `blocked_patterns`).
|
|
12
|
+
- **message** / **messaging.*** → policy **messaging.message.send.v1** → passport **limits.messaging** (e.g. `msgs_per_min`, `msgs_per_day`).
|
|
13
|
+
- **read**, **write**, **edit**, **browser**, **cron**, etc. → **no mapping** in this plugin → treated as unmapped (allowed by default when `allowUnmappedTools: true`).
|
|
14
|
+
3. **OAP spec:** The passport schema has **limits** per policy and, for MCP, **mcp.servers** / **mcp.tools**. It does not define a single “allowed OpenClaw tools” list; the plugin decides which tools are mapped and which are unmapped.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## exec and allowed_commands (OAP: limits live in the passport)
|
|
19
|
+
|
|
20
|
+
Per the **Open Agent Passport (OAP) spec**, the passport has a **limits** object: *operational limits per capability*. Each policy reads its limits from the passport under a key that matches the policy (e.g. `system.command.execute`, `messaging`). There is no separate “allowed list” config outside the passport.
|
|
21
|
+
|
|
22
|
+
- **Where it’s set:** **passport.limits.system.command.execute.allowed_commands** (array of strings). The guardrail script and API both read `LIMITS = passport.limits["system.command.execute"]` and then `.allowed_commands[]` from that. So **allowed_commands is supposed to be set in the passport**; the wizard (or you) populate it when creating/editing the passport.
|
|
23
|
+
- **How it’s enforced:** For **system.command.execute.v1**, the evaluator checks the request’s `context.command` (e.g. `mkdir` or `mkdir -p foo`). The command is allowed if: (1) **allow-all:** `allowed_commands` contains `"*"` (any command passes the allowlist; **blocked_patterns** still apply), or (2) **allowlist:** the command matches one of the passport’s `allowed_commands` entries (exact or prefix match). If there is at least one entry and the command doesn’t match any and there’s no `"*"`, the policy returns **oap.command_not_allowed** (“Command must be in allowed list”). The **bash** guardrail and the **agent-passport API** both support `"*"` for “allow all commands.”
|
|
24
|
+
- OpenClaw uses **exec** both to run the guardrail script (we detect that and evaluate the **inner** tool) and to run real shell commands (`mkdir`, `npm install`, etc.). When the run is a real command, we evaluate **system.command.execute.v1** and check the command against **passport limits.system.command.execute.allowed_commands**. So: **every command you want to allow must be in that passport array** (e.g. `mkdir`, `cp`, `ls`, `cat`, `echo`, `pwd`, `mv`, `touch`, `npx`, `open`). Re-run the passport wizard for an expanded default, or edit the passport and add them.
|
|
25
|
+
- **If the guardrail is run via exec** (e.g. a skill runs `bash ~/.openclaw/.skills/aport-guardrail.sh ...`), that **exec** is also checked against **allowed_commands**. Include **`bash`** (so `bash /path/to/aport-guardrail.sh ...` passes prefix match) or the full script path so the guardrail invocation is allowed. The default wizard list includes `bash` and `sh`; if you use a narrow list, add them.
|
|
26
|
+
- If you want OpenClaw to run any command without guardrail checks for exec, set **mapExecToPolicy: false** in the plugin config; then **exec** is unmapped and allowed (no policy check). This disables command allowlisting for exec.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## read, write, and other unmapped tools
|
|
31
|
+
|
|
32
|
+
- **read**, **write**, **edit**, **apply_patch**, **browser**, **cron**, **gateway**, **sessions_***, **nodes**, **image**, **web_search**, **web_fetch** are **not** mapped to any APort policy in this plugin. They are **unmapped** and **allowed** by default (when `allowUnmappedTools: true`). So **read is enabled** by default: the plugin sees no policy for `read`, returns allow, and OpenClaw runs the read tool.
|
|
33
|
+
- A failure like **read failed: ENOENT: no such file or directory** is the **tool** failing (e.g. wrong path or missing file, such as `config.json` when the app uses `config.yaml`), not the guardrail blocking. The guardrail already allowed the call; the error happens when the tool executes.
|
|
34
|
+
- To enforce policy on file access you would add a new policy (e.g. **file.read.v1**) and map **read** to it in the plugin, with passport limits (e.g. **allowed_paths**). That is not implemented today.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Summary table
|
|
39
|
+
|
|
40
|
+
| OpenClaw tool (examples) | APort policy | Passport limits (key) |
|
|
41
|
+
|--------------------------|---------------------------|------------------------------------------|
|
|
42
|
+
| exec | system.command.execute.v1 | limits.system.command.execute |
|
|
43
|
+
| message, messaging.* | messaging.message.send.v1 | limits.messaging |
|
|
44
|
+
| git.* | code.repository.merge.v1 | limits.code.repository.merge |
|
|
45
|
+
| mcp.* | mcp.tool.execute.v1 | (API / MCP limits) |
|
|
46
|
+
| read, write, edit, etc. | *(none)* | *(unmapped, allowed by default)* |
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## References
|
|
51
|
+
|
|
52
|
+
- [TOOL_POLICY_MAPPING.md](TOOL_POLICY_MAPPING.md) — Full mapping table and script behavior.
|
|
53
|
+
- [OpenClaw Tools](https://docs.openclaw.ai/tools) — Official list of OpenClaw tools (exec, read, message, cron, etc.).
|
|
54
|
+
- Plugin config: `mapExecToPolicy`, `allowUnmappedTools` in [extensions/openclaw-aport/README.md](../extensions/openclaw-aport/README.md).
|