@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,415 @@
|
|
|
1
|
+
# APort OpenClaw Plugin
|
|
2
|
+
|
|
3
|
+
[](LICENSE) [](package.json) — **APort Node SDK:** [npm](https://www.npmjs.com/package/@aporthq/sdk-node)
|
|
4
|
+
|
|
5
|
+
**Deterministic pre-action authorization for OpenClaw agents.**
|
|
6
|
+
|
|
7
|
+
This plugin registers `before_tool_call` hooks to enforce APort policies **before every tool execution**. No more relying on prompts or hoping the AI follows instructions - the platform enforces policy.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
✅ **100% Deterministic** - Platform enforces, AI cannot bypass
|
|
14
|
+
✅ **Fail-Closed** - Blocks on error (configurable)
|
|
15
|
+
✅ **Local or API Mode** - Use local script or APort cloud API
|
|
16
|
+
✅ **Zero OpenClaw Changes** - Uses existing plugin API
|
|
17
|
+
✅ **Audit Logging** - Optional after_tool_call hook
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
### Option 1: Via Setup Script (Recommended)
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# From aport-agent-guardrails repo root — one run secures OpenClaw
|
|
27
|
+
./bin/openclaw
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The setup script will:
|
|
31
|
+
1. Ask for OpenClaw config directory (default `~/.openclaw`)
|
|
32
|
+
2. Create passport (OAP v1.0 spec) there
|
|
33
|
+
3. Prompt to install this OpenClaw plugin (deterministic enforcement)
|
|
34
|
+
4. Generate `config.yaml` with plugin config (passport path, guardrail script path, mode)
|
|
35
|
+
5. Install guardrail wrappers in `CONFIG_DIR/.skills/` (including `aport-guardrail-bash.sh` used by the plugin in local mode)
|
|
36
|
+
6. Optionally install the APort skill, AGENTS.md rule, and run a smoke test
|
|
37
|
+
7. Verify plugin installation
|
|
38
|
+
|
|
39
|
+
**One run is enough.** After that, start OpenClaw with the generated config (e.g. `openclaw gateway start --config ~/.openclaw/config.yaml`); the plugin will enforce policy on every tool call. See [QUICKSTART_OPENCLAW_PLUGIN.md](../../docs/QUICKSTART_OPENCLAW_PLUGIN.md).
|
|
40
|
+
|
|
41
|
+
### Option 2: Manual Installation
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# From your OpenClaw config directory
|
|
45
|
+
openclaw plugins install /path/to/aport-agent-guardrails/extensions/openclaw-aport
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Configuration
|
|
51
|
+
|
|
52
|
+
Add to your OpenClaw `config.yaml`:
|
|
53
|
+
|
|
54
|
+
```yaml
|
|
55
|
+
plugins:
|
|
56
|
+
enabled: true
|
|
57
|
+
entries:
|
|
58
|
+
openclaw-aport:
|
|
59
|
+
enabled: true
|
|
60
|
+
config:
|
|
61
|
+
# Mode: "local" (use guardrail script) or "api" (use APort cloud API)
|
|
62
|
+
mode: local
|
|
63
|
+
|
|
64
|
+
# Passport file location (in aport/ subdir; legacy: ~/.openclaw/passport.json)
|
|
65
|
+
passportFile: ~/.openclaw/aport/passport.json
|
|
66
|
+
|
|
67
|
+
# For local mode: path to guardrail script
|
|
68
|
+
guardrailScript: ~/.openclaw/.skills/aport-guardrail-bash.sh
|
|
69
|
+
|
|
70
|
+
# For API mode: APort API endpoint
|
|
71
|
+
apiUrl: https://api.aport.io
|
|
72
|
+
# Optional: set APORT_API_KEY in the environment if your API requires auth
|
|
73
|
+
|
|
74
|
+
# Fail-closed: block on error (default: true)
|
|
75
|
+
failClosed: true
|
|
76
|
+
|
|
77
|
+
# Run APort verify on every tool call; never reuse a previous decision (default: true)
|
|
78
|
+
alwaysVerifyEachToolCall: true
|
|
79
|
+
|
|
80
|
+
# Map exec to system.command.execute.v1 and check passport allowed_commands (default: true).
|
|
81
|
+
# Set to false to never block exec (OpenClaw can run any command; no guardrail for exec).
|
|
82
|
+
mapExecToPolicy: true
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## exec, allowed_commands, and unmapped tools
|
|
88
|
+
|
|
89
|
+
- **exec** is OpenClaw’s main “run something” tool: it can run the guardrail script (we delegate to the inner tool) or a real shell command (e.g. `mkdir`, `npm install`). By default we map **exec** → **system.command.execute.v1** and check the **command** against your passport’s **limits.system.command.execute.allowed_commands**. If `mkdir` (or another command) is not in that list, the policy denies with `oap.command_not_allowed`.
|
|
90
|
+
- **Fix:** Add every command you need to **allowed_commands** in your passport (e.g. `mkdir`, `cp`, `ls`, `cat`, `echo`, `pwd`, `mv`, `touch`, `npx`, `open`). Re-run the passport wizard to get an expanded default list, or edit `~/.openclaw/aport/passport.json` (or `~/.openclaw/passport.json` for legacy) and add to `limits.system.command.execute.allowed_commands`. If the guardrail is ever run via **exec** (e.g. a skill runs `bash ~/.openclaw/.skills/aport-guardrail.sh ...`), include **`bash`** (or the full script path) in **allowed_commands** so that invocation is allowed; the wizard default includes `bash` and `sh`.
|
|
91
|
+
- **Optional:** Set **mapExecToPolicy: false** in plugin config so **exec** is not mapped; then exec is treated as an unmapped tool and allowed (no command allowlist). Use only if you rely on other controls; this disables guardrail protection for shell commands.
|
|
92
|
+
- **read, write, edit, etc.** (OpenClaw file/browser tools) have **no policy mapping** in this plugin, so they are **allowed** by default when `allowUnmappedTools` is true. A “read failed: ENOENT” is the tool failing (e.g. file not found), not the guardrail blocking. Tool→policy mapping and passport limits are documented in [TOOL_POLICY_MAPPING.md](../../docs/TOOL_POLICY_MAPPING.md) and [OPENCLAW_TOOLS_AND_POLICIES.md](../../docs/OPENCLAW_TOOLS_AND_POLICIES.md).
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Every tool call = fresh APort check (no caching)
|
|
97
|
+
|
|
98
|
+
The plugin **never reuses a previous decision**. Each `before_tool_call` runs a new verify (local script or API). In local mode each call gets a **unique decision file path** (`decisions/<timestamp>-<id>.json`); the plugin only reads the file it passed to that invocation, so there is no cache or reuse.
|
|
99
|
+
|
|
100
|
+
- **mkdir** → APort runs → Deny
|
|
101
|
+
- **mkdir** again → APort runs again → Allow or Deny based on **current** passport/limits
|
|
102
|
+
|
|
103
|
+
**Exec with no command:** If OpenClaw sends an `exec` tool call with an empty or missing command (e.g. a probe or placeholder), the plugin allows it without calling the guardrail so those pre-checks are not blocked. The real `exec` with a command (e.g. `ls`) is still evaluated by the guardrail.
|
|
104
|
+
|
|
105
|
+
If you updated your passport (e.g. added a command to `allowed_commands` or changed limits), the next tool call is evaluated against the new state. Set `alwaysVerifyEachToolCall: false` only if you add a future cache and want to opt out of per-call verification.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Agent instructions (AGENTS.md)
|
|
110
|
+
|
|
111
|
+
The **guardrail** always runs for every tool call. The **agent** (LLM) must not assume "same tool → same result as last time." Add this to your OpenClaw project's **AGENTS.md** (or equivalent) so the agent always invokes the tool and lets APort decide each time:
|
|
112
|
+
|
|
113
|
+
```markdown
|
|
114
|
+
## APort guardrails
|
|
115
|
+
- **Always invoke the tool** when the user requests an action. Do not skip or assume a tool will be denied because a previous invocation was denied.
|
|
116
|
+
- APort is re-evaluated on every tool call; passport or limits may have changed. The plugin does not reuse previous decisions.
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## How It Works
|
|
122
|
+
|
|
123
|
+
```mermaid
|
|
124
|
+
sequenceDiagram
|
|
125
|
+
participant Agent as 🤖 OpenClaw Agent
|
|
126
|
+
participant Plugin as 🛡️ APort Plugin
|
|
127
|
+
participant Guard as 📋 Guardrail
|
|
128
|
+
participant Tool as 🔧 Tool
|
|
129
|
+
|
|
130
|
+
Agent->>Plugin: before_tool_call(toolName, params)
|
|
131
|
+
Plugin->>Plugin: Map tool → policy
|
|
132
|
+
Plugin->>Guard: Verify policy
|
|
133
|
+
|
|
134
|
+
alt Policy Allows
|
|
135
|
+
Guard-->>Plugin: ✅ Decision: Allow
|
|
136
|
+
Plugin-->>Agent: {} (continue)
|
|
137
|
+
Agent->>Tool: Execute tool
|
|
138
|
+
else Policy Denies
|
|
139
|
+
Guard-->>Plugin: ❌ Decision: Deny
|
|
140
|
+
Plugin-->>Agent: { block: true, blockReason }
|
|
141
|
+
Agent->>Agent: Throw error (tool NOT executed)
|
|
142
|
+
end
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Tool-to-Policy Mapping
|
|
146
|
+
|
|
147
|
+
| OpenClaw Tool | APort Policy |
|
|
148
|
+
|---------------|--------------|
|
|
149
|
+
| `git.create_pr`, `git.merge`, `git.push` | `code.repository.merge.v1` |
|
|
150
|
+
| `exec.run`, `system.command.*`, `bash` | `system.command.execute.v1` |
|
|
151
|
+
| `message.send`, `messaging.*` | `messaging.message.send.v1` |
|
|
152
|
+
| `mcp.*` | `mcp.tool.execute.v1` |
|
|
153
|
+
| `session.create` | `agent.session.create.v1` |
|
|
154
|
+
| `tool.register` | `agent.tool.register.v1` |
|
|
155
|
+
| `payment.refund` | `finance.payment.refund.v1` |
|
|
156
|
+
| `payment.charge` | `finance.payment.charge.v1` |
|
|
157
|
+
| `data.export` | `data.export.create.v1` |
|
|
158
|
+
|
|
159
|
+
Unmapped tools are **allowed** by default so [custom skills](https://clawhub.ai/skills), [ClawHub](https://clawhub.ai/skills?sort=downloads), and built-in tools (e.g. browser, memory) work without APort blocking them. Only tools we map (exec, git, message, mcp, payment, data.export, etc.) are checked against the passport. Set `allowUnmappedTools: false` for strict environments that want to block any unmapped tool.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Modes
|
|
164
|
+
|
|
165
|
+
### Local Mode (Default)
|
|
166
|
+
|
|
167
|
+
**Best for:** Privacy, offline use, no network dependency
|
|
168
|
+
|
|
169
|
+
```yaml
|
|
170
|
+
config:
|
|
171
|
+
mode: local
|
|
172
|
+
passportFile: ~/.openclaw/passport.json
|
|
173
|
+
guardrailScript: ~/.openclaw/.skills/aport-guardrail-bash.sh
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**How it works:**
|
|
177
|
+
1. Plugin calls local bash script
|
|
178
|
+
2. Script evaluates policy using local passport
|
|
179
|
+
3. Returns decision (exit 0 = allow, exit 1 = deny)
|
|
180
|
+
|
|
181
|
+
**No network required** - everything runs locally.
|
|
182
|
+
|
|
183
|
+
### API Mode
|
|
184
|
+
|
|
185
|
+
**Best for:** Advanced features, cloud kill switch, audit logs
|
|
186
|
+
|
|
187
|
+
```yaml
|
|
188
|
+
config:
|
|
189
|
+
mode: api
|
|
190
|
+
passportFile: ~/.openclaw/passport.json
|
|
191
|
+
apiUrl: https://api.aport.io # or your self-hosted API URL
|
|
192
|
+
# Set APORT_API_KEY in the environment if your API requires auth
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**How it works:**
|
|
196
|
+
1. Plugin loads local passport
|
|
197
|
+
2. Sends passport + context to APort API
|
|
198
|
+
3. API evaluates (passport NOT stored, stateless)
|
|
199
|
+
4. Returns signed decision
|
|
200
|
+
|
|
201
|
+
**Network required** - sends passport to API for evaluation.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Testing
|
|
206
|
+
|
|
207
|
+
### Test the Plugin
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
# 1. Install plugin (via setup or manually)
|
|
211
|
+
openclaw plugins install /path/to/extensions/openclaw-aport
|
|
212
|
+
|
|
213
|
+
# 2. Configure in config.yaml (see above)
|
|
214
|
+
|
|
215
|
+
# 3. Start OpenClaw agent
|
|
216
|
+
openclaw agent start
|
|
217
|
+
|
|
218
|
+
# 4. Try a command that should be allowed
|
|
219
|
+
# (Agent will call plugin before executing)
|
|
220
|
+
"Create a file called test.txt"
|
|
221
|
+
|
|
222
|
+
# 5. Try a command that should be denied
|
|
223
|
+
"Run: rm -rf /"
|
|
224
|
+
# Expected: Plugin blocks with reason from passport limits
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Check Plugin Logs
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
# Plugin logs to OpenClaw logs
|
|
231
|
+
openclaw logs | grep "APort Guardrails"
|
|
232
|
+
|
|
233
|
+
# Should see:
|
|
234
|
+
# [APort Guardrails] Loaded: mode=local, passportFile=~/.openclaw/passport.json
|
|
235
|
+
# [APort Guardrails] Checking tool: exec.run → policy: system.command.execute.v1
|
|
236
|
+
# [APort Guardrails] ALLOW: system.command.execute - mkdir test
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Troubleshooting
|
|
242
|
+
|
|
243
|
+
### Plugin not loading
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
# Check plugin list
|
|
247
|
+
openclaw plugins list
|
|
248
|
+
|
|
249
|
+
# Should show:
|
|
250
|
+
# openclaw-aport (enabled)
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
If not listed:
|
|
254
|
+
1. Verify installation: `openclaw plugins install /path/to/extensions/openclaw-aport`
|
|
255
|
+
2. Check config.yaml has `plugins.entries.openclaw-aport.enabled: true`
|
|
256
|
+
3. Restart OpenClaw gateway
|
|
257
|
+
|
|
258
|
+
### Tools not being blocked
|
|
259
|
+
|
|
260
|
+
Check:
|
|
261
|
+
1. **Plugin enabled?** `openclaw plugins list` should show `openclaw-aport (enabled)`
|
|
262
|
+
2. **Tool mapped?** See "Tool-to-Policy Mapping" above. Unmapped tools are allowed by default (so skills work); set `allowUnmappedTools: false` to block them (strict).
|
|
263
|
+
3. **Passport allows it?** Check passport limits in `~/.openclaw/passport.json`
|
|
264
|
+
4. **Script working?** Test directly: `~/.openclaw/.skills/aport-guardrail-bash.sh system.command.execute '{"command":"ls"}'`
|
|
265
|
+
|
|
266
|
+
### Error: "Failed to run guardrail script"
|
|
267
|
+
|
|
268
|
+
Check:
|
|
269
|
+
1. Script exists: `ls -l ~/.openclaw/.skills/aport-guardrail-bash.sh`
|
|
270
|
+
2. Script executable: `chmod +x ~/.openclaw/.skills/aport-guardrail-bash.sh`
|
|
271
|
+
3. Script works: Run test command above
|
|
272
|
+
|
|
273
|
+
### Error: "API request failed"
|
|
274
|
+
|
|
275
|
+
Check:
|
|
276
|
+
1. API URL correct: `echo $APORT_API_URL` or check config.yaml
|
|
277
|
+
2. API running: `curl $APORT_API_URL/health` (if self-hosted)
|
|
278
|
+
3. If your API requires auth: set `APORT_API_KEY` in the environment (do not put it in config)
|
|
279
|
+
4. Network connectivity
|
|
280
|
+
|
|
281
|
+
### Error: "MissingEnvVarError: Missing env var APORT_API_KEY"
|
|
282
|
+
|
|
283
|
+
OpenClaw substitutes `${VAR}` in config and requires the variable to exist. **Do not put `apiKey: \${APORT_API_KEY}` in config.** Fix:
|
|
284
|
+
|
|
285
|
+
1. **Remove apiKey from config:** Edit `~/.openclaw/openclaw.json` and delete the `"apiKey": "${APORT_API_KEY}"` line under `plugins.entries.openclaw-aport.config`, or run `make openclaw-setup` again (setup no longer writes apiKey to config).
|
|
286
|
+
2. If your API requires auth, set `APORT_API_KEY` in the environment only; the plugin reads it at runtime.
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Security Considerations
|
|
291
|
+
|
|
292
|
+
### Fail-Closed by Default
|
|
293
|
+
|
|
294
|
+
By default, `failClosed: true` means **any error blocks the tool**:
|
|
295
|
+
- Script not found → BLOCK
|
|
296
|
+
- API unreachable → BLOCK
|
|
297
|
+
- Invalid passport → BLOCK
|
|
298
|
+
|
|
299
|
+
This is secure-by-default. To fail-open (not recommended):
|
|
300
|
+
|
|
301
|
+
```yaml
|
|
302
|
+
config:
|
|
303
|
+
failClosed: false # Allow on error (NOT RECOMMENDED)
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Plugin Trust
|
|
307
|
+
|
|
308
|
+
Plugins run **in-process** with full access to OpenClaw. Only install from trusted sources:
|
|
309
|
+
- Official APort plugin (this)
|
|
310
|
+
- Your own forks/modifications
|
|
311
|
+
|
|
312
|
+
Use `plugins.allow` allowlist in config.yaml:
|
|
313
|
+
|
|
314
|
+
```yaml
|
|
315
|
+
plugins:
|
|
316
|
+
allow:
|
|
317
|
+
- openclaw-aport
|
|
318
|
+
- your-other-trusted-plugin
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Bypass Prevention
|
|
322
|
+
|
|
323
|
+
**With this plugin:** AI **cannot** bypass policy enforcement. The platform calls `before_tool_call` before every tool.
|
|
324
|
+
|
|
325
|
+
**Without this plugin (AGENTS.md only):** AI **can** bypass via:
|
|
326
|
+
- Prompt injection
|
|
327
|
+
- Forgetting to call guardrail
|
|
328
|
+
- Deciding action is "safe"
|
|
329
|
+
|
|
330
|
+
**Bottom line:** Plugin = deterministic. AGENTS.md = best-effort (not secure).
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## Decisions and audit (OAP)
|
|
335
|
+
|
|
336
|
+
APort decisions are **structured and auditable**. They follow the [OAP v1.0 decision schema](https://github.com/aporthq/aport-spec/oap/decision-schema) (e.g. `decision_id`, `policy_id`, `allow`, `reasons`, `passport_digest`, `signature`, `kid`). The agent-passport API returns signed decisions and can chain them in an audit trail (KV/D1 + audit actions).
|
|
337
|
+
|
|
338
|
+
**Local mode (this plugin):**
|
|
339
|
+
- **Decisions** are written to `<config_dir>/decisions/<timestamp>-<id>.json` and **kept** (not deleted). Each file is a full OAP decision (allow or deny). Config dir is derived from `passportFile` (e.g. `~/.openclaw` → `~/.openclaw/decisions/`).
|
|
340
|
+
- **Audit log** one-line summary is appended to `<config_dir>/audit.log` by the guardrail script (tool, decision_id, allow, policy, code).
|
|
341
|
+
- Local evaluations use **unsigned** decisions (`signature: "ed25519:local-unsigned"`, `kid: "oap:local:dev-key"`). This is the open-source/local promise: structured decisions and audit trail, with optional signing in API or enterprise.
|
|
342
|
+
|
|
343
|
+
**API mode:** The APort API can return signed decisions (`ed25519:...`, `kid: oap:registry:...`) and log decisions server-side (e.g. DecisionService, chained audit). Use API mode when you need signed, verifiable decisions and central audit.
|
|
344
|
+
|
|
345
|
+
**Tamper-resistant local decisions:** Each decision file includes a **content_hash** (SHA-256 of the canonical decision payload). A **chain** is maintained in `decisions/.chain-state.json`: each decision stores `prev_decision_id` and `prev_content_hash`. If a file is edited or the chain is reordered, the plugin detects it (content_hash mismatch) and logs a warning. Decisions remain valid for allow/deny; the check is for audit integrity.
|
|
346
|
+
|
|
347
|
+
**References:** `agent-passport` [spec/oap/decision-schema.json](https://github.com/aporthq/agent-passport/blob/main/spec/oap/decision-schema.json), [examples](https://github.com/aporthq/agent-passport/tree/main/spec/oap/examples), and [functions/api/verify/policy/[pack_id].ts](https://github.com/aporthq/agent-passport/blob/main/functions/api/verify/policy/%5Bpack_id%5D.ts) for how decisions are built and logged.
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## Performance and non-blocking behavior
|
|
352
|
+
|
|
353
|
+
- **Critical path:** Only policy evaluation and writing the decision file (so the plugin can read allow/deny) block the tool call. Chain state is updated synchronously so the next decision can link; audit log append runs in the background and must not block.
|
|
354
|
+
- **Plugin:** Tamper checking (content_hash verification) runs in `setImmediate` after the allow/deny return, so it never delays the tool call.
|
|
355
|
+
- **Guardrail script:** Audit log append is done in a background subshell (`( echo ... >> audit.log ) &`). Chain state write is best-effort (failures do not change the script exit code).
|
|
356
|
+
|
|
357
|
+
**Tests:** Run `npm test` in this directory. Unit tests cover mapping, integrity verification, and canonicalize; performance tests assert that hot paths stay within latency bounds; integration test runs the guardrail script when the repo is available and checks content_hash and chain.
|
|
358
|
+
|
|
359
|
+
## Development
|
|
360
|
+
|
|
361
|
+
### Running Locally
|
|
362
|
+
|
|
363
|
+
```bash
|
|
364
|
+
# From this directory
|
|
365
|
+
cd extensions/openclaw-aport
|
|
366
|
+
|
|
367
|
+
# Test plugin registration
|
|
368
|
+
node index.js
|
|
369
|
+
|
|
370
|
+
# Link for local testing
|
|
371
|
+
npm link
|
|
372
|
+
openclaw plugins install $(pwd)
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Debugging
|
|
376
|
+
|
|
377
|
+
Add debug logging:
|
|
378
|
+
|
|
379
|
+
```javascript
|
|
380
|
+
// In index.js
|
|
381
|
+
api.on('before_tool_call', async (event, ctx) => {
|
|
382
|
+
console.log('[DEBUG] Tool:', event.toolName);
|
|
383
|
+
console.log('[DEBUG] Params:', event.params);
|
|
384
|
+
// ...
|
|
385
|
+
});
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
View logs:
|
|
389
|
+
```bash
|
|
390
|
+
openclaw logs --follow | grep -E "(APort|DEBUG)"
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## License
|
|
396
|
+
|
|
397
|
+
Apache 2.0 - See [LICENSE](../../LICENSE)
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## Support
|
|
402
|
+
|
|
403
|
+
- **Documentation:** [aport-agent-guardrails/docs](../../docs/)
|
|
404
|
+
- **Issues:** [GitHub Issues](https://github.com/aporthq/aport-agent-guardrails/issues)
|
|
405
|
+
- **Discord:** [discord.gg/aport](https://discord.gg/aport)
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
409
|
+
## Roadmap
|
|
410
|
+
|
|
411
|
+
- [ ] **Policy analytics** - Track allow/deny rates per policy
|
|
412
|
+
- [ ] **Custom mappings** - User-defined tool-to-policy mappings
|
|
413
|
+
- [ ] **Performance metrics** - Measure policy evaluation latency
|
|
414
|
+
- [ ] **Batch verification** - Verify multiple tools at once
|
|
415
|
+
- [ ] **Policy caching** - Cache decisions for repeated actions
|