@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,444 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# aport-create-passport.sh
|
|
3
|
+
# Interactive passport creation wizard (OAP v1.0).
|
|
4
|
+
# Use for any framework; for OpenClaw with a custom config directory, run ./bin/openclaw instead.
|
|
5
|
+
#
|
|
6
|
+
# Usage: ./aport-create-passport.sh [--output FILE] [--non-interactive] [--framework=NAME]
|
|
7
|
+
# --output FILE Write passport to FILE (overrides framework default).
|
|
8
|
+
# --non-interactive Use defaults only; no prompts (for CI/tests). Use --output or set APORT_FRAMEWORK for default path.
|
|
9
|
+
# --framework=NAME Default passport path for this framework (cursor, openclaw, langchain, crewai, n8n).
|
|
10
|
+
#
|
|
11
|
+
# In interactive mode the first question is "Passport file path [default]:"; you can press Enter for the
|
|
12
|
+
# framework default or type a different path. In non-interactive mode, --output always overrides; if not
|
|
13
|
+
# given and APORT_FRAMEWORK is set, the framework default path is used.
|
|
14
|
+
|
|
15
|
+
set -e
|
|
16
|
+
|
|
17
|
+
PASSPORT_FILE=""
|
|
18
|
+
NON_INTERACTIVE=""
|
|
19
|
+
# Parse --output, --non-interactive, --framework=
|
|
20
|
+
while [ $# -gt 0 ]; do
|
|
21
|
+
case "$1" in
|
|
22
|
+
--output) [ -n "${2:-}" ] && PASSPORT_FILE="$2" && shift ;;
|
|
23
|
+
--non-interactive) NON_INTERACTIVE=1 ;;
|
|
24
|
+
--framework=*) APORT_FRAMEWORK="${1#--framework=}" ;;
|
|
25
|
+
esac
|
|
26
|
+
shift
|
|
27
|
+
done
|
|
28
|
+
|
|
29
|
+
# Repo root and lib for get_default_passport_path
|
|
30
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
31
|
+
LIB_DIR="$SCRIPT_DIR/bin/lib"
|
|
32
|
+
if [ -f "$LIB_DIR/config.sh" ]; then
|
|
33
|
+
# shellcheck source=bin/lib/config.sh
|
|
34
|
+
. "$LIB_DIR/config.sh"
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
# Default passport path: --output wins; else framework-specific default; else OpenClaw legacy path
|
|
38
|
+
if [ -z "$PASSPORT_FILE" ]; then
|
|
39
|
+
if [ -n "${APORT_FRAMEWORK:-}" ] && type get_default_passport_path &> /dev/null; then
|
|
40
|
+
PASSPORT_FILE="$(get_default_passport_path "$APORT_FRAMEWORK")"
|
|
41
|
+
else
|
|
42
|
+
PASSPORT_FILE="$HOME/.openclaw/aport/passport.json"
|
|
43
|
+
fi
|
|
44
|
+
fi
|
|
45
|
+
PASSPORT_FILE="${PASSPORT_FILE/#\~/$HOME}"
|
|
46
|
+
|
|
47
|
+
# Config dir: from env, or derived from passport path (e.g. .../aport/passport.json -> parent of aport)
|
|
48
|
+
if [ -n "${OPENCLAW_CONFIG_DIR:-}" ]; then
|
|
49
|
+
CONFIG_DIR="${OPENCLAW_CONFIG_DIR/#\~/$HOME}"
|
|
50
|
+
else
|
|
51
|
+
CONFIG_DIR="$(dirname "$PASSPORT_FILE")"
|
|
52
|
+
case "$PASSPORT_FILE" in
|
|
53
|
+
*/aport/passport.json) CONFIG_DIR="$(dirname "$CONFIG_DIR")" ;;
|
|
54
|
+
esac
|
|
55
|
+
CONFIG_DIR="${CONFIG_DIR/#\~/$HOME}"
|
|
56
|
+
fi
|
|
57
|
+
IDENTITY_FILE="$CONFIG_DIR/workspace/IDENTITY.md"
|
|
58
|
+
|
|
59
|
+
# Repo root for external/aport-spec submodule
|
|
60
|
+
SPEC_SCHEMA="$SCRIPT_DIR/external/aport-spec/oap/passport-schema.json"
|
|
61
|
+
# OAP spec: spec_version "oap/1.0", limits nested per capability (e.g. limits["system.command.execute"]).
|
|
62
|
+
# Local creation has no KYC/assurance proof → L0.
|
|
63
|
+
if [ -f "$SPEC_SCHEMA" ] && command -v jq &> /dev/null; then
|
|
64
|
+
DEFAULT_SPEC_VERSION=$(jq -r '.properties.spec_version.const // "oap/1.0"' "$SPEC_SCHEMA")
|
|
65
|
+
else
|
|
66
|
+
DEFAULT_SPEC_VERSION="oap/1.0"
|
|
67
|
+
fi
|
|
68
|
+
[ -z "$DEFAULT_SPEC_VERSION" ] || [ "$DEFAULT_SPEC_VERSION" = "null" ] && DEFAULT_SPEC_VERSION="oap/1.0"
|
|
69
|
+
DEFAULT_ASSURANCE_LEVEL="L0"
|
|
70
|
+
|
|
71
|
+
# --- Smart defaults ---
|
|
72
|
+
get_default_email() {
|
|
73
|
+
local e
|
|
74
|
+
e=$(git config user.email 2> /dev/null)
|
|
75
|
+
if [ -n "$e" ]; then
|
|
76
|
+
echo "$e"
|
|
77
|
+
return
|
|
78
|
+
fi
|
|
79
|
+
if command -v gh &> /dev/null; then
|
|
80
|
+
e=$(gh api user --jq '.email // .login + "@users.noreply.github.com"' 2> /dev/null)
|
|
81
|
+
[ -n "$e" ] && echo "$e"
|
|
82
|
+
fi
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
get_identity_name() {
|
|
86
|
+
[ ! -f "$IDENTITY_FILE" ] && return
|
|
87
|
+
# OpenClaw IDENTITY.md: "Name: ..." or "**Name**: ..."
|
|
88
|
+
grep -iE '^\s*\*\{0,2\}Name\*\{0,2\}\s*:' "$IDENTITY_FILE" 2> /dev/null | head -1 | sed 's/^[^:]*:[[:space:]]*//; s/^[[:space:]]*//; s/[[:space:]]*$//' | tr -d '\n' | head -c 200
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
get_identity_description() {
|
|
92
|
+
[ ! -f "$IDENTITY_FILE" ] && return
|
|
93
|
+
# Prefer Vibe: or Description: line (case-insensitive)
|
|
94
|
+
local v
|
|
95
|
+
v=$(grep -iE '^\s*\*\{0,2\}(Vibe|Description)\*\{0,2\}\s*:' "$IDENTITY_FILE" 2> /dev/null | head -1 | sed 's/^[^:]*:[[:space:]]*//; s/^[[:space:]]*//; s/[[:space:]]*$//' | tr -d '\n')
|
|
96
|
+
if [ -n "$v" ]; then
|
|
97
|
+
echo "$v" | head -c 300
|
|
98
|
+
return
|
|
99
|
+
fi
|
|
100
|
+
awk '/^#/ { next } /^[[:space:]]*$/ { next } { print; exit }' "$IDENTITY_FILE" 2> /dev/null | head -c 300
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# Avoid set -e exit when git/gh unavailable (e.g. CI with no git user.email)
|
|
104
|
+
DEFAULT_EMAIL=$(get_default_email) || true
|
|
105
|
+
DEFAULT_EMAIL=${DEFAULT_EMAIL:-"user@example.com"}
|
|
106
|
+
DEFAULT_OWNER_TYPE="user"
|
|
107
|
+
DEFAULT_AGENT_NAME=$(get_identity_name) || true
|
|
108
|
+
DEFAULT_AGENT_NAME=${DEFAULT_AGENT_NAME:-"OpenClaw Agent"}
|
|
109
|
+
DEFAULT_AGENT_DESC=$(get_identity_description) || true
|
|
110
|
+
DEFAULT_AGENT_DESC=${DEFAULT_AGENT_DESC:-"Local OpenClaw AI agent with APort guardrails"}
|
|
111
|
+
|
|
112
|
+
if [ -n "$NON_INTERACTIVE" ]; then
|
|
113
|
+
# CI/tests: use defaults, no prompts. Use --output or APORT_FRAMEWORK for default path. Match interactive defaults (README: messaging out of the box).
|
|
114
|
+
owner_id="$DEFAULT_EMAIL"
|
|
115
|
+
owner_type="$DEFAULT_OWNER_TYPE"
|
|
116
|
+
agent_name="$DEFAULT_AGENT_NAME"
|
|
117
|
+
agent_description="$DEFAULT_AGENT_DESC"
|
|
118
|
+
pr_cap=y
|
|
119
|
+
exec_cap=y
|
|
120
|
+
msg_cap=y
|
|
121
|
+
data_cap=n
|
|
122
|
+
max_pr_size=500
|
|
123
|
+
max_prs_per_day=10
|
|
124
|
+
max_msgs_per_day=100
|
|
125
|
+
allowed_repos_input="*"
|
|
126
|
+
exec_allow_scope="*"
|
|
127
|
+
should_expire=n
|
|
128
|
+
never_expires="true"
|
|
129
|
+
expires_at=""
|
|
130
|
+
if command -v uuidgen &> /dev/null; then
|
|
131
|
+
passport_id=$(uuidgen)
|
|
132
|
+
else
|
|
133
|
+
passport_id="local-$(date +%s)-$(openssl rand -hex 4 2> /dev/null || echo $((RANDOM)))"
|
|
134
|
+
fi
|
|
135
|
+
else
|
|
136
|
+
echo ""
|
|
137
|
+
echo " 🛂 APort Passport Creation Wizard"
|
|
138
|
+
echo " ══════════════════════════════════"
|
|
139
|
+
echo ""
|
|
140
|
+
echo " Creates an Open Agent Passport (OAP v1.0) for your agent."
|
|
141
|
+
echo " (Press Enter to use the default when shown in brackets.)"
|
|
142
|
+
echo ""
|
|
143
|
+
# First question: where to store the passport (default = framework-specific or OpenClaw path)
|
|
144
|
+
read -p " Passport file path [$PASSPORT_FILE]: " passport_input
|
|
145
|
+
if [ -n "$passport_input" ]; then
|
|
146
|
+
PASSPORT_FILE="${passport_input/#\~/$HOME}"
|
|
147
|
+
fi
|
|
148
|
+
mkdir -p "$(dirname "$PASSPORT_FILE")"
|
|
149
|
+
CONFIG_DIR="$(dirname "$PASSPORT_FILE")"
|
|
150
|
+
# If passport is in .../aport/passport.json, config dir is parent of aport
|
|
151
|
+
case "$PASSPORT_FILE" in
|
|
152
|
+
*/aport/passport.json) CONFIG_DIR="$(dirname "$(dirname "$PASSPORT_FILE")")" ;;
|
|
153
|
+
esac
|
|
154
|
+
CONFIG_DIR="${CONFIG_DIR/#\~/$HOME}"
|
|
155
|
+
IDENTITY_FILE="$CONFIG_DIR/workspace/IDENTITY.md"
|
|
156
|
+
echo ""
|
|
157
|
+
|
|
158
|
+
# Check if passport already exists
|
|
159
|
+
if [ -f "$PASSPORT_FILE" ]; then
|
|
160
|
+
read -p " Passport already exists. Overwrite? [y/N]: " overwrite
|
|
161
|
+
if [ "$overwrite" != "y" ] && [ "$overwrite" != "Y" ]; then
|
|
162
|
+
echo " Aborting. Re-run and choose a different path, or use --output to specify a file."
|
|
163
|
+
exit 1
|
|
164
|
+
fi
|
|
165
|
+
echo ""
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
# Collect user info
|
|
169
|
+
echo " 📋 Owner & agent"
|
|
170
|
+
echo " ─────────────────"
|
|
171
|
+
echo " (Press Enter to use the default when shown in brackets.)"
|
|
172
|
+
echo ""
|
|
173
|
+
read -p " Your email or ID [$DEFAULT_EMAIL]: " owner_id
|
|
174
|
+
owner_id=${owner_id:-"$DEFAULT_EMAIL"}
|
|
175
|
+
|
|
176
|
+
read -p " Owner type (user/org) [$DEFAULT_OWNER_TYPE]: " owner_type
|
|
177
|
+
owner_type=${owner_type:-"$DEFAULT_OWNER_TYPE"}
|
|
178
|
+
|
|
179
|
+
read -p " Agent name [$DEFAULT_AGENT_NAME]: " agent_name
|
|
180
|
+
agent_name=${agent_name:-"$DEFAULT_AGENT_NAME"}
|
|
181
|
+
|
|
182
|
+
read -p " Agent description [$DEFAULT_AGENT_DESC]: " agent_description
|
|
183
|
+
agent_description=${agent_description:-"$DEFAULT_AGENT_DESC"}
|
|
184
|
+
|
|
185
|
+
echo ""
|
|
186
|
+
|
|
187
|
+
# Choose capabilities
|
|
188
|
+
echo " 🔐 Capabilities"
|
|
189
|
+
echo " ───────────────"
|
|
190
|
+
echo " Choose what your agent can do (y/n). Defaults: PRs, exec, and messaging = yes (matches README/docs); data export = no."
|
|
191
|
+
echo ""
|
|
192
|
+
read -p " • Create and merge pull requests? [Y/n]: " pr_cap
|
|
193
|
+
pr_cap=${pr_cap:-y}
|
|
194
|
+
|
|
195
|
+
read -p " • Execute system commands (npm, git, etc.)? [Y/n]: " exec_cap
|
|
196
|
+
exec_cap=${exec_cap:-y}
|
|
197
|
+
|
|
198
|
+
read -p " • Send messages (email, SMS, etc.)? [Y/n]: " msg_cap
|
|
199
|
+
msg_cap=${msg_cap:-y}
|
|
200
|
+
|
|
201
|
+
read -p " • Export data (database, files, etc.)? [y/N]: " data_cap
|
|
202
|
+
data_cap=${data_cap:-n}
|
|
203
|
+
|
|
204
|
+
echo ""
|
|
205
|
+
|
|
206
|
+
# Configure limits
|
|
207
|
+
echo " ⚙️ Limits"
|
|
208
|
+
echo " ─────────"
|
|
209
|
+
|
|
210
|
+
if [ "$pr_cap" = "y" ] || [ "$pr_cap" = "Y" ]; then
|
|
211
|
+
read -p " Max PR size (files) [500]: " max_pr_size
|
|
212
|
+
max_pr_size=${max_pr_size:-500}
|
|
213
|
+
|
|
214
|
+
read -p " Max PRs per day [10]: " max_prs_per_day
|
|
215
|
+
max_prs_per_day=${max_prs_per_day:-10}
|
|
216
|
+
|
|
217
|
+
read -p " Allowed repos (comma-separated, * for all) [*]: " allowed_repos_input
|
|
218
|
+
allowed_repos_input=${allowed_repos_input:-"*"}
|
|
219
|
+
fi
|
|
220
|
+
|
|
221
|
+
if [ "$exec_cap" = "y" ] || [ "$exec_cap" = "Y" ]; then
|
|
222
|
+
echo " Shell commands: default is allow any (*); blocked patterns (rm -rf, sudo, etc.) still apply."
|
|
223
|
+
echo " Press Enter or type * for allow any; type 'list' for a fixed list (ls, mkdir, npm, …)."
|
|
224
|
+
read -p " [Enter or *=allow any / list=fixed list]: " exec_allow_scope
|
|
225
|
+
exec_allow_scope=${exec_allow_scope:-*}
|
|
226
|
+
fi
|
|
227
|
+
|
|
228
|
+
if [ "$msg_cap" = "y" ] || [ "$msg_cap" = "Y" ]; then
|
|
229
|
+
read -p " Max messages per day [100]: " max_msgs_per_day
|
|
230
|
+
max_msgs_per_day=${max_msgs_per_day:-100}
|
|
231
|
+
fi
|
|
232
|
+
|
|
233
|
+
if [ "$data_cap" = "y" ] || [ "$data_cap" = "Y" ]; then
|
|
234
|
+
read -p " Max export rows [10000]: " max_export_rows
|
|
235
|
+
max_export_rows=${max_export_rows:-10000}
|
|
236
|
+
|
|
237
|
+
read -p " Allow PII export? [y/N]: " allow_pii
|
|
238
|
+
allow_pii=${allow_pii:-n}
|
|
239
|
+
if [ "$allow_pii" = "y" ] || [ "$allow_pii" = "Y" ]; then
|
|
240
|
+
allow_pii_bool="true"
|
|
241
|
+
else
|
|
242
|
+
allow_pii_bool="false"
|
|
243
|
+
fi
|
|
244
|
+
fi
|
|
245
|
+
|
|
246
|
+
echo ""
|
|
247
|
+
|
|
248
|
+
# Generate passport ID
|
|
249
|
+
if command -v uuidgen &> /dev/null; then
|
|
250
|
+
passport_id=$(uuidgen)
|
|
251
|
+
else
|
|
252
|
+
passport_id="local-$(date +%s)-$(openssl rand -hex 4 2> /dev/null || echo $((RANDOM)))"
|
|
253
|
+
fi
|
|
254
|
+
|
|
255
|
+
# Ask about expiration
|
|
256
|
+
echo " 📅 Expiration"
|
|
257
|
+
echo " ─────────────"
|
|
258
|
+
read -p " Should this passport expire? [y/N]: " should_expire
|
|
259
|
+
should_expire=${should_expire:-n}
|
|
260
|
+
|
|
261
|
+
if [ "$should_expire" = "y" ] || [ "$should_expire" = "Y" ]; then
|
|
262
|
+
read -p " Days until expiration [30]: " expire_days
|
|
263
|
+
expire_days=${expire_days:-30}
|
|
264
|
+
|
|
265
|
+
# Calculate expiration date
|
|
266
|
+
if date -v+${expire_days}d &> /dev/null 2>&1; then
|
|
267
|
+
# BSD date (macOS)
|
|
268
|
+
expires_at=$(date -u -v+${expire_days}d +%Y-%m-%dT%H:%M:%SZ)
|
|
269
|
+
else
|
|
270
|
+
# GNU date (Linux)
|
|
271
|
+
expires_at=$(date -u -d "+${expire_days} days" +%Y-%m-%dT%H:%M:%SZ)
|
|
272
|
+
fi
|
|
273
|
+
never_expires="false"
|
|
274
|
+
else
|
|
275
|
+
expires_at=""
|
|
276
|
+
never_expires="true"
|
|
277
|
+
fi
|
|
278
|
+
|
|
279
|
+
fi
|
|
280
|
+
# End of interactive branch; non-interactive already set never_expires/expires_at above.
|
|
281
|
+
|
|
282
|
+
# Build capabilities array
|
|
283
|
+
capabilities_json="["
|
|
284
|
+
if [ "$pr_cap" = "y" ] || [ "$pr_cap" = "Y" ]; then
|
|
285
|
+
capabilities_json="$capabilities_json{\"id\": \"repo.pr.create\"},"
|
|
286
|
+
capabilities_json="$capabilities_json{\"id\": \"repo.merge\"},"
|
|
287
|
+
fi
|
|
288
|
+
if [ "$exec_cap" = "y" ] || [ "$exec_cap" = "Y" ]; then
|
|
289
|
+
capabilities_json="$capabilities_json{\"id\": \"system.command.execute\"},"
|
|
290
|
+
fi
|
|
291
|
+
# Capability IDs must match agent-passport policy requires_capabilities (e.g. messaging.message.send.v1 → messaging.send)
|
|
292
|
+
if [ "$msg_cap" = "y" ] || [ "$msg_cap" = "Y" ]; then
|
|
293
|
+
capabilities_json="$capabilities_json{\"id\": \"messaging.send\"},"
|
|
294
|
+
fi
|
|
295
|
+
if [ "$data_cap" = "y" ] || [ "$data_cap" = "Y" ]; then
|
|
296
|
+
capabilities_json="$capabilities_json{\"id\": \"data.export\"},"
|
|
297
|
+
fi
|
|
298
|
+
# Remove trailing comma
|
|
299
|
+
capabilities_json="${capabilities_json%,}]"
|
|
300
|
+
|
|
301
|
+
# Build limits object
|
|
302
|
+
limits_json="{"
|
|
303
|
+
|
|
304
|
+
if [ "$pr_cap" = "y" ] || [ "$pr_cap" = "Y" ]; then
|
|
305
|
+
# Parse allowed repos
|
|
306
|
+
IFS=',' read -ra REPOS <<< "$allowed_repos_input"
|
|
307
|
+
allowed_repos_json="["
|
|
308
|
+
for repo in "${REPOS[@]}"; do
|
|
309
|
+
repo=$(echo "$repo" | xargs) # trim whitespace
|
|
310
|
+
allowed_repos_json="$allowed_repos_json\"$repo\","
|
|
311
|
+
done
|
|
312
|
+
allowed_repos_json="${allowed_repos_json%,}]"
|
|
313
|
+
|
|
314
|
+
limits_json="$limits_json\"code.repository.merge\": {\"max_prs_per_day\": $max_prs_per_day, \"max_merges_per_day\": 5, \"max_pr_size_kb\": $max_pr_size, \"allowed_repos\": $allowed_repos_json, \"allowed_base_branches\": [\"*\"], \"require_review\": false},"
|
|
315
|
+
fi
|
|
316
|
+
|
|
317
|
+
if [ "$exec_cap" = "y" ] || [ "$exec_cap" = "Y" ]; then
|
|
318
|
+
exec_allow_lower=$(echo "$exec_allow_scope" | tr 'A-Z' 'a-z')
|
|
319
|
+
if [ "$exec_allow_lower" = "list" ]; then
|
|
320
|
+
allowed_commands_json="[\"npm\", \"yarn\", \"git\", \"node\", \"pnpm\", \"npx\", \"bash\", \"sh\", \"mkdir\", \"cp\", \"ls\", \"cat\", \"echo\", \"pwd\", \"mv\", \"touch\", \"which\", \"open\"]"
|
|
321
|
+
else
|
|
322
|
+
# default: allow any (*); blocked_patterns still apply
|
|
323
|
+
allowed_commands_json="[\"*\"]"
|
|
324
|
+
fi
|
|
325
|
+
limits_json="$limits_json\"system.command.execute\": {\"allowed_commands\": $allowed_commands_json, \"blocked_patterns\": [\"rm -rf\", \"sudo\", \"chmod 777\", \"dd if=\", \"mkfs\"], \"max_execution_time\": 300},"
|
|
326
|
+
fi
|
|
327
|
+
|
|
328
|
+
if [ "$msg_cap" = "y" ] || [ "$msg_cap" = "Y" ]; then
|
|
329
|
+
# API/verifier expect flat keys at top level of limits (not nested under messaging.message.send)
|
|
330
|
+
limits_json="$limits_json\"msgs_per_min\": 5, \"msgs_per_day\": $max_msgs_per_day, \"allowed_recipients\": [\"*\"], \"approval_required\": false,"
|
|
331
|
+
fi
|
|
332
|
+
|
|
333
|
+
if [ "$data_cap" = "y" ] || [ "$data_cap" = "Y" ]; then
|
|
334
|
+
limits_json="$limits_json\"data.export\": {\"max_rows\": $max_export_rows, \"allow_pii\": $allow_pii_bool, \"allowed_collections\": [\"*\"]},"
|
|
335
|
+
fi
|
|
336
|
+
|
|
337
|
+
# Remove trailing comma
|
|
338
|
+
limits_json="${limits_json%,}}"
|
|
339
|
+
|
|
340
|
+
# Build metadata object using jq
|
|
341
|
+
metadata_json=$(jq -n \
|
|
342
|
+
--arg name "$agent_name" \
|
|
343
|
+
--arg desc "$agent_description" \
|
|
344
|
+
'{
|
|
345
|
+
name: $name,
|
|
346
|
+
description: $desc,
|
|
347
|
+
version: "1.0.0",
|
|
348
|
+
created_by: "aport-create-passport.sh"
|
|
349
|
+
}')
|
|
350
|
+
|
|
351
|
+
# Create passport JSON (OAP v1.0 compliant)
|
|
352
|
+
mkdir -p "$(dirname "$PASSPORT_FILE")"
|
|
353
|
+
current_timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
354
|
+
|
|
355
|
+
# API expects both agent_id and owner_id; use passport_id as agent_id for local passports
|
|
356
|
+
if [ "$never_expires" = "true" ]; then
|
|
357
|
+
# Passport without expiration (assurance_level and spec_version from external/aport-spec)
|
|
358
|
+
cat > "$PASSPORT_FILE.tmp" << EOF
|
|
359
|
+
{
|
|
360
|
+
"passport_id": "$passport_id",
|
|
361
|
+
"agent_id": "$passport_id",
|
|
362
|
+
"kind": "template",
|
|
363
|
+
"spec_version": "$DEFAULT_SPEC_VERSION",
|
|
364
|
+
"owner_id": "$owner_id",
|
|
365
|
+
"owner_type": "$owner_type",
|
|
366
|
+
"assurance_level": "$DEFAULT_ASSURANCE_LEVEL",
|
|
367
|
+
"status": "active",
|
|
368
|
+
"capabilities": $capabilities_json,
|
|
369
|
+
"limits": $limits_json,
|
|
370
|
+
"regions": ["US", "CA"],
|
|
371
|
+
"metadata": $metadata_json,
|
|
372
|
+
"never_expires": true,
|
|
373
|
+
"created_at": "$current_timestamp",
|
|
374
|
+
"updated_at": "$current_timestamp",
|
|
375
|
+
"version": "1.0.0"
|
|
376
|
+
}
|
|
377
|
+
EOF
|
|
378
|
+
else
|
|
379
|
+
# Passport with expiration (assurance_level and spec_version from external/aport-spec)
|
|
380
|
+
cat > "$PASSPORT_FILE.tmp" << EOF
|
|
381
|
+
{
|
|
382
|
+
"passport_id": "$passport_id",
|
|
383
|
+
"agent_id": "$passport_id",
|
|
384
|
+
"kind": "template",
|
|
385
|
+
"spec_version": "$DEFAULT_SPEC_VERSION",
|
|
386
|
+
"owner_id": "$owner_id",
|
|
387
|
+
"owner_type": "$owner_type",
|
|
388
|
+
"assurance_level": "$DEFAULT_ASSURANCE_LEVEL",
|
|
389
|
+
"status": "active",
|
|
390
|
+
"capabilities": $capabilities_json,
|
|
391
|
+
"limits": $limits_json,
|
|
392
|
+
"regions": ["US", "CA"],
|
|
393
|
+
"metadata": $metadata_json,
|
|
394
|
+
"expires_at": "$expires_at",
|
|
395
|
+
"created_at": "$current_timestamp",
|
|
396
|
+
"updated_at": "$current_timestamp",
|
|
397
|
+
"version": "1.0.0"
|
|
398
|
+
}
|
|
399
|
+
EOF
|
|
400
|
+
fi
|
|
401
|
+
|
|
402
|
+
# Format JSON with jq if available
|
|
403
|
+
if command -v jq &> /dev/null; then
|
|
404
|
+
jq . "$PASSPORT_FILE.tmp" > "$PASSPORT_FILE"
|
|
405
|
+
rm "$PASSPORT_FILE.tmp"
|
|
406
|
+
else
|
|
407
|
+
mv "$PASSPORT_FILE.tmp" "$PASSPORT_FILE"
|
|
408
|
+
echo " ⚠️ jq not found; passport JSON not pretty-printed."
|
|
409
|
+
fi
|
|
410
|
+
# Restrict permissions: passport holds allowlists and identity
|
|
411
|
+
chmod 600 "$PASSPORT_FILE" 2> /dev/null || true
|
|
412
|
+
|
|
413
|
+
echo ""
|
|
414
|
+
echo " ✅ Passport created successfully!"
|
|
415
|
+
echo ""
|
|
416
|
+
echo " 📋 Summary"
|
|
417
|
+
echo " ──────────"
|
|
418
|
+
echo " Location: $PASSPORT_FILE"
|
|
419
|
+
echo " Passport ID: $passport_id"
|
|
420
|
+
echo " Owner: $owner_id ($owner_type)"
|
|
421
|
+
echo " Agent: $agent_name"
|
|
422
|
+
echo " Status: active"
|
|
423
|
+
echo " Spec: oap/1.0"
|
|
424
|
+
if [ "$never_expires" = "true" ]; then
|
|
425
|
+
echo " Expiration: Never"
|
|
426
|
+
else
|
|
427
|
+
echo " Expires: $expires_at"
|
|
428
|
+
fi
|
|
429
|
+
echo ""
|
|
430
|
+
echo " 🔐 Capabilities:"
|
|
431
|
+
[ "$pr_cap" = "y" ] || [ "$pr_cap" = "Y" ] && echo " • Create and merge pull requests"
|
|
432
|
+
[ "$exec_cap" = "y" ] || [ "$exec_cap" = "Y" ] && echo " • Execute system commands"
|
|
433
|
+
[ "$msg_cap" = "y" ] || [ "$msg_cap" = "Y" ] && echo " • Send messages"
|
|
434
|
+
[ "$data_cap" = "y" ] || [ "$data_cap" = "Y" ] && echo " • Export data"
|
|
435
|
+
echo ""
|
|
436
|
+
echo " 📝 Next steps:"
|
|
437
|
+
echo " • Review limits: vim $PASSPORT_FILE"
|
|
438
|
+
echo " • Test guardrail: aport-guardrail.sh system.command.execute '{\"command\":\"node --version\"}'; echo \"Exit: \$? (0=ALLOW, 1=DENY)\""
|
|
439
|
+
echo " • View status: aport-status.sh"
|
|
440
|
+
echo ""
|
|
441
|
+
if [ "$never_expires" != "true" ]; then
|
|
442
|
+
echo " 💡 Passport expires in $expire_days days. Renew before then."
|
|
443
|
+
fi
|
|
444
|
+
echo ""
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# APort Cursor/Copilot/Claude Code hook: read JSON from stdin, call guardrail, return allow/deny; exit 2 = block.
|
|
3
|
+
# Compatible with Cursor (beforeShellExecution, preToolUse), VS Code Copilot, and Claude Code.
|
|
4
|
+
# Input: JSON with "command" and/or "tool"/"name"/"input" (host-dependent). We map to system.command.execute.
|
|
5
|
+
# Output: JSON with "permission": "allow"|"deny" (Cursor) and "allowed": true|false; optional "agentMessage"/"reason".
|
|
6
|
+
# Exit: 0 = allow, 2 = block (deny). Other exits = hook error (host may proceed or fail-open).
|
|
7
|
+
|
|
8
|
+
set -e
|
|
9
|
+
|
|
10
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
|
+
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
12
|
+
GUARDRAIL="$ROOT_DIR/bin/aport-guardrail-bash.sh"
|
|
13
|
+
|
|
14
|
+
# Passport/config: resolver probes ~/.cursor, ~/.openclaw, ~/.aport/langchain, etc. when OPENCLAW_CONFIG_DIR not set
|
|
15
|
+
# shellcheck source=bin/aport-resolve-paths.sh
|
|
16
|
+
. "$ROOT_DIR/bin/aport-resolve-paths.sh"
|
|
17
|
+
|
|
18
|
+
# Read stdin (single JSON object; Cursor sends one payload per invocation)
|
|
19
|
+
INPUT=""
|
|
20
|
+
if [ -t 0 ]; then
|
|
21
|
+
# No stdin (e.g. manual test): treat as allow to avoid blocking
|
|
22
|
+
INPUT='{}'
|
|
23
|
+
else
|
|
24
|
+
INPUT="$(cat)"
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# Empty or invalid JSON -> allow with warning (fail-open for bad input)
|
|
28
|
+
if [ -z "$INPUT" ]; then
|
|
29
|
+
echo '{"permission":"allow","allowed":true,"agentMessage":"APort: no input received"}'
|
|
30
|
+
exit 0
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
# Parse and normalize to tool + context for guardrail
|
|
34
|
+
# Cursor beforeShellExecution: { "command": "..." }
|
|
35
|
+
# preToolUse / Copilot: { "tool": "runTerminalCommand", "input": { "command": "..." } } or similar
|
|
36
|
+
TOOL_NAME="exec.run"
|
|
37
|
+
CONTEXT_JSON="{}"
|
|
38
|
+
if command -v jq &> /dev/null; then
|
|
39
|
+
CMD=$(echo "$INPUT" | jq -r '.command // .input.command // .input.cmd // .args[0] // ""')
|
|
40
|
+
if [ -n "$CMD" ] && [ "$CMD" != "null" ]; then
|
|
41
|
+
CONTEXT_JSON=$(echo "$INPUT" | jq -c '{command: (.command // .input.command // .input.cmd), args: (.args // .input.args // [])}' 2> /dev/null || echo "{}")
|
|
42
|
+
if [ -z "$CONTEXT_JSON" ] || [ "$CONTEXT_JSON" = "null" ]; then
|
|
43
|
+
CONTEXT_JSON=$(jq -n -c --arg cmd "$CMD" '{command: $cmd}')
|
|
44
|
+
fi
|
|
45
|
+
fi
|
|
46
|
+
# If no command found, still pass through; guardrail may deny unknown
|
|
47
|
+
if [ -z "$CONTEXT_JSON" ] || [ "$CONTEXT_JSON" = "null" ]; then
|
|
48
|
+
CONTEXT_JSON="$INPUT"
|
|
49
|
+
fi
|
|
50
|
+
else
|
|
51
|
+
CONTEXT_JSON="$INPUT"
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
# Call existing bash guardrail: exit 0 = allow, exit 1 = deny (forward config for subprocess)
|
|
55
|
+
set +e
|
|
56
|
+
OPENCLAW_CONFIG_DIR="${OPENCLAW_CONFIG_DIR:-$HOME/.openclaw}" OPENCLAW_PASSPORT_FILE="${OPENCLAW_PASSPORT_FILE:-}" OPENCLAW_DECISION_FILE="${OPENCLAW_DECISION_FILE:-}" "$GUARDRAIL" "$TOOL_NAME" "$CONTEXT_JSON" 2> /dev/null
|
|
57
|
+
GUARDRAIL_EXIT=$?
|
|
58
|
+
set -e
|
|
59
|
+
|
|
60
|
+
if [ "$GUARDRAIL_EXIT" -eq 0 ]; then
|
|
61
|
+
echo '{"permission":"allow","allowed":true}'
|
|
62
|
+
exit 0
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# Deny: output reason from decision file if available (guardrail writes decision before exit 1)
|
|
66
|
+
REASON="Policy denied this action."
|
|
67
|
+
if [ -n "${OPENCLAW_DECISION_FILE:-}" ] && [ -f "$OPENCLAW_DECISION_FILE" ] && command -v jq &> /dev/null; then
|
|
68
|
+
R=$(jq -r '.reasons[0].message // empty' "$OPENCLAW_DECISION_FILE" 2> /dev/null)
|
|
69
|
+
if [ -n "$R" ]; then
|
|
70
|
+
REASON="$R"
|
|
71
|
+
fi
|
|
72
|
+
fi
|
|
73
|
+
# If no decision file was set, try common config dirs so we can show actual deny reason
|
|
74
|
+
if [ "$REASON" = "Policy denied this action." ] && command -v jq &> /dev/null; then
|
|
75
|
+
for DEC in "${OPENCLAW_CONFIG_DIR:-$HOME/.cursor}/aport/decision.json" "$HOME/.cursor/aport/decision.json" "$HOME/.openclaw/aport/decision.json"; do
|
|
76
|
+
if [ -f "$DEC" ]; then
|
|
77
|
+
R=$(jq -r '.reasons[0].message // empty' "$DEC" 2> /dev/null)
|
|
78
|
+
if [ -n "$R" ]; then
|
|
79
|
+
REASON="$R"
|
|
80
|
+
break
|
|
81
|
+
fi
|
|
82
|
+
fi
|
|
83
|
+
done
|
|
84
|
+
fi
|
|
85
|
+
# Fallback: help user debug guardrail/script errors
|
|
86
|
+
if [ "$REASON" = "Policy denied this action." ]; then
|
|
87
|
+
REASON="Policy denied or guardrail error. Check passport and guardrail script (see docs/frameworks/cursor.md)."
|
|
88
|
+
fi
|
|
89
|
+
echo "{\"permission\":\"deny\",\"allowed\":false,\"agentMessage\":$(echo "$REASON" | jq -Rs .),\"reason\":$(echo "$REASON" | jq -Rs .)}"
|
|
90
|
+
exit 2
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# APort API policy evaluator — calls APort API with agent_id (cloud) or passport (local)
|
|
3
|
+
# Supports: APORT_AGENT_ID (registry lookup) or local passport file (sent in request, not stored)
|
|
4
|
+
# Usage: aport-guardrail-api.sh <tool_name> '<context_json>'
|
|
5
|
+
#
|
|
6
|
+
# Endpoint (self-hosted / private instance):
|
|
7
|
+
# export APORT_API_URL="https://api.aport.io" # default; or your self-hosted API
|
|
8
|
+
# export APORT_API_URL="https://api.aport.io" # default cloud
|
|
9
|
+
# export APORT_API_URL="https://your-private.aport.example"
|
|
10
|
+
|
|
11
|
+
set -e
|
|
12
|
+
|
|
13
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
14
|
+
# Resolve paths: config_dir/aport/ (new) or config_dir (legacy); same as bash guardrail
|
|
15
|
+
# shellcheck source=bin/aport-resolve-paths.sh
|
|
16
|
+
. "${SCRIPT_DIR}/bin/aport-resolve-paths.sh"
|
|
17
|
+
|
|
18
|
+
NODE_EVALUATOR="$SCRIPT_DIR/src/evaluator.js"
|
|
19
|
+
|
|
20
|
+
TOOL_NAME="$1"
|
|
21
|
+
DEFAULT_CONTEXT='{}'
|
|
22
|
+
CONTEXT_JSON="${2:-$DEFAULT_CONTEXT}"
|
|
23
|
+
|
|
24
|
+
# DEBUG: Print received arguments
|
|
25
|
+
if [ -n "$DEBUG_APORT" ]; then
|
|
26
|
+
echo "DEBUG: TOOL_NAME=$TOOL_NAME" >&2
|
|
27
|
+
echo "DEBUG: CONTEXT_JSON=$CONTEXT_JSON" >&2
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# Ensure APort data dir exists (for decision, audit)
|
|
31
|
+
mkdir -p "$(dirname "$AUDIT_LOG")"
|
|
32
|
+
|
|
33
|
+
# Check if Node.js is available
|
|
34
|
+
if ! command -v node &> /dev/null; then
|
|
35
|
+
echo "Error: node is required but not installed. Install with: brew install node" >&2
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# Check if evaluator exists
|
|
40
|
+
if [ ! -f "$NODE_EVALUATOR" ]; then
|
|
41
|
+
echo "Error: Node.js evaluator not found at $NODE_EVALUATOR" >&2
|
|
42
|
+
exit 1
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# Passport required only for local mode (passport in request). Cloud mode uses APORT_AGENT_ID.
|
|
46
|
+
if [ -z "$APORT_AGENT_ID" ] && [ ! -f "$PASSPORT_FILE" ]; then
|
|
47
|
+
echo "Error: Passport file not found at $PASSPORT_FILE. Create one with aport-create-passport.sh, or set APORT_AGENT_ID for cloud mode." >&2
|
|
48
|
+
exit 1
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# Map tool to policy pack ID
|
|
52
|
+
POLICY_ID=""
|
|
53
|
+
case "$TOOL_NAME" in
|
|
54
|
+
git.create_pr | git.merge | git.push | git.*)
|
|
55
|
+
POLICY_ID="code.repository.merge.v1"
|
|
56
|
+
;;
|
|
57
|
+
exec.run | exec.* | system.command.* | system.*)
|
|
58
|
+
POLICY_ID="system.command.execute.v1"
|
|
59
|
+
;;
|
|
60
|
+
message.send | message.* | messaging.*)
|
|
61
|
+
POLICY_ID="messaging.message.send.v1"
|
|
62
|
+
;;
|
|
63
|
+
mcp.tool.* | mcp.*)
|
|
64
|
+
POLICY_ID="mcp.tool.execute.v1"
|
|
65
|
+
;;
|
|
66
|
+
agent.session.* | session.create | session.*)
|
|
67
|
+
POLICY_ID="agent.session.create.v1"
|
|
68
|
+
;;
|
|
69
|
+
agent.tool.* | tool.register | tool.*)
|
|
70
|
+
POLICY_ID="agent.tool.register.v1"
|
|
71
|
+
;;
|
|
72
|
+
payment.refund | payment.* | finance.payment.refund)
|
|
73
|
+
POLICY_ID="finance.payment.refund.v1"
|
|
74
|
+
;;
|
|
75
|
+
payment.charge | finance.payment.charge)
|
|
76
|
+
POLICY_ID="finance.payment.charge.v1"
|
|
77
|
+
;;
|
|
78
|
+
database.write | database.* | data.export)
|
|
79
|
+
POLICY_ID="data.export.create.v1"
|
|
80
|
+
;;
|
|
81
|
+
*)
|
|
82
|
+
echo "Error: Tool '$TOOL_NAME' is not mapped to a policy pack" >&2
|
|
83
|
+
exit 1
|
|
84
|
+
;;
|
|
85
|
+
esac
|
|
86
|
+
|
|
87
|
+
# Call Node.js evaluator with API
|
|
88
|
+
if [ -n "$DEBUG_APORT" ]; then
|
|
89
|
+
echo "DEBUG: Calling Node.js evaluator with policy $POLICY_ID" >&2
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
# Export environment variables for evaluator (APORT_API_URL, APORT_AGENT_ID, APORT_API_KEY passed through)
|
|
93
|
+
export OPENCLAW_PASSPORT_FILE="$PASSPORT_FILE"
|
|
94
|
+
export OPENCLAW_DECISION_FILE="$DECISION_FILE"
|
|
95
|
+
|
|
96
|
+
# Call evaluator and capture exit code
|
|
97
|
+
node "$NODE_EVALUATOR" "$POLICY_ID" "$CONTEXT_JSON"
|
|
98
|
+
EXIT_CODE=$?
|
|
99
|
+
|
|
100
|
+
# Log to audit trail
|
|
101
|
+
if [ -f "$DECISION_FILE" ]; then
|
|
102
|
+
DECISION_ID=$(jq -r '.decision_id // "unknown"' "$DECISION_FILE")
|
|
103
|
+
ALLOW=$(jq -r '.allow // false' "$DECISION_FILE")
|
|
104
|
+
DENY_CODE=$(jq -r '.reasons[0].code // "unknown"' "$DECISION_FILE")
|
|
105
|
+
echo "[$(date -u +%Y-%m-%d\ %H:%M:%S)] tool=$TOOL_NAME decision_id=$DECISION_ID allow=$ALLOW policy=$POLICY_ID code=$DENY_CODE" >> "$AUDIT_LOG"
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
exit $EXIT_CODE
|