@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,394 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# APort built-in local policy evaluator (bash, no API)
|
|
3
|
+
# Evaluates OAP v1.0 policies locally without any network call
|
|
4
|
+
# Usage: aport-guardrail-bash.sh <tool_name> '<context_json>'
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
9
|
+
# Resolve paths: config_dir/aport/ (new) or config_dir (legacy)
|
|
10
|
+
# shellcheck source=bin/aport-resolve-paths.sh
|
|
11
|
+
. "${SCRIPT_DIR}/bin/aport-resolve-paths.sh"
|
|
12
|
+
|
|
13
|
+
# Source validation library for input sanitization
|
|
14
|
+
# shellcheck source=bin/lib/validation.sh
|
|
15
|
+
. "${SCRIPT_DIR}/bin/lib/validation.sh"
|
|
16
|
+
|
|
17
|
+
# Get script directory to find submodules (external/ per GIT_SUBMODULES_EXPLAINED.md)
|
|
18
|
+
POLICIES_DIR="$SCRIPT_DIR/external/aport-policies"
|
|
19
|
+
LOCAL_POLICIES_DIR="$SCRIPT_DIR/local-overrides/policies"
|
|
20
|
+
|
|
21
|
+
TOOL_NAME="$1"
|
|
22
|
+
# Default empty object via variable to avoid bash parsing ${2:-{}} as ${2:-{ + literal }
|
|
23
|
+
DEFAULT_CONTEXT='{}'
|
|
24
|
+
CONTEXT_JSON="${2:-$DEFAULT_CONTEXT}"
|
|
25
|
+
|
|
26
|
+
# SECURITY: Validate tool name to prevent injection attacks
|
|
27
|
+
if ! validate_tool_name "$TOOL_NAME"; then
|
|
28
|
+
echo '{"allow":false,"reasons":[{"code":"oap.invalid_tool_name","message":"Tool name contains invalid characters or format"}]}' > "$DECISION_FILE" 2> /dev/null || true
|
|
29
|
+
exit 1
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# SECURITY: Validate JSON context size to prevent DoS
|
|
33
|
+
if ! validate_json_size "$CONTEXT_JSON"; then
|
|
34
|
+
echo '{"allow":false,"reasons":[{"code":"oap.context_too_large","message":"Context JSON exceeds maximum size"}]}' > "$DECISION_FILE" 2> /dev/null || true
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# DEBUG: Print received arguments (sanitized to prevent leaking sensitive data)
|
|
39
|
+
if [ -n "$DEBUG_APORT" ]; then
|
|
40
|
+
echo "DEBUG: TOOL_NAME=$TOOL_NAME" >&2
|
|
41
|
+
# SECURITY: Sanitize context JSON to prevent logging sensitive data
|
|
42
|
+
SANITIZED_CONTEXT=$(sanitize_log_value "$CONTEXT_JSON" "context")
|
|
43
|
+
echo "DEBUG: CONTEXT_JSON=$SANITIZED_CONTEXT" >&2
|
|
44
|
+
echo "DEBUG: CONTEXT length=${#CONTEXT_JSON}" >&2
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# Ensure APort data dir exists (for decision.json, audit.log)
|
|
48
|
+
mkdir -p "$(dirname "$AUDIT_LOG")"
|
|
49
|
+
|
|
50
|
+
# Function to load policy from upstream or local-overrides
|
|
51
|
+
load_policy() {
|
|
52
|
+
local policy_base="$1"
|
|
53
|
+
local policy_file=""
|
|
54
|
+
|
|
55
|
+
# Try official policy from submodule first (with .v1, .v2, etc)
|
|
56
|
+
for version_dir in "$POLICIES_DIR/${policy_base}".v*/; do
|
|
57
|
+
if [ -f "${version_dir}policy.json" ]; then
|
|
58
|
+
policy_file="${version_dir}policy.json"
|
|
59
|
+
break
|
|
60
|
+
fi
|
|
61
|
+
done
|
|
62
|
+
|
|
63
|
+
# Fallback to local overrides
|
|
64
|
+
if [ -z "$policy_file" ] || [ ! -f "$policy_file" ]; then
|
|
65
|
+
for local_file in "$LOCAL_POLICIES_DIR/${policy_base}".v*.json; do
|
|
66
|
+
if [ -f "$local_file" ]; then
|
|
67
|
+
policy_file="$local_file"
|
|
68
|
+
break
|
|
69
|
+
fi
|
|
70
|
+
done
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
if [ -n "$policy_file" ] && [ -f "$policy_file" ]; then
|
|
74
|
+
cat "$policy_file"
|
|
75
|
+
else
|
|
76
|
+
echo "{}"
|
|
77
|
+
fi
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# Function to compute JCS-canonicalized SHA-256 digest
|
|
81
|
+
compute_passport_digest() {
|
|
82
|
+
local passport_file="$1"
|
|
83
|
+
echo "sha256:$(jq --sort-keys -c . "$passport_file" | shasum -a 256 | awk '{print $1}')"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# Function to build OAP v1.0 compliant decision and exit.
|
|
87
|
+
# Adds content_hash (tamper-resistant) and optional chain (prev_decision_id, prev_content_hash).
|
|
88
|
+
# If a decision file is edited or the chain is reordered, content_hash verification fails.
|
|
89
|
+
write_decision() {
|
|
90
|
+
local allow="$1"
|
|
91
|
+
local policy_id="${2:-unknown}"
|
|
92
|
+
local deny_code="${3:-oap.policy_error}"
|
|
93
|
+
local deny_message="${4:-Policy evaluation failed}"
|
|
94
|
+
|
|
95
|
+
local decision_id=$(uuidgen 2> /dev/null || echo "local-$(date +%s)")
|
|
96
|
+
local passport_id=$(jq -r '.passport_id // "unknown"' "$PASSPORT_FILE")
|
|
97
|
+
local owner_id=$(jq -r '.owner_id // "unknown"' "$PASSPORT_FILE")
|
|
98
|
+
local assurance_level=$(jq -r '.assurance_level // "L0"' "$PASSPORT_FILE")
|
|
99
|
+
local passport_digest=$(compute_passport_digest "$PASSPORT_FILE")
|
|
100
|
+
local issued_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
101
|
+
local expires_at=$(date -u -v+1H +%Y-%m-%dT%H:%M:%SZ 2> /dev/null || date -u -d '+1 hour' +%Y-%m-%dT%H:%M:%SZ)
|
|
102
|
+
|
|
103
|
+
# Build reasons array per OAP v1.0 spec
|
|
104
|
+
local reasons
|
|
105
|
+
if [ "$allow" = "true" ]; then
|
|
106
|
+
reasons='[{"code": "oap.allowed", "message": "All policy checks passed"}]'
|
|
107
|
+
else
|
|
108
|
+
reasons="[{\"code\": \"$deny_code\", \"message\": \"$deny_message\"}]"
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
# Chain state: last decision id and hash for tamper-resistant chain
|
|
112
|
+
local decisions_dir
|
|
113
|
+
decisions_dir="$(dirname "$DECISION_FILE")"
|
|
114
|
+
local chain_state="$decisions_dir/.chain-state.json"
|
|
115
|
+
local prev_decision_id=""
|
|
116
|
+
local prev_content_hash=""
|
|
117
|
+
if [ -f "$chain_state" ]; then
|
|
118
|
+
prev_decision_id=$(jq -r '.last_decision_id // ""' "$chain_state" 2> /dev/null || true)
|
|
119
|
+
prev_content_hash=$(jq -r '.last_content_hash // ""' "$chain_state" 2> /dev/null || true)
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
# Build base decision JSON (no content_hash yet)
|
|
123
|
+
local base_json
|
|
124
|
+
base_json=$(jq -n -c --sort-keys \
|
|
125
|
+
--arg decision_id "$decision_id" \
|
|
126
|
+
--arg policy_id "$policy_id" \
|
|
127
|
+
--arg passport_id "$passport_id" \
|
|
128
|
+
--arg owner_id "$owner_id" \
|
|
129
|
+
--arg assurance_level "$assurance_level" \
|
|
130
|
+
--argjson allow "$allow" \
|
|
131
|
+
--argjson reasons "$reasons" \
|
|
132
|
+
--arg issued_at "$issued_at" \
|
|
133
|
+
--arg expires_at "$expires_at" \
|
|
134
|
+
--arg passport_digest "$passport_digest" \
|
|
135
|
+
--arg prev_decision_id "$prev_decision_id" \
|
|
136
|
+
--arg prev_content_hash "$prev_content_hash" \
|
|
137
|
+
'{
|
|
138
|
+
decision_id: $decision_id,
|
|
139
|
+
policy_id: $policy_id,
|
|
140
|
+
passport_id: $passport_id,
|
|
141
|
+
owner_id: $owner_id,
|
|
142
|
+
assurance_level: $assurance_level,
|
|
143
|
+
allow: $allow,
|
|
144
|
+
reasons: $reasons,
|
|
145
|
+
issued_at: $issued_at,
|
|
146
|
+
expires_at: $expires_at,
|
|
147
|
+
passport_digest: $passport_digest,
|
|
148
|
+
signature: "ed25519:local-unsigned",
|
|
149
|
+
kid: "oap:local:dev-key",
|
|
150
|
+
prev_decision_id: (if $prev_decision_id == "" then null else $prev_decision_id end),
|
|
151
|
+
prev_content_hash: (if $prev_content_hash == "" then null else $prev_content_hash end)
|
|
152
|
+
}')
|
|
153
|
+
|
|
154
|
+
# Content hash over canonical form (without content_hash field) — tamper-resistant
|
|
155
|
+
local content_hash
|
|
156
|
+
content_hash="sha256:$(printf '%s' "$base_json" | shasum -a 256 | awk '{print $1}')"
|
|
157
|
+
|
|
158
|
+
# Add content_hash and write final decision (critical path — plugin reads this)
|
|
159
|
+
local final_json
|
|
160
|
+
final_json=$(echo "$base_json" | jq -c --arg h "$content_hash" '. + {content_hash: $h}')
|
|
161
|
+
echo "$final_json" > "$DECISION_FILE"
|
|
162
|
+
|
|
163
|
+
# Update chain state for next decision (best-effort; do not block or fail the script)
|
|
164
|
+
echo "{\"last_decision_id\":\"$decision_id\",\"last_content_hash\":\"$content_hash\"}" > "$chain_state" 2> /dev/null || true
|
|
165
|
+
|
|
166
|
+
# Audit trail is non-core: append in background so it never blocks the tool call.
|
|
167
|
+
# Include capability context (command, recipient, repo/branch) when set.
|
|
168
|
+
audit_context=""
|
|
169
|
+
if [ -n "${CONTEXT_SUMMARY:-}" ]; then
|
|
170
|
+
audit_context=" context=\"${CONTEXT_SUMMARY}\""
|
|
171
|
+
fi
|
|
172
|
+
(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_context}" >> "$AUDIT_LOG") 2> /dev/null &
|
|
173
|
+
|
|
174
|
+
if [ "$allow" = "true" ]; then
|
|
175
|
+
exit 0
|
|
176
|
+
else
|
|
177
|
+
exit 1
|
|
178
|
+
fi
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
# Check if jq is available
|
|
182
|
+
if ! command -v jq &> /dev/null; then
|
|
183
|
+
echo "Error: jq is required but not installed. Install with: brew install jq" >&2
|
|
184
|
+
write_decision false "unknown" "oap.missing_dependency" "jq not found"
|
|
185
|
+
fi
|
|
186
|
+
|
|
187
|
+
# Load passport (source of truth; status checked first below)
|
|
188
|
+
if [ ! -f "$PASSPORT_FILE" ]; then
|
|
189
|
+
write_decision false "unknown" "oap.passport_not_found" "Passport file not found at $PASSPORT_FILE. Create one with aport-create-passport.sh"
|
|
190
|
+
fi
|
|
191
|
+
|
|
192
|
+
PASSPORT=$(cat "$PASSPORT_FILE")
|
|
193
|
+
|
|
194
|
+
# Validate passport JSON
|
|
195
|
+
if ! echo "$PASSPORT" | jq . > /dev/null 2>&1; then
|
|
196
|
+
write_decision false "unknown" "oap.passport_invalid" "Passport file contains invalid JSON"
|
|
197
|
+
fi
|
|
198
|
+
|
|
199
|
+
# Check passport status first (kill switch = suspended/revoked; passport is source of truth per OAP spec)
|
|
200
|
+
STATUS=$(echo "$PASSPORT" | jq -r '.status // "unknown"')
|
|
201
|
+
if [ "$STATUS" != "active" ]; then
|
|
202
|
+
write_decision false "unknown" "oap.passport_suspended" "Passport status is '$STATUS', not 'active'. Agent suspended."
|
|
203
|
+
fi
|
|
204
|
+
|
|
205
|
+
# Check spec version
|
|
206
|
+
SPEC_VERSION=$(echo "$PASSPORT" | jq -r '.spec_version // "unknown"')
|
|
207
|
+
if [ "$SPEC_VERSION" != "oap/1.0" ]; then
|
|
208
|
+
write_decision false "unknown" "oap.passport_version_mismatch" "Passport spec version is '$SPEC_VERSION', expected 'oap/1.0'"
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
# Map tool to policy pack ID
|
|
212
|
+
POLICY_ID=""
|
|
213
|
+
case "$TOOL_NAME" in
|
|
214
|
+
git.create_pr | git.merge | git.push)
|
|
215
|
+
POLICY_ID="code.repository.merge.v1"
|
|
216
|
+
;;
|
|
217
|
+
exec.run | exec.* | system.*)
|
|
218
|
+
POLICY_ID="system.command.execute.v1"
|
|
219
|
+
;;
|
|
220
|
+
message.send | message.* | messaging.*)
|
|
221
|
+
POLICY_ID="messaging.message.send.v1"
|
|
222
|
+
;;
|
|
223
|
+
payment.* | finance.*)
|
|
224
|
+
POLICY_ID="finance.payment.refund.v1"
|
|
225
|
+
;;
|
|
226
|
+
database.write | database.insert | database.update | database.delete | data.export)
|
|
227
|
+
POLICY_ID="data.export.v1"
|
|
228
|
+
;;
|
|
229
|
+
*)
|
|
230
|
+
# Unknown tool - deny by default for security
|
|
231
|
+
write_decision false "unknown" "oap.unknown_capability" "Tool '$TOOL_NAME' is not mapped to a policy pack"
|
|
232
|
+
;;
|
|
233
|
+
esac
|
|
234
|
+
|
|
235
|
+
# Capability-specific context summary for audit log (command, recipient, repo/branch, etc.)
|
|
236
|
+
CONTEXT_SUMMARY=""
|
|
237
|
+
if [ -n "$CONTEXT_JSON" ] && [ "$CONTEXT_JSON" != "{}" ]; then
|
|
238
|
+
if [[ "$POLICY_ID" == "system.command.execute"* ]]; then
|
|
239
|
+
CONTEXT_SUMMARY=$(echo "$CONTEXT_JSON" | jq -r '.command // .cmd // .args[0] // ""' 2> /dev/null || true)
|
|
240
|
+
elif [[ "$POLICY_ID" == "messaging.message.send"* ]]; then
|
|
241
|
+
CONTEXT_SUMMARY=$(echo "$CONTEXT_JSON" | jq -r '.recipient // .to // ""' 2> /dev/null || true)
|
|
242
|
+
elif [[ "$POLICY_ID" == "code.repository.merge"* ]]; then
|
|
243
|
+
REPO=$(echo "$CONTEXT_JSON" | jq -r '.repo // .repository // ""' 2> /dev/null || true)
|
|
244
|
+
BRANCH=$(echo "$CONTEXT_JSON" | jq -r '.branch // ""' 2> /dev/null || true)
|
|
245
|
+
[ -n "$REPO" ] && CONTEXT_SUMMARY="$REPO"
|
|
246
|
+
[ -n "$BRANCH" ] && CONTEXT_SUMMARY="${CONTEXT_SUMMARY:+$CONTEXT_SUMMARY }$BRANCH"
|
|
247
|
+
fi
|
|
248
|
+
# Sanitize for one-line audit: no newlines, truncate, escape double quotes
|
|
249
|
+
if [ -n "$CONTEXT_SUMMARY" ]; then
|
|
250
|
+
CONTEXT_SUMMARY=$(printf '%s' "$CONTEXT_SUMMARY" | tr '\n' ' ' | head -c 120 | sed 's/"/\\"/g')
|
|
251
|
+
fi
|
|
252
|
+
fi
|
|
253
|
+
|
|
254
|
+
# Load policy definition
|
|
255
|
+
POLICY_DEF=$(load_policy "$(echo "$POLICY_ID" | sed 's/\.v[0-9]*$//')")
|
|
256
|
+
|
|
257
|
+
# Check if all required capabilities exist in passport
|
|
258
|
+
REQUIRED_CAPS=$(echo "$POLICY_DEF" | jq -r '.requires_capabilities[]? // empty')
|
|
259
|
+
PASSPORT_CAPS=$(echo "$PASSPORT" | jq -r '.capabilities[]?.id // empty')
|
|
260
|
+
|
|
261
|
+
# If policy has required capabilities, check them all
|
|
262
|
+
# (Alias: policy "messaging.send" is satisfied by passport "messaging.message.send")
|
|
263
|
+
if [ -n "$REQUIRED_CAPS" ]; then
|
|
264
|
+
for req_cap in $REQUIRED_CAPS; do
|
|
265
|
+
HAS_CAP=false
|
|
266
|
+
for passport_cap in $PASSPORT_CAPS; do
|
|
267
|
+
if [ "$passport_cap" = "$req_cap" ]; then
|
|
268
|
+
HAS_CAP=true
|
|
269
|
+
break
|
|
270
|
+
fi
|
|
271
|
+
if [ "$req_cap" = "messaging.send" ] && [ "$passport_cap" = "messaging.message.send" ]; then
|
|
272
|
+
HAS_CAP=true
|
|
273
|
+
break
|
|
274
|
+
fi
|
|
275
|
+
done
|
|
276
|
+
if [ "$HAS_CAP" = false ]; then
|
|
277
|
+
write_decision false "$POLICY_ID" "oap.unknown_capability" "Passport does not have required capability '$req_cap' for policy '$POLICY_ID'"
|
|
278
|
+
fi
|
|
279
|
+
done
|
|
280
|
+
fi
|
|
281
|
+
|
|
282
|
+
# Get policy limits from passport
|
|
283
|
+
POLICY_BASE=$(echo "$POLICY_ID" | sed 's/\.v[0-9]*$//')
|
|
284
|
+
# Messaging: API/verifier use flat keys at limits top level; accept nested limits["messaging.message.send"] or flat
|
|
285
|
+
if [[ "$POLICY_ID" == "messaging.message.send"* ]]; then
|
|
286
|
+
LIMITS=$(echo "$PASSPORT" | jq '.limits | if .["messaging.message.send"] then .["messaging.message.send"] else {msgs_per_min, msgs_per_day, allowed_recipients, approval_required} end')
|
|
287
|
+
else
|
|
288
|
+
LIMITS=$(echo "$PASSPORT" | jq ".limits.\"$POLICY_BASE\" // {}")
|
|
289
|
+
fi
|
|
290
|
+
|
|
291
|
+
# Evaluate policy-specific limits
|
|
292
|
+
if [[ "$POLICY_ID" == "code.repository.merge"* ]]; then
|
|
293
|
+
FILES_CHANGED=$(echo "$CONTEXT_JSON" | jq -r '.files_changed // .files // 0')
|
|
294
|
+
MAX_FILES=$(echo "$LIMITS" | jq -r '.max_pr_size_kb // 500')
|
|
295
|
+
|
|
296
|
+
if [ "$FILES_CHANGED" -gt "$MAX_FILES" ]; then
|
|
297
|
+
write_decision false "$POLICY_ID" "oap.limit_exceeded" "PR size $FILES_CHANGED exceeds limit of $MAX_FILES files"
|
|
298
|
+
fi
|
|
299
|
+
|
|
300
|
+
# Check allowed repos (use while-read to avoid glob expansion when pattern is *)
|
|
301
|
+
REPO=$(echo "$CONTEXT_JSON" | jq -r '.repo // .repository // ""')
|
|
302
|
+
if [ -n "$REPO" ]; then
|
|
303
|
+
REPO_ALLOWED=false
|
|
304
|
+
while IFS= read -r pattern; do
|
|
305
|
+
[ -z "$pattern" ] && continue
|
|
306
|
+
if [[ "$REPO" == "$pattern" ]] || [[ "$REPO" == */$pattern ]] || [[ "$pattern" == "*" ]]; then
|
|
307
|
+
REPO_ALLOWED=true
|
|
308
|
+
break
|
|
309
|
+
fi
|
|
310
|
+
done < <(echo "$LIMITS" | jq -r '.allowed_repos[]? // empty')
|
|
311
|
+
HAS_ALLOWED_REPOS=$(echo "$LIMITS" | jq -r '.allowed_repos | length')
|
|
312
|
+
if [ "$REPO_ALLOWED" = false ] && [ "$HAS_ALLOWED_REPOS" -gt 0 ] 2> /dev/null; then
|
|
313
|
+
write_decision false "$POLICY_ID" "oap.repo_not_allowed" "Repository '$REPO' is not in allowed list"
|
|
314
|
+
fi
|
|
315
|
+
fi
|
|
316
|
+
|
|
317
|
+
# Check allowed branches (use while-read to avoid glob expansion when pattern is *)
|
|
318
|
+
BRANCH=$(echo "$CONTEXT_JSON" | jq -r '.branch // ""')
|
|
319
|
+
if [ -n "$BRANCH" ]; then
|
|
320
|
+
BRANCH_ALLOWED=false
|
|
321
|
+
while IFS= read -r pattern; do
|
|
322
|
+
[ -z "$pattern" ] && continue
|
|
323
|
+
if [[ "$BRANCH" == "$pattern" ]] || [[ "$pattern" == "*" ]]; then
|
|
324
|
+
BRANCH_ALLOWED=true
|
|
325
|
+
break
|
|
326
|
+
fi
|
|
327
|
+
done < <(echo "$LIMITS" | jq -r '.allowed_base_branches[]? // empty')
|
|
328
|
+
HAS_ALLOWED_BRANCHES=$(echo "$LIMITS" | jq -r '.allowed_base_branches | length')
|
|
329
|
+
if [ "$BRANCH_ALLOWED" = false ] && [ "$HAS_ALLOWED_BRANCHES" -gt 0 ] 2> /dev/null; then
|
|
330
|
+
write_decision false "$POLICY_ID" "oap.branch_not_allowed" "Branch '$BRANCH' is not in allowed list"
|
|
331
|
+
fi
|
|
332
|
+
fi
|
|
333
|
+
fi
|
|
334
|
+
|
|
335
|
+
if [[ "$POLICY_ID" == "system.command.execute"* ]]; then
|
|
336
|
+
COMMAND=$(echo "$CONTEXT_JSON" | jq -r '.command // .cmd // ""')
|
|
337
|
+
if [ -z "$COMMAND" ]; then
|
|
338
|
+
# Try to extract from args
|
|
339
|
+
COMMAND=$(echo "$CONTEXT_JSON" | jq -r '.args[0] // ""')
|
|
340
|
+
fi
|
|
341
|
+
|
|
342
|
+
if [ -n "$COMMAND" ]; then
|
|
343
|
+
# SECURITY: Validate command doesn't contain injection characters
|
|
344
|
+
if ! validate_command_string "$COMMAND"; then
|
|
345
|
+
write_decision false "$POLICY_ID" "oap.command_injection_detected" "Command contains potentially dangerous characters"
|
|
346
|
+
fi
|
|
347
|
+
|
|
348
|
+
# Check allowed commands using safe prefix matching
|
|
349
|
+
COMMAND_ALLOWED=false
|
|
350
|
+
while IFS= read -r allowed_cmd; do
|
|
351
|
+
[ -z "$allowed_cmd" ] && continue
|
|
352
|
+
# Use safe_prefix_match instead of bash glob patterns
|
|
353
|
+
if safe_prefix_match "$COMMAND" "$allowed_cmd"; then
|
|
354
|
+
COMMAND_ALLOWED=true
|
|
355
|
+
break
|
|
356
|
+
fi
|
|
357
|
+
done < <(echo "$LIMITS" | jq -r '.allowed_commands[]? // empty')
|
|
358
|
+
HAS_ALLOWED=$(echo "$LIMITS" | jq -r '.allowed_commands | length')
|
|
359
|
+
if [ "$COMMAND_ALLOWED" = false ] && [ "${HAS_ALLOWED:-0}" -gt 0 ] 2> /dev/null; then
|
|
360
|
+
write_decision false "$POLICY_ID" "oap.command_not_allowed" "Command '$COMMAND' is not in allowed list"
|
|
361
|
+
fi
|
|
362
|
+
|
|
363
|
+
# Check blocked patterns using safe pattern matching
|
|
364
|
+
while IFS= read -r pattern; do
|
|
365
|
+
[ -z "$pattern" ] && continue
|
|
366
|
+
# Use safe_pattern_match instead of bash glob patterns
|
|
367
|
+
if safe_pattern_match "$COMMAND" "$pattern"; then
|
|
368
|
+
write_decision false "$POLICY_ID" "oap.blocked_pattern" "Command contains blocked pattern: $pattern"
|
|
369
|
+
break
|
|
370
|
+
fi
|
|
371
|
+
done < <(echo "$LIMITS" | jq -r '.blocked_patterns[]? // empty')
|
|
372
|
+
fi
|
|
373
|
+
fi
|
|
374
|
+
|
|
375
|
+
if [[ "$POLICY_ID" == "messaging.message.send"* ]]; then
|
|
376
|
+
RECIPIENT=$(echo "$CONTEXT_JSON" | jq -r '.recipient // .to // ""')
|
|
377
|
+
if [ -n "$RECIPIENT" ]; then
|
|
378
|
+
RECIPIENT_ALLOWED=false
|
|
379
|
+
while IFS= read -r allowed; do
|
|
380
|
+
[ -z "$allowed" ] && continue
|
|
381
|
+
if [ "$RECIPIENT" = "$allowed" ] || [ "$allowed" = "*" ]; then
|
|
382
|
+
RECIPIENT_ALLOWED=true
|
|
383
|
+
break
|
|
384
|
+
fi
|
|
385
|
+
done < <(echo "$LIMITS" | jq -r '.allowed_recipients[]? // empty')
|
|
386
|
+
HAS_ALLOWED=$(echo "$LIMITS" | jq -r '.allowed_recipients | length')
|
|
387
|
+
if [ "$RECIPIENT_ALLOWED" = false ] && [ "${HAS_ALLOWED:-0}" -gt 0 ] 2> /dev/null; then
|
|
388
|
+
write_decision false "$POLICY_ID" "oap.recipient_not_allowed" "Recipient '$RECIPIENT' is not in allowed list"
|
|
389
|
+
fi
|
|
390
|
+
fi
|
|
391
|
+
fi
|
|
392
|
+
|
|
393
|
+
# All checks passed - allow
|
|
394
|
+
write_decision true "$POLICY_ID" "oap.allowed" "All policy checks passed"
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Resolve APort data paths: prefer config_dir/aport/, fallback to config_dir (legacy).
|
|
3
|
+
# MUST be sourced by any script that reads/writes passport, decision, or audit:
|
|
4
|
+
# - aport-guardrail-bash.sh
|
|
5
|
+
# - aport-guardrail-api.sh
|
|
6
|
+
# - aport-status.sh
|
|
7
|
+
# (aport-create-passport.sh uses --output; wrappers set env so children get resolved paths.)
|
|
8
|
+
# Sets: OPENCLAW_PASSPORT_FILE, OPENCLAW_DECISION_FILE, OPENCLAW_AUDIT_LOG
|
|
9
|
+
# and PASSPORT_FILE, DECISION_FILE, AUDIT_LOG. Passport status (active|suspended|revoked) is source of truth for suspend; no separate file.
|
|
10
|
+
# Caller must ensure APORT_DATA_DIR exists before writing (e.g. mkdir -p "$(dirname "$AUDIT_LOG")").
|
|
11
|
+
|
|
12
|
+
resolve_aport_paths() {
|
|
13
|
+
local config_dir
|
|
14
|
+
local passport_path
|
|
15
|
+
local data_dir
|
|
16
|
+
|
|
17
|
+
# 1) Explicit path set and file exists → use it (plugin or wrapper)
|
|
18
|
+
if [ -n "${OPENCLAW_PASSPORT_FILE:-}" ] && [ -f "$OPENCLAW_PASSPORT_FILE" ]; then
|
|
19
|
+
data_dir="$(dirname "$OPENCLAW_PASSPORT_FILE")"
|
|
20
|
+
passport_path="$OPENCLAW_PASSPORT_FILE"
|
|
21
|
+
# 2) Explicit path set but file missing → legacy: try parent dir (e.g. .../openclaw/passport.json)
|
|
22
|
+
elif [ -n "${OPENCLAW_PASSPORT_FILE:-}" ]; then
|
|
23
|
+
config_dir="$(cd "$(dirname "$OPENCLAW_PASSPORT_FILE")/.." 2> /dev/null && pwd)"
|
|
24
|
+
if [ -f "${config_dir}/passport.json" ]; then
|
|
25
|
+
passport_path="${config_dir}/passport.json"
|
|
26
|
+
data_dir="$config_dir"
|
|
27
|
+
else
|
|
28
|
+
passport_path="$OPENCLAW_PASSPORT_FILE"
|
|
29
|
+
data_dir="$(dirname "$OPENCLAW_PASSPORT_FILE")"
|
|
30
|
+
fi
|
|
31
|
+
# 3) No env → probe framework-specific default paths (where each framework stores data), then OpenClaw legacy
|
|
32
|
+
else
|
|
33
|
+
config_dir=""
|
|
34
|
+
for candidate in "$HOME/.cursor" "$HOME/.openclaw" "$HOME/.aport/langchain" "$HOME/.aport/crewai" "$HOME/.n8n"; do
|
|
35
|
+
if [ -f "${candidate}/aport/passport.json" ]; then
|
|
36
|
+
config_dir="$candidate"
|
|
37
|
+
break
|
|
38
|
+
fi
|
|
39
|
+
done
|
|
40
|
+
if [ -z "$config_dir" ]; then
|
|
41
|
+
config_dir="${OPENCLAW_CONFIG_DIR:-$HOME/.openclaw}"
|
|
42
|
+
fi
|
|
43
|
+
config_dir="${config_dir/#\~/$HOME}"
|
|
44
|
+
if [ -f "${config_dir}/aport/passport.json" ]; then
|
|
45
|
+
passport_path="${config_dir}/aport/passport.json"
|
|
46
|
+
data_dir="${config_dir}/aport"
|
|
47
|
+
elif [ -f "${config_dir}/passport.json" ]; then
|
|
48
|
+
passport_path="${config_dir}/passport.json"
|
|
49
|
+
data_dir="$config_dir"
|
|
50
|
+
else
|
|
51
|
+
passport_path="${config_dir}/aport/passport.json"
|
|
52
|
+
data_dir="${config_dir}/aport"
|
|
53
|
+
fi
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
export OPENCLAW_PASSPORT_FILE="$passport_path"
|
|
57
|
+
# Preserve explicitly set decision path (e.g. tests set OPENCLAW_DECISION_FILE); otherwise use data_dir
|
|
58
|
+
if [ -n "${OPENCLAW_DECISION_FILE:-}" ]; then
|
|
59
|
+
export OPENCLAW_DECISION_FILE="$OPENCLAW_DECISION_FILE"
|
|
60
|
+
else
|
|
61
|
+
export OPENCLAW_DECISION_FILE="${data_dir}/decision.json"
|
|
62
|
+
fi
|
|
63
|
+
export OPENCLAW_AUDIT_LOG="${data_dir}/audit.log"
|
|
64
|
+
|
|
65
|
+
PASSPORT_FILE="$OPENCLAW_PASSPORT_FILE"
|
|
66
|
+
DECISION_FILE="$OPENCLAW_DECISION_FILE"
|
|
67
|
+
AUDIT_LOG="$OPENCLAW_AUDIT_LOG"
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# When sourced, resolve immediately so callers just use the vars
|
|
71
|
+
resolve_aport_paths
|