@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
package/bin/openclaw
ADDED
|
@@ -0,0 +1,660 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# OpenClaw + APort: interactive setup
|
|
3
|
+
# Run from repo root. One run secures OpenClaw: creates passport, installs the
|
|
4
|
+
# APort plugin (deterministic enforcement), writes config and guardrail wrappers.
|
|
5
|
+
# After setup, start OpenClaw with the generated config (e.g. --config ~/.openclaw/config.yaml).
|
|
6
|
+
#
|
|
7
|
+
# Usage: ./bin/openclaw [agent_id]
|
|
8
|
+
# agent_id (optional): Hosted passport ID from aport.io (e.g., ap_abc123...)
|
|
9
|
+
#
|
|
10
|
+
# Naming: This script has no .sh extension so it can be invoked as a single
|
|
11
|
+
# command (e.g. make openclaw-setup → openclaw). Binaries/entrypoints are
|
|
12
|
+
# typically named without extension (e.g. git, npm); .sh is for library scripts.
|
|
13
|
+
|
|
14
|
+
set -e
|
|
15
|
+
|
|
16
|
+
# Resolve REPO_ROOT: follow symlinks so npx (node_modules/.bin/agent-guardrails -> package/bin/openclaw) works
|
|
17
|
+
SCRIPT_PATH="${BASH_SOURCE[0]}"
|
|
18
|
+
while [ -L "$SCRIPT_PATH" ]; do
|
|
19
|
+
TARGET="$(readlink "$SCRIPT_PATH")"
|
|
20
|
+
if [[ "$TARGET" == /* ]]; then
|
|
21
|
+
SCRIPT_PATH="$TARGET"
|
|
22
|
+
else
|
|
23
|
+
SCRIPT_PATH="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)/$TARGET"
|
|
24
|
+
fi
|
|
25
|
+
done
|
|
26
|
+
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
|
|
27
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
28
|
+
|
|
29
|
+
APORT_PLUGIN_PATH="$REPO_ROOT/extensions/openclaw-aport"
|
|
30
|
+
DEFAULT_CONFIG="${OPENCLAW_HOME:-$HOME/.openclaw}"
|
|
31
|
+
|
|
32
|
+
# Parse agent_id from command line
|
|
33
|
+
HOSTED_AGENT_ID=""
|
|
34
|
+
if [ -n "$1" ]; then
|
|
35
|
+
# Validate agent_id format (ap_ followed by 32 hex chars)
|
|
36
|
+
if [[ "$1" =~ ^ap_[a-f0-9]{32}$ ]]; then
|
|
37
|
+
HOSTED_AGENT_ID="$1"
|
|
38
|
+
else
|
|
39
|
+
echo "❌ Invalid agent_id format: $1"
|
|
40
|
+
echo " Expected: ap_[32 hex characters]"
|
|
41
|
+
echo " Example: ap_fa2f6d53bb5b4c98b9af0124285b6e0f"
|
|
42
|
+
exit 1
|
|
43
|
+
fi
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
echo ""
|
|
47
|
+
echo " 🛡️ APort + OpenClaw Setup"
|
|
48
|
+
echo " ═══════════════════════════"
|
|
49
|
+
echo ""
|
|
50
|
+
|
|
51
|
+
if [ -n "$HOSTED_AGENT_ID" ]; then
|
|
52
|
+
echo " ✅ Using hosted passport"
|
|
53
|
+
echo " Agent ID: $HOSTED_AGENT_ID"
|
|
54
|
+
echo " Your passport will be fetched from APort API on every call."
|
|
55
|
+
echo ""
|
|
56
|
+
echo " This will:"
|
|
57
|
+
echo " 1. Choose where to store your OpenClaw config"
|
|
58
|
+
echo " 2. Install APort OpenClaw plugin (deterministic enforcement)"
|
|
59
|
+
echo " 3. Install skill wrappers and the APort skill for OpenClaw"
|
|
60
|
+
echo " 4. Show tool → policy pack mapping"
|
|
61
|
+
echo ""
|
|
62
|
+
else
|
|
63
|
+
echo " This will:"
|
|
64
|
+
echo " 1. Choose where to store your OpenClaw config (passport, decisions, audit)"
|
|
65
|
+
echo " 2. Run the passport wizard (OAP v1.0)"
|
|
66
|
+
echo " 3. Install APort OpenClaw plugin (deterministic enforcement)"
|
|
67
|
+
echo " 4. Install skill wrappers and the APort skill for OpenClaw"
|
|
68
|
+
echo " 5. Show tool → policy pack mapping"
|
|
69
|
+
echo ""
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
# 1. OpenClaw config directory
|
|
73
|
+
echo " 📁 OpenClaw config directory"
|
|
74
|
+
echo " Default: $DEFAULT_CONFIG"
|
|
75
|
+
echo " (Enter = default; or full path e.g. /Users/you/project/.openclaw or relative e.g. ../my-project)"
|
|
76
|
+
echo ""
|
|
77
|
+
read -p " Config directory [$DEFAULT_CONFIG]: " CONFIG_DIR
|
|
78
|
+
CONFIG_DIR="${CONFIG_DIR:-$DEFAULT_CONFIG}"
|
|
79
|
+
CONFIG_DIR="${CONFIG_DIR/#\~/$HOME}"
|
|
80
|
+
# Resolve to absolute path
|
|
81
|
+
if [ "${CONFIG_DIR#/}" = "$CONFIG_DIR" ]; then
|
|
82
|
+
# No leading / or ~: treat as relative, but fix common mistake (pasted path missing leading /)
|
|
83
|
+
first_seg="${CONFIG_DIR%%/*}"
|
|
84
|
+
case "$(echo "$first_seg" | tr 'A-Z' 'a-z')" in
|
|
85
|
+
users|home) CONFIG_DIR="/$CONFIG_DIR" ;;
|
|
86
|
+
*) CONFIG_DIR="$PWD/$CONFIG_DIR" ;;
|
|
87
|
+
esac
|
|
88
|
+
fi
|
|
89
|
+
# Fix duplicated path (e.g. .../aport-agent-guardrails/Users/uchi/... -> /Users/uchi/...)
|
|
90
|
+
if echo "$CONFIG_DIR" | grep -q '/Users/'; then
|
|
91
|
+
CONFIG_DIR=$(echo "$CONFIG_DIR" | sed 's|.*/Users/|/Users/|')
|
|
92
|
+
elif echo "$CONFIG_DIR" | grep -q '/home/'; then
|
|
93
|
+
CONFIG_DIR=$(echo "$CONFIG_DIR" | sed 's|.*/home/|/home/|')
|
|
94
|
+
fi
|
|
95
|
+
mkdir -p "$CONFIG_DIR"
|
|
96
|
+
CONFIG_DIR="$(cd "$CONFIG_DIR" && pwd)"
|
|
97
|
+
|
|
98
|
+
# APort data: passport, decision, audit in config_dir/aport/ (passport status = source of truth for suspend)
|
|
99
|
+
APORT_DIR="$CONFIG_DIR/aport"
|
|
100
|
+
mkdir -p "$APORT_DIR"
|
|
101
|
+
|
|
102
|
+
echo ""
|
|
103
|
+
echo " ✓ Using: $CONFIG_DIR"
|
|
104
|
+
echo ""
|
|
105
|
+
|
|
106
|
+
# 2. Passport setup (hosted or local)
|
|
107
|
+
# Resolve passport path: prefer aport/, fallback to config root (legacy)
|
|
108
|
+
if [ -f "$APORT_DIR/passport.json" ]; then
|
|
109
|
+
PASSPORT_FILE="$APORT_DIR/passport.json"
|
|
110
|
+
elif [ -f "$CONFIG_DIR/passport.json" ]; then
|
|
111
|
+
PASSPORT_FILE="$CONFIG_DIR/passport.json"
|
|
112
|
+
else
|
|
113
|
+
PASSPORT_FILE="$APORT_DIR/passport.json"
|
|
114
|
+
fi
|
|
115
|
+
export OPENCLAW_CONFIG_DIR="$CONFIG_DIR"
|
|
116
|
+
USE_HOSTED_PASSPORT=false
|
|
117
|
+
|
|
118
|
+
if [ -n "$HOSTED_AGENT_ID" ]; then
|
|
119
|
+
# Agent ID provided via command line - use hosted passport
|
|
120
|
+
echo " 📋 Passport: Hosted (agent_id: $HOSTED_AGENT_ID)"
|
|
121
|
+
echo " Skipping local passport creation."
|
|
122
|
+
echo ""
|
|
123
|
+
USE_HOSTED_PASSPORT=true
|
|
124
|
+
elif [ -f "$PASSPORT_FILE" ]; then
|
|
125
|
+
# Local passport exists
|
|
126
|
+
read -p " Passport already exists. Run wizard again (overwrite)? [y/N]: " again
|
|
127
|
+
if [ "$again" != "y" ] && [ "$again" != "Y" ]; then
|
|
128
|
+
echo " Skipping passport creation."
|
|
129
|
+
else
|
|
130
|
+
"$REPO_ROOT/bin/aport-create-passport.sh" --output "$PASSPORT_FILE"
|
|
131
|
+
fi
|
|
132
|
+
else
|
|
133
|
+
# No agent_id, no local passport - ask user
|
|
134
|
+
echo " 📋 Passport Options:"
|
|
135
|
+
echo " 1. Use hosted passport (agent_id from aport.io)"
|
|
136
|
+
echo " 2. Create new local passport (wizard)"
|
|
137
|
+
echo ""
|
|
138
|
+
read -p " Choice [1/2]: " passport_choice
|
|
139
|
+
|
|
140
|
+
if [ "$passport_choice" = "1" ]; then
|
|
141
|
+
echo ""
|
|
142
|
+
read -p " Enter agent_id from aport.io: " agent_id_input
|
|
143
|
+
# Validate format
|
|
144
|
+
if [[ ! "$agent_id_input" =~ ^ap_[a-f0-9]{32}$ ]]; then
|
|
145
|
+
echo " ❌ Invalid agent_id format. Expected: ap_[32 hex characters]"
|
|
146
|
+
exit 1
|
|
147
|
+
fi
|
|
148
|
+
HOSTED_AGENT_ID="$agent_id_input"
|
|
149
|
+
USE_HOSTED_PASSPORT=true
|
|
150
|
+
echo " ✅ Using hosted passport (agent_id: $HOSTED_AGENT_ID)"
|
|
151
|
+
echo ""
|
|
152
|
+
else
|
|
153
|
+
# Create local passport
|
|
154
|
+
"$REPO_ROOT/bin/aport-create-passport.sh" --output "$PASSPORT_FILE"
|
|
155
|
+
fi
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
# 3. Install OpenClaw Plugin (Deterministic Enforcement)
|
|
159
|
+
echo ""
|
|
160
|
+
echo " 🔌 OpenClaw Plugin Installation"
|
|
161
|
+
echo " ════════════════════════════════"
|
|
162
|
+
echo ""
|
|
163
|
+
echo " The APort OpenClaw plugin provides DETERMINISTIC policy enforcement:"
|
|
164
|
+
echo " ✅ Platform enforces policy before EVERY tool execution"
|
|
165
|
+
echo " ✅ AI cannot bypass - enforcement happens at platform level"
|
|
166
|
+
echo " ✅ Fail-closed by default - blocks on error"
|
|
167
|
+
echo ""
|
|
168
|
+
echo " Compare this to AGENTS.md (best-effort only):"
|
|
169
|
+
echo " ⚠️ AI follows prompts to call guardrail"
|
|
170
|
+
echo " ⚠️ Can be bypassed via prompt injection"
|
|
171
|
+
echo " ⚠️ Not deterministic"
|
|
172
|
+
echo ""
|
|
173
|
+
|
|
174
|
+
PLUGIN_INSTALLED=false
|
|
175
|
+
if true; then
|
|
176
|
+
# Check if openclaw CLI is available
|
|
177
|
+
if ! command -v openclaw &> /dev/null; then
|
|
178
|
+
echo ""
|
|
179
|
+
echo " ⚠️ OpenClaw CLI not found in PATH"
|
|
180
|
+
echo " The plugin will be configured, but you'll need to install it manually:"
|
|
181
|
+
echo " openclaw plugins install -l $APORT_PLUGIN_PATH"
|
|
182
|
+
echo ""
|
|
183
|
+
read -p " Continue anyway? [Y/n]: " continue_anyway
|
|
184
|
+
continue_anyway=${continue_anyway:-y}
|
|
185
|
+
if [ "$continue_anyway" != "y" ] && [ "$continue_anyway" != "Y" ]; then
|
|
186
|
+
echo " Skipping plugin installation."
|
|
187
|
+
fi
|
|
188
|
+
else
|
|
189
|
+
echo ""
|
|
190
|
+
echo " Installing plugin from: $APORT_PLUGIN_PATH"
|
|
191
|
+
echo " (Using -l to link local directory; OpenClaw accepts only registry or --link for install.)"
|
|
192
|
+
if openclaw plugins install -l "$APORT_PLUGIN_PATH" 2>&1; then
|
|
193
|
+
echo " ✅ Plugin installed successfully"
|
|
194
|
+
PLUGIN_INSTALLED=true
|
|
195
|
+
else
|
|
196
|
+
echo " ⚠️ Plugin installation failed. You can install manually later:"
|
|
197
|
+
echo " openclaw plugins install -l $APORT_PLUGIN_PATH"
|
|
198
|
+
fi
|
|
199
|
+
echo ""
|
|
200
|
+
|
|
201
|
+
# Verify installation
|
|
202
|
+
if [ "$PLUGIN_INSTALLED" = true ]; then
|
|
203
|
+
if openclaw plugins list 2>/dev/null | grep -q "openclaw-aport"; then
|
|
204
|
+
echo " ✅ Plugin verified in plugins list"
|
|
205
|
+
else
|
|
206
|
+
echo " ⚠️ Plugin installed but not showing in 'openclaw plugins list'"
|
|
207
|
+
fi
|
|
208
|
+
echo ""
|
|
209
|
+
export OPENCLAW_CONFIG_DIR="$CONFIG_DIR"
|
|
210
|
+
# Restart only works when gateway is already running; else start in background.
|
|
211
|
+
# Never let gateway start/restart fail the APort installation.
|
|
212
|
+
if openclaw gateway restart 2>/dev/null; then
|
|
213
|
+
echo " ✅ Gateway restarted; plugin is active."
|
|
214
|
+
else
|
|
215
|
+
# Gateway was not running; start it in background so setup completes without blocking.
|
|
216
|
+
# Any failure here must not break APort installation (no exit 1).
|
|
217
|
+
mkdir -p "$CONFIG_DIR/logs" 2>/dev/null || true
|
|
218
|
+
( OPENCLAW_CONFIG_DIR="$CONFIG_DIR" nohup openclaw gateway start >> "$CONFIG_DIR/logs/gateway.log" 2>&1 & ) 2>/dev/null || true
|
|
219
|
+
sleep 2
|
|
220
|
+
if openclaw gateway probe 2>/dev/null; then
|
|
221
|
+
echo " ✅ Gateway started in background; plugin is active."
|
|
222
|
+
echo " Dashboard: http://127.0.0.1:18789/"
|
|
223
|
+
else
|
|
224
|
+
echo " ⚠️ Gateway start was initiated; it may still be starting."
|
|
225
|
+
echo " If the dashboard is unreachable, run: openclaw doctor"
|
|
226
|
+
echo " Then start in a terminal: openclaw gateway start"
|
|
227
|
+
fi
|
|
228
|
+
fi
|
|
229
|
+
fi
|
|
230
|
+
fi
|
|
231
|
+
|
|
232
|
+
# Generate plugin configuration
|
|
233
|
+
echo ""
|
|
234
|
+
echo " 📝 Generating plugin configuration..."
|
|
235
|
+
CONFIG_YAML="$CONFIG_DIR/config.yaml"
|
|
236
|
+
|
|
237
|
+
# Ask for mode (skip for hosted passport - must use API)
|
|
238
|
+
if [ "$USE_HOSTED_PASSPORT" = true ]; then
|
|
239
|
+
PLUGIN_MODE="api"
|
|
240
|
+
echo ""
|
|
241
|
+
echo " Plugin mode: api (required for hosted passport)"
|
|
242
|
+
read -p " APort API URL [https://api.aport.io]: " api_url
|
|
243
|
+
api_url=${api_url:-https://api.aport.io}
|
|
244
|
+
else
|
|
245
|
+
echo ""
|
|
246
|
+
echo " Plugin mode:"
|
|
247
|
+
echo " 1. local - Use local guardrail script (privacy, offline)"
|
|
248
|
+
echo " 2. api - Use APort cloud API (default; for traction and cloud features)"
|
|
249
|
+
echo ""
|
|
250
|
+
read -p " Mode [1=local, 2=api]: " mode_choice
|
|
251
|
+
mode_choice=${mode_choice:-2}
|
|
252
|
+
|
|
253
|
+
if [ "$mode_choice" = "2" ]; then
|
|
254
|
+
PLUGIN_MODE="api"
|
|
255
|
+
echo ""
|
|
256
|
+
read -p " APort API URL [https://api.aport.io]: " api_url
|
|
257
|
+
api_url=${api_url:-https://api.aport.io}
|
|
258
|
+
else
|
|
259
|
+
PLUGIN_MODE="local"
|
|
260
|
+
fi
|
|
261
|
+
fi
|
|
262
|
+
|
|
263
|
+
echo ""
|
|
264
|
+
echo " Strict mode: block tools with no policy mapping (custom skills/ClawHub allowed by default)."
|
|
265
|
+
read -p " Enable strict mode? [y/N]: " strict_mode
|
|
266
|
+
strict_mode=${strict_mode:-n}
|
|
267
|
+
if [ "$strict_mode" = "y" ] || [ "$strict_mode" = "Y" ]; then
|
|
268
|
+
ALLOW_UNMAPPED="false"
|
|
269
|
+
else
|
|
270
|
+
ALLOW_UNMAPPED="true"
|
|
271
|
+
fi
|
|
272
|
+
|
|
273
|
+
# Create or update config.yaml
|
|
274
|
+
if [ ! -f "$CONFIG_YAML" ]; then
|
|
275
|
+
cat > "$CONFIG_YAML" << YAML
|
|
276
|
+
# OpenClaw Configuration
|
|
277
|
+
# Generated by APort setup script
|
|
278
|
+
|
|
279
|
+
plugins:
|
|
280
|
+
enabled: true
|
|
281
|
+
entries:
|
|
282
|
+
openclaw-aport:
|
|
283
|
+
enabled: true
|
|
284
|
+
config:
|
|
285
|
+
# Mode: "local" (use guardrail script) or "api" (use APort cloud API)
|
|
286
|
+
mode: $PLUGIN_MODE
|
|
287
|
+
YAML
|
|
288
|
+
|
|
289
|
+
# Add passport configuration (hosted = agentId only, local = passportFile)
|
|
290
|
+
if [ "$USE_HOSTED_PASSPORT" = true ]; then
|
|
291
|
+
cat >> "$CONFIG_YAML" << YAML
|
|
292
|
+
|
|
293
|
+
# Hosted passport (agent_id only - no local file)
|
|
294
|
+
agentId: $HOSTED_AGENT_ID
|
|
295
|
+
YAML
|
|
296
|
+
else
|
|
297
|
+
cat >> "$CONFIG_YAML" << YAML
|
|
298
|
+
|
|
299
|
+
# Passport file location (in aport/ subdir)
|
|
300
|
+
passportFile: $PASSPORT_FILE
|
|
301
|
+
|
|
302
|
+
# For local mode: path to guardrail script
|
|
303
|
+
guardrailScript: $CONFIG_DIR/.skills/aport-guardrail-bash.sh
|
|
304
|
+
YAML
|
|
305
|
+
fi
|
|
306
|
+
|
|
307
|
+
if [ "$PLUGIN_MODE" = "api" ]; then
|
|
308
|
+
cat >> "$CONFIG_YAML" << YAML
|
|
309
|
+
|
|
310
|
+
# For API mode: APort API endpoint
|
|
311
|
+
apiUrl: $api_url
|
|
312
|
+
YAML
|
|
313
|
+
fi
|
|
314
|
+
|
|
315
|
+
cat >> "$CONFIG_YAML" << YAML
|
|
316
|
+
|
|
317
|
+
# Fail-closed: block on error (default: true)
|
|
318
|
+
failClosed: true
|
|
319
|
+
|
|
320
|
+
# Allow unmapped tools (false = strict: block custom skills/ClawHub unless mapped)
|
|
321
|
+
allowUnmappedTools: $ALLOW_UNMAPPED
|
|
322
|
+
YAML
|
|
323
|
+
|
|
324
|
+
echo " ✅ Created $CONFIG_YAML"
|
|
325
|
+
else
|
|
326
|
+
# Config exists - check if plugin section already present
|
|
327
|
+
if grep -q "openclaw-aport:" "$CONFIG_YAML" 2>/dev/null; then
|
|
328
|
+
echo " ✅ Plugin configuration already present in $CONFIG_YAML"
|
|
329
|
+
else
|
|
330
|
+
echo "" >> "$CONFIG_YAML"
|
|
331
|
+
echo "# APort Plugin Configuration" >> "$CONFIG_YAML"
|
|
332
|
+
echo "plugins:" >> "$CONFIG_YAML"
|
|
333
|
+
echo " enabled: true" >> "$CONFIG_YAML"
|
|
334
|
+
echo " entries:" >> "$CONFIG_YAML"
|
|
335
|
+
echo " openclaw-aport:" >> "$CONFIG_YAML"
|
|
336
|
+
echo " enabled: true" >> "$CONFIG_YAML"
|
|
337
|
+
echo " config:" >> "$CONFIG_YAML"
|
|
338
|
+
echo " mode: $PLUGIN_MODE" >> "$CONFIG_YAML"
|
|
339
|
+
|
|
340
|
+
# Add passport configuration (hosted or local)
|
|
341
|
+
if [ "$USE_HOSTED_PASSPORT" = true ]; then
|
|
342
|
+
echo " agentId: $HOSTED_AGENT_ID" >> "$CONFIG_YAML"
|
|
343
|
+
else
|
|
344
|
+
echo " passportFile: $PASSPORT_FILE" >> "$CONFIG_YAML"
|
|
345
|
+
echo " guardrailScript: $CONFIG_DIR/.skills/aport-guardrail-bash.sh" >> "$CONFIG_YAML"
|
|
346
|
+
fi
|
|
347
|
+
|
|
348
|
+
if [ "$PLUGIN_MODE" = "api" ]; then
|
|
349
|
+
echo " apiUrl: $api_url" >> "$CONFIG_YAML"
|
|
350
|
+
fi
|
|
351
|
+
|
|
352
|
+
echo " failClosed: true" >> "$CONFIG_YAML"
|
|
353
|
+
echo " allowUnmappedTools: $ALLOW_UNMAPPED" >> "$CONFIG_YAML"
|
|
354
|
+
echo " ✅ Appended plugin configuration to $CONFIG_YAML"
|
|
355
|
+
fi
|
|
356
|
+
fi
|
|
357
|
+
|
|
358
|
+
# So the default config (openclaw.json) has plugin mode/apiUrl - gateway often loads it, not config.yaml
|
|
359
|
+
OPENCLAW_JSON="$CONFIG_DIR/openclaw.json"
|
|
360
|
+
if [ -f "$OPENCLAW_JSON" ] && command -v jq &>/dev/null; then
|
|
361
|
+
api_url_val="${api_url:-https://api.aport.io}"
|
|
362
|
+
|
|
363
|
+
if [ "$USE_HOSTED_PASSPORT" = true ]; then
|
|
364
|
+
# Hosted passport config (agentId only)
|
|
365
|
+
CONFIG_JSON=$(jq -n \
|
|
366
|
+
--arg mode "$PLUGIN_MODE" \
|
|
367
|
+
--arg agent_id "$HOSTED_AGENT_ID" \
|
|
368
|
+
--arg api_url "$api_url_val" \
|
|
369
|
+
--arg allow_unmapped "$ALLOW_UNMAPPED" \
|
|
370
|
+
'{ mode: $mode, agentId: $agent_id, failClosed: true, allowUnmappedTools: ($allow_unmapped == "true") } + (if $mode == "api" then { apiUrl: $api_url } else {} end)')
|
|
371
|
+
else
|
|
372
|
+
# Local passport config (passportFile in aport/)
|
|
373
|
+
CONFIG_JSON=$(jq -n \
|
|
374
|
+
--arg mode "$PLUGIN_MODE" \
|
|
375
|
+
--arg pf "$PASSPORT_FILE" \
|
|
376
|
+
--arg gs "$CONFIG_DIR/.skills/aport-guardrail-bash.sh" \
|
|
377
|
+
--arg api_url "$api_url_val" \
|
|
378
|
+
--arg allow_unmapped "$ALLOW_UNMAPPED" \
|
|
379
|
+
'{ mode: $mode, passportFile: $pf, guardrailScript: $gs, failClosed: true, allowUnmappedTools: ($allow_unmapped == "true") } + (if $mode == "api" then { apiUrl: $api_url } else {} end)')
|
|
380
|
+
fi
|
|
381
|
+
|
|
382
|
+
if jq --argjson cfg "$CONFIG_JSON" --arg plugin_path "$APORT_PLUGIN_PATH" '
|
|
383
|
+
.plugins = (.plugins // {}) |
|
|
384
|
+
.plugins.entries = (.plugins.entries // {}) |
|
|
385
|
+
.plugins.entries["openclaw-aport"] = ((.plugins.entries["openclaw-aport"] // {}) | .enabled = true | .config = $cfg) |
|
|
386
|
+
.plugins.load = (.plugins.load // {}) |
|
|
387
|
+
.plugins.load.paths = (
|
|
388
|
+
((.plugins.load.paths // []) | map(select((type == "string" and contains("/openclaw-aport")) | not))) + [$plugin_path]
|
|
389
|
+
) |
|
|
390
|
+
.plugins.installs = (.plugins.installs // {}) |
|
|
391
|
+
.plugins.installs["openclaw-aport"] = ((.plugins.installs["openclaw-aport"] // {})
|
|
392
|
+
| .source = "path"
|
|
393
|
+
| .sourcePath = $plugin_path
|
|
394
|
+
| .installPath = $plugin_path)
|
|
395
|
+
' "$OPENCLAW_JSON" > "$OPENCLAW_JSON.tmp" 2>/dev/null && mv "$OPENCLAW_JSON.tmp" "$OPENCLAW_JSON"; then
|
|
396
|
+
if [ "$USE_HOSTED_PASSPORT" = true ]; then
|
|
397
|
+
echo " ✅ Merged plugin config into $OPENCLAW_JSON (mode=$PLUGIN_MODE, hosted passport, allowUnmappedTools=$ALLOW_UNMAPPED)"
|
|
398
|
+
else
|
|
399
|
+
echo " ✅ Merged plugin config into $OPENCLAW_JSON (mode=$PLUGIN_MODE, allowUnmappedTools=$ALLOW_UNMAPPED)"
|
|
400
|
+
fi
|
|
401
|
+
echo " ✅ Canonicalized plugin load path → $APORT_PLUGIN_PATH"
|
|
402
|
+
fi
|
|
403
|
+
fi
|
|
404
|
+
|
|
405
|
+
echo ""
|
|
406
|
+
echo " ✅ Plugin setup complete!"
|
|
407
|
+
echo ""
|
|
408
|
+
else
|
|
409
|
+
echo ""
|
|
410
|
+
echo " Skipping plugin installation."
|
|
411
|
+
echo " ⚠️ Without the plugin, enforcement relies on AGENTS.md (not deterministic)"
|
|
412
|
+
echo ""
|
|
413
|
+
fi
|
|
414
|
+
|
|
415
|
+
# 4. Store repo path and install wrappers
|
|
416
|
+
echo "$REPO_ROOT" > "$CONFIG_DIR/.aport-repo"
|
|
417
|
+
mkdir -p "$CONFIG_DIR/.skills"
|
|
418
|
+
|
|
419
|
+
write_wrapper() {
|
|
420
|
+
local name="$1"
|
|
421
|
+
cat > "$CONFIG_DIR/.skills/$name" << WRAP
|
|
422
|
+
#!/bin/bash
|
|
423
|
+
CONFIG_DIR="\$(cd "\$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
|
|
424
|
+
APORT_REPO_ROOT="\$(cat "\$CONFIG_DIR/.aport-repo" 2>/dev/null)"
|
|
425
|
+
[ -z "\$APORT_REPO_ROOT" ] && { echo "Error: \$CONFIG_DIR/.aport-repo missing. Re-run bin/openclaw from the guardrails repo." >&2; exit 1; }
|
|
426
|
+
export OPENCLAW_PASSPORT_FILE="\${OPENCLAW_PASSPORT_FILE:-\$CONFIG_DIR/aport/passport.json}"
|
|
427
|
+
export OPENCLAW_DECISION_FILE="\${OPENCLAW_DECISION_FILE:-\$CONFIG_DIR/aport/decision.json}"
|
|
428
|
+
export OPENCLAW_AUDIT_LOG="\${OPENCLAW_AUDIT_LOG:-\$CONFIG_DIR/aport/audit.log}"
|
|
429
|
+
exec "\$APORT_REPO_ROOT/bin/$name" "\$@"
|
|
430
|
+
WRAP
|
|
431
|
+
chmod +x "$CONFIG_DIR/.skills/$name"
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
write_wrapper "aport-guardrail.sh"
|
|
435
|
+
write_wrapper "aport-guardrail-bash.sh"
|
|
436
|
+
write_wrapper "aport-guardrail-api.sh"
|
|
437
|
+
write_wrapper "aport-guardrail-v2.sh"
|
|
438
|
+
write_wrapper "aport-create-passport.sh"
|
|
439
|
+
write_wrapper "aport-status.sh"
|
|
440
|
+
|
|
441
|
+
echo ""
|
|
442
|
+
echo " ✅ Wrappers installed in $CONFIG_DIR/.skills/"
|
|
443
|
+
echo " (They point to this repo and use your config dir for passport/decision/audit.)"
|
|
444
|
+
echo ""
|
|
445
|
+
|
|
446
|
+
# 4b. Normalize passport to OAP spec (spec_version + nested limits) then ensure allowed_commands
|
|
447
|
+
if [ -f "$PASSPORT_FILE" ] && command -v jq &>/dev/null; then
|
|
448
|
+
# 4b1. Ensure spec_version and nested limits (fix old or malformed passports)
|
|
449
|
+
# OAP spec: spec_version "oap/1.0", limits per capability e.g. limits["system.command.execute"]
|
|
450
|
+
NEED_FIX=0
|
|
451
|
+
if [ "$(jq -r '.spec_version // "missing"' "$PASSPORT_FILE")" != "oap/1.0" ]; then
|
|
452
|
+
NEED_FIX=1
|
|
453
|
+
fi
|
|
454
|
+
if jq -e '.limits.allowed_commands' "$PASSPORT_FILE" &>/dev/null; then
|
|
455
|
+
NEED_FIX=1
|
|
456
|
+
fi
|
|
457
|
+
if [ "$NEED_FIX" = "1" ]; then
|
|
458
|
+
echo " 📋 Normalizing passport to OAP spec (spec_version oap/1.0, nested limits)..."
|
|
459
|
+
jq '
|
|
460
|
+
(.spec_version = (.spec_version // "oap/1.0")) |
|
|
461
|
+
(if .limits.allowed_commands then
|
|
462
|
+
.limits["system.command.execute"] = ((.limits["system.command.execute"] // {}) |
|
|
463
|
+
.allowed_commands = (.limits.allowed_commands // ["*"]) |
|
|
464
|
+
.blocked_patterns = (.limits.blocked_patterns // ["rm -rf", "sudo"]) |
|
|
465
|
+
.max_execution_time = (.limits.max_execution_time // 300)) |
|
|
466
|
+
.limits |= del(.allowed_commands, .blocked_patterns, .max_execution_time)
|
|
467
|
+
else . end)
|
|
468
|
+
' "$PASSPORT_FILE" > "$PASSPORT_FILE.tmp" && mv "$PASSPORT_FILE.tmp" "$PASSPORT_FILE"
|
|
469
|
+
echo " ✅ Passport normalized."
|
|
470
|
+
fi
|
|
471
|
+
echo " 📋 Updating passport allowed_commands (default commands: bash, sh, ls, mkdir, npm, …)..."
|
|
472
|
+
# Default commands so normal exec works; user can add more in passport or via wizard
|
|
473
|
+
DEFAULT_CMDS='["npm","yarn","git","node","pnpm","npx","bash","sh","mkdir","cp","ls","cat","echo","pwd","mv","touch","which","open"]'
|
|
474
|
+
if jq -e '.limits["system.command.execute"]' "$PASSPORT_FILE" &>/dev/null; then
|
|
475
|
+
EXISTING=$(jq -r '.limits["system.command.execute"].allowed_commands // []' "$PASSPORT_FILE")
|
|
476
|
+
# Keep ["*"] if user chose allow-any in wizard; otherwise merge with default list
|
|
477
|
+
if echo "$EXISTING" | jq -e 'index("*") != null' &>/dev/null; then
|
|
478
|
+
MERGED="$EXISTING"
|
|
479
|
+
else
|
|
480
|
+
MERGED=$(jq -n \
|
|
481
|
+
--argjson existing "$EXISTING" \
|
|
482
|
+
--argjson default "$DEFAULT_CMDS" \
|
|
483
|
+
'$existing + $default | unique')
|
|
484
|
+
fi
|
|
485
|
+
jq --argjson merged "$MERGED" '.limits["system.command.execute"].allowed_commands = $merged' "$PASSPORT_FILE" > "$PASSPORT_FILE.tmp" && mv "$PASSPORT_FILE.tmp" "$PASSPORT_FILE"
|
|
486
|
+
else
|
|
487
|
+
# New nested block: default to ["*"] per README (blocked_patterns still apply)
|
|
488
|
+
MERGED='["*"]'
|
|
489
|
+
jq --argjson merged "$MERGED" \
|
|
490
|
+
'.limits["system.command.execute"] = ((.limits["system.command.execute"] // {}) | .allowed_commands = $merged | .blocked_patterns = (.blocked_patterns // ["rm -rf", "sudo"]) | .max_execution_time = (.max_execution_time // 300))' "$PASSPORT_FILE" > "$PASSPORT_FILE.tmp" && mv "$PASSPORT_FILE.tmp" "$PASSPORT_FILE"
|
|
491
|
+
fi
|
|
492
|
+
echo " ✅ Passport updated: default commands are in allowed_commands."
|
|
493
|
+
echo ""
|
|
494
|
+
# Validate: run guardrail via same path OpenClaw will use; fail fast if denied
|
|
495
|
+
GUARDRAIL_SCRIPT="$CONFIG_DIR/.skills/aport-guardrail-bash.sh"
|
|
496
|
+
echo " 🔍 Self-check: running guardrail (same path OpenClaw uses)..."
|
|
497
|
+
if ! OPENCLAW_PASSPORT_FILE="$PASSPORT_FILE" "$GUARDRAIL_SCRIPT" system.command.execute '{"command":"bash"}' 2>/dev/null; then
|
|
498
|
+
echo ""
|
|
499
|
+
echo " ❌ Setup incomplete: guardrail self-check was DENIED."
|
|
500
|
+
echo " The passport may still be missing required commands (e.g. node)."
|
|
501
|
+
echo " Edit $PASSPORT_FILE and add needed commands to limits.system.command.execute.allowed_commands,"
|
|
502
|
+
echo " or re-run this script and choose to overwrite the passport in the wizard."
|
|
503
|
+
echo ""
|
|
504
|
+
exit 1
|
|
505
|
+
fi
|
|
506
|
+
echo " ✅ Guardrail self-check passed (ALLOW)."
|
|
507
|
+
echo ""
|
|
508
|
+
else
|
|
509
|
+
if [ ! -f "$PASSPORT_FILE" ]; then
|
|
510
|
+
echo " ⚠️ No passport found; skip allowlist update. Run the wizard first or re-run with an existing passport."
|
|
511
|
+
else
|
|
512
|
+
echo " ⚠️ jq not found; cannot auto-update passport allowed_commands. Add needed commands to limits.system.command.execute.allowed_commands manually."
|
|
513
|
+
fi
|
|
514
|
+
echo ""
|
|
515
|
+
fi
|
|
516
|
+
|
|
517
|
+
# 3b. Install APort skill so OpenClaw loads it (managed skill: ~/.openclaw/skills)
|
|
518
|
+
# OpenClaw loads from: workspace/skills, then ~/.openclaw/skills, then bundled
|
|
519
|
+
# See https://docs.openclaw.ai/tools/skills
|
|
520
|
+
# Copy from repo so installed skill matches skills/aport-agent-guardrail/SKILL.md (single source of truth)
|
|
521
|
+
SKILL_DIR="$CONFIG_DIR/skills/aport-guardrail"
|
|
522
|
+
REPO_SKILL="$REPO_ROOT/skills/aport-agent-guardrail/SKILL.md"
|
|
523
|
+
mkdir -p "$SKILL_DIR"
|
|
524
|
+
if [ -f "$REPO_SKILL" ]; then
|
|
525
|
+
cp "$REPO_SKILL" "$SKILL_DIR/SKILL.md"
|
|
526
|
+
else
|
|
527
|
+
echo " ⚠️ Repo SKILL.md not found at $REPO_SKILL; skipping skill install." >&2
|
|
528
|
+
fi
|
|
529
|
+
echo "✅ APort skill installed in $CONFIG_DIR/skills/aport-guardrail/"
|
|
530
|
+
echo " OpenClaw will load it (managed skill). Use it before effectful actions."
|
|
531
|
+
echo
|
|
532
|
+
|
|
533
|
+
# 4. Tool → policy mapping (how OpenClaw knows which policy pack)
|
|
534
|
+
echo "📋 Tool → policy pack mapping"
|
|
535
|
+
echo " OpenClaw (or your code) calls the guardrail with a tool name and context."
|
|
536
|
+
echo " The guardrail maps that to a policy pack in external/aport-policies:"
|
|
537
|
+
echo
|
|
538
|
+
cat << 'TABLE'
|
|
539
|
+
Tool name (examples) → Policy pack
|
|
540
|
+
------------------------------------------------
|
|
541
|
+
git.create_pr, git.merge, git.* → code.repository.merge.v1
|
|
542
|
+
exec.run, system.command.* → system.command.execute.v1
|
|
543
|
+
message.send, messaging.* → messaging.message.send.v1
|
|
544
|
+
mcp.tool.*, mcp.* → mcp.tool.execute.v1
|
|
545
|
+
agent.session.*, session.* → agent.session.create.v1
|
|
546
|
+
agent.tool.*, tool.register → agent.tool.register.v1
|
|
547
|
+
payment.refund, finance.* → finance.payment.refund.v1
|
|
548
|
+
payment.charge → finance.payment.charge.v1
|
|
549
|
+
database.write, data.export → data.export.create.v1
|
|
550
|
+
TABLE
|
|
551
|
+
echo " Full list: docs/TOOL_POLICY_MAPPING.md"
|
|
552
|
+
echo
|
|
553
|
+
|
|
554
|
+
# 5. Enforcement summary
|
|
555
|
+
echo ""
|
|
556
|
+
echo " 🛡️ Enforcement Summary"
|
|
557
|
+
echo " ═══════════════════════"
|
|
558
|
+
echo ""
|
|
559
|
+
|
|
560
|
+
if [ "$PLUGIN_INSTALLED" = true ]; then
|
|
561
|
+
echo " ✅ DETERMINISTIC ENFORCEMENT: Plugin installed"
|
|
562
|
+
echo ""
|
|
563
|
+
echo " The APort OpenClaw plugin enforces policies at the platform level."
|
|
564
|
+
echo " Every tool execution is checked BEFORE running - AI cannot bypass."
|
|
565
|
+
echo ""
|
|
566
|
+
echo " Config: $CONFIG_DIR/config.yaml"
|
|
567
|
+
echo " Mode: $PLUGIN_MODE"
|
|
568
|
+
echo ""
|
|
569
|
+
echo " To verify plugin is active:"
|
|
570
|
+
echo " openclaw plugins list | grep openclaw-aport"
|
|
571
|
+
echo ""
|
|
572
|
+
else
|
|
573
|
+
echo " ⚠️ BEST-EFFORT ENFORCEMENT: Using AGENTS.md only"
|
|
574
|
+
echo ""
|
|
575
|
+
echo " Without the plugin, enforcement relies on the AI following prompts"
|
|
576
|
+
echo " in workspace/AGENTS.md to call the guardrail before actions."
|
|
577
|
+
echo ""
|
|
578
|
+
echo " This is NOT deterministic and can be bypassed."
|
|
579
|
+
echo ""
|
|
580
|
+
echo " To install the plugin later, run:"
|
|
581
|
+
echo " openclaw plugins install -l $APORT_PLUGIN_PATH"
|
|
582
|
+
echo ""
|
|
583
|
+
fi
|
|
584
|
+
|
|
585
|
+
# 6. Next steps (paths use config dir above)
|
|
586
|
+
echo "📝 Next steps"
|
|
587
|
+
echo "-------------"
|
|
588
|
+
echo " (Paths below are for this config dir. Re-run setup if you use a different one.)"
|
|
589
|
+
echo ""
|
|
590
|
+
echo " 1. Smoke test – run this (one line); expect Exit: 0 = ALLOW:"
|
|
591
|
+
echo " (copy the line below as-is; it has your config path)"
|
|
592
|
+
echo " $CONFIG_DIR/.skills/aport-guardrail.sh system.command.execute '{\"command\":\"node --version\"}'; echo \"Exit: \$? (0=ALLOW, 1=DENY)\""
|
|
593
|
+
echo ""
|
|
594
|
+
|
|
595
|
+
if [ "$PLUGIN_INSTALLED" = true ]; then
|
|
596
|
+
echo " 2. Start the gateway (run in a terminal and keep it open):"
|
|
597
|
+
echo " openclaw gateway start"
|
|
598
|
+
echo " (If the gateway was already running, use: openclaw gateway restart)"
|
|
599
|
+
echo ""
|
|
600
|
+
echo " 3. Test plugin enforcement by trying a blocked action"
|
|
601
|
+
echo " (The plugin will automatically block based on passport limits)"
|
|
602
|
+
echo ""
|
|
603
|
+
else
|
|
604
|
+
echo " 2. In OpenClaw: point skills to the guardrail script:"
|
|
605
|
+
echo " $CONFIG_DIR/.skills/aport-guardrail.sh"
|
|
606
|
+
echo ""
|
|
607
|
+
echo " 3. Optional – use API (self-hosted or cloud):"
|
|
608
|
+
echo " export APORT_API_URL=\"https://api.aport.io\""
|
|
609
|
+
echo " $CONFIG_DIR/.skills/aport-guardrail-api.sh system.command.execute '{\"command\":\"node --version\"}'"
|
|
610
|
+
echo ""
|
|
611
|
+
fi
|
|
612
|
+
# 7. Ensure workspace has APort rule in AGENTS.md (auto-install; no manual merge)
|
|
613
|
+
WORKSPACE_DIR="$CONFIG_DIR/workspace"
|
|
614
|
+
AGENTS="$WORKSPACE_DIR/AGENTS.md"
|
|
615
|
+
mkdir -p "$WORKSPACE_DIR"
|
|
616
|
+
APORT_MARKER="Pre-Action Authorization (APort Guardrails)"
|
|
617
|
+
if [ ! -f "$AGENTS" ]; then
|
|
618
|
+
cp "$REPO_ROOT/docs/AGENTS.md.example" "$AGENTS"
|
|
619
|
+
echo " ✅ Created $AGENTS with APort pre-action rule."
|
|
620
|
+
else
|
|
621
|
+
if grep -q "$APORT_MARKER" "$AGENTS" 2>/dev/null; then
|
|
622
|
+
echo " ✅ APort rule already present in $AGENTS"
|
|
623
|
+
else
|
|
624
|
+
echo "" >> "$AGENTS"
|
|
625
|
+
echo "---" >> "$AGENTS"
|
|
626
|
+
cat "$REPO_ROOT/docs/AGENTS.md.example" >> "$AGENTS"
|
|
627
|
+
echo " ✅ Appended APort pre-action rule to $AGENTS"
|
|
628
|
+
fi
|
|
629
|
+
fi
|
|
630
|
+
echo ""
|
|
631
|
+
|
|
632
|
+
if [ "$PLUGIN_INSTALLED" = true ]; then
|
|
633
|
+
echo " Note: AGENTS.md is installed for reference, but the plugin provides"
|
|
634
|
+
echo " deterministic enforcement (AI cannot bypass). AGENTS.md is optional."
|
|
635
|
+
else
|
|
636
|
+
echo " Note: Without the plugin, AGENTS.md is your only enforcement layer."
|
|
637
|
+
echo " This relies on the AI following instructions (not deterministic)."
|
|
638
|
+
fi
|
|
639
|
+
|
|
640
|
+
echo ""
|
|
641
|
+
echo " View status: $CONFIG_DIR/.skills/aport-status.sh"
|
|
642
|
+
echo ""
|
|
643
|
+
if [ "$USE_HOSTED_PASSPORT" = true ]; then
|
|
644
|
+
echo " Running smoke test (hosted passport): API guardrail with agent_id..."
|
|
645
|
+
if APORT_AGENT_ID="$HOSTED_AGENT_ID" APORT_API_URL="${api_url:-https://api.aport.io}" "$CONFIG_DIR/.skills/aport-guardrail-api.sh" system.command.execute '{"command":"node --version"}' 2>/dev/null; then
|
|
646
|
+
echo " ✅ Smoke test passed (hosted passport, exit 0 = ALLOW)"
|
|
647
|
+
else
|
|
648
|
+
echo " ⚠️ Smoke test skipped or failed (hosted). Ensure API is reachable; plugin will use agentId on each tool call."
|
|
649
|
+
fi
|
|
650
|
+
else
|
|
651
|
+
echo " Running smoke test: $CONFIG_DIR/.skills/aport-guardrail.sh system.command.execute '{\"command\":\"node --version\"}'"
|
|
652
|
+
if "$CONFIG_DIR/.skills/aport-guardrail.sh" system.command.execute '{"command":"node --version"}'; then
|
|
653
|
+
echo " ✅ Smoke test passed (exit 0 = ALLOW)"
|
|
654
|
+
else
|
|
655
|
+
echo " ❌ Smoke test denied or failed (exit 1). Check passport/limits or run: $CONFIG_DIR/.skills/aport-status.sh"
|
|
656
|
+
fi
|
|
657
|
+
fi
|
|
658
|
+
echo ""
|
|
659
|
+
echo " Done. OpenClaw config: $CONFIG_DIR"
|
|
660
|
+
echo ""
|