@aporthq/aport-agent-guardrails 1.0.27 → 1.0.28
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/README.md +30 -6
- package/bin/aport-claude-code-hook.sh +8 -2
- package/bin/aport-create-passport.sh +3 -2
- package/bin/aport-cursor-hook.sh +9 -3
- package/bin/aport-guardrail-api.sh +2 -0
- package/bin/aport-resolve-paths.sh +67 -40
- package/bin/frameworks/claude-code.sh +11 -1
- package/bin/frameworks/cursor.sh +6 -1
- package/bin/frameworks/generic.sh +9 -0
- package/bin/lib/framework-hook-paths.sh +96 -0
- package/bin/lib/guardrail-mode.sh +44 -3
- package/bin/lib/quick-hosted.sh +164 -0
- package/bin/openclaw +76 -45
- package/docs/HOSTED_PASSPORT_SETUP.md +29 -4
- package/docs/QUICKSTART.md +9 -3
- package/docs/README.md +3 -3
- package/docs/RELEASE.md +1 -1
- package/docs/VERIFICATION_METHODS.md +1 -1
- package/docs/frameworks/claude-code.md +25 -2
- package/docs/frameworks/cursor.md +4 -4
- package/extensions/openclaw-aport/CHANGELOG.md +2 -0
- package/extensions/openclaw-aport/openclaw.plugin.json +1 -1
- package/extensions/openclaw-aport/package-lock.json +2 -2
- package/extensions/openclaw-aport/package.json +1 -1
- package/package.json +1 -1
- package/src/evaluator.js +8 -2
package/README.md
CHANGED
|
@@ -46,17 +46,41 @@ From the live APort Vault adversarial testbed:
|
|
|
46
46
|
|
|
47
47
|
## Start Here
|
|
48
48
|
|
|
49
|
-
### Install in
|
|
49
|
+
### Install in 60 seconds
|
|
50
50
|
|
|
51
51
|
```bash
|
|
52
52
|
npx @aporthq/aport-agent-guardrails
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
- Choose your framework: `openclaw`, `cursor`, `claude-code`, `langchain`, `crewai`, `deerflow`, `n8n`
|
|
56
|
-
-
|
|
57
|
-
-
|
|
56
|
+
- Claude Code direct: `npx @aporthq/aport-agent-guardrails claude-code`
|
|
57
|
+
- Curl install URL: `curl -fsSL https://aport.io/install.sh | bash -s -- claude-code`
|
|
58
|
+
- Existing hosted passport: `npx @aporthq/aport-agent-guardrails claude-code <agent_id>`
|
|
58
59
|
- Reset a framework to a clean APort state: `npx @aporthq/aport-agent-guardrails reset claude-code --yes`
|
|
59
60
|
|
|
61
|
+
When prompted for passport setup, the choices are:
|
|
62
|
+
|
|
63
|
+
1. `Create hosted APort passport now` — recommended; creates a hosted passport and narrow setup key.
|
|
64
|
+
2. `Use existing hosted passport ID` — paste an existing `agent_id`.
|
|
65
|
+
3. `Create local passport file` — offline/local JSON passport.
|
|
66
|
+
|
|
67
|
+
For a new hosted setup, choose option `1`. The installer creates a passport, creates a narrow setup key, writes the framework hook/config, and starts sending decisions to APort. For non-interactive installs:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npx --yes @aporthq/aport-agent-guardrails claude-code \
|
|
71
|
+
--quick-hosted \
|
|
72
|
+
--email you@example.com \
|
|
73
|
+
--non-interactive
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Equivalent environment-variable form:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
APORT_OWNER_EMAIL="you@example.com" \
|
|
80
|
+
APORT_QUICK_HOSTED=1 \
|
|
81
|
+
npx --yes @aporthq/aport-agent-guardrails claude-code --non-interactive
|
|
82
|
+
```
|
|
83
|
+
|
|
60
84
|
### Why Developers and teams trust APort
|
|
61
85
|
|
|
62
86
|
- **Deterministic enforcement:** runtime hook, not prompt instructions
|
|
@@ -159,11 +183,11 @@ aport setup --framework=langchain
|
|
|
159
183
|
```
|
|
160
184
|
Then install the framework-specific Python package and follow the printed integration step for your framework.
|
|
161
185
|
|
|
162
|
-
This runs
|
|
186
|
+
This runs setup and writes config for your framework. Choose hosted setup for passport and setup-key creation, or local setup for an on-disk passport. Follow the **next steps** printed at the end (e.g. restart Cursor; or for CrewAI: by default install `aport-agent-guardrails-crewai` for released CrewAI, or opt into native-provider mode if your CrewAI build supports it).
|
|
163
187
|
|
|
164
188
|
**Guardrail mode (local vs API)** — On the **Node** installer (`npx @aporthq/aport-agent-guardrails …` / `bin/agent-guardrails`), every framework accepts the same flags: `--mode=api` (with optional `--api-url`, default `https://api.aport.io`) or `--mode=local`, and an optional hosted `ap_<hex>` argument (API mode, no local passport). That flow writes `…/aport/guardrail-mode.env` where the hooks/generic installers need it. The **Python** `aport setup` CLI does not parse those flags yet; use the Node command above for API/local mode during setup, or set mode in your framework `config.yaml` per the framework doc.
|
|
165
189
|
|
|
166
|
-
**2. Hosted passport (optional)** — If you already have an agent_id from [aport.io](https://aport.io), use it to skip
|
|
190
|
+
**2. Hosted passport (optional)** — The installer can create a hosted passport during setup. If you already have an agent_id from [aport.io](https://aport.io), use it to skip passport creation: `npx @aporthq/aport-agent-guardrails claude-code <agent_id>`. See [Hosted passport setup](docs/HOSTED_PASSPORT_SETUP.md).
|
|
167
191
|
|
|
168
192
|
**3. Test that policy runs** — After setup, the guardrail runs automatically when your agent uses tools (Cursor hook, LangChain callback, OpenClaw plugin, etc.). To try allow/deny from the command line (any framework), use the installed `aport-guardrail` command (Node) or call the evaluator from Python; both use your existing passport from the framework config dir (e.g. `~/.cursor/aport/`, `~/.aport/langchain/aport/`).
|
|
169
193
|
|
|
@@ -445,7 +469,7 @@ Use the framework-specific doc for where config and passport live and for any ex
|
|
|
445
469
|
|
|
446
470
|
| Doc | Description |
|
|
447
471
|
|-----|-------------|
|
|
448
|
-
| [QuickStart: OpenClaw Plugin](docs/QUICKSTART_OPENCLAW_PLUGIN.md) |
|
|
472
|
+
| [QuickStart: OpenClaw Plugin](docs/QUICKSTART_OPENCLAW_PLUGIN.md) | One-command OpenClaw setup |
|
|
449
473
|
| [Hosted passport setup](docs/HOSTED_PASSPORT_SETUP.md) | Use passport from aport.io — `npx ... openclaw <agent_id>` or choose hosted in wizard |
|
|
450
474
|
| [Verification methods (local vs API)](docs/VERIFICATION_METHODS.md) | Deep dive: bash vs API evaluator |
|
|
451
475
|
| [Quick Start Guide](docs/QUICKSTART.md) | Passport wizard, copy-paste option |
|
|
@@ -9,6 +9,11 @@ set -e
|
|
|
9
9
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
10
|
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
11
11
|
|
|
12
|
+
# Anchor data paths to Claude Code config before resolve (hosted/API installs may have no passport.json).
|
|
13
|
+
# shellcheck source=bin/lib/framework-hook-paths.sh
|
|
14
|
+
. "$ROOT_DIR/bin/lib/framework-hook-paths.sh"
|
|
15
|
+
aport_hook_prepare_framework_paths "claude-code" "${APORT_CLAUDE_CODE_CONFIG_DIR:-}" "$HOME/.claude"
|
|
16
|
+
|
|
12
17
|
# Path resolver: probes ~/.claude, ~/.cursor, ~/.openclaw, etc.
|
|
13
18
|
# shellcheck source=bin/aport-resolve-paths.sh
|
|
14
19
|
. "$ROOT_DIR/bin/aport-resolve-paths.sh"
|
|
@@ -16,7 +21,7 @@ ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
|
16
21
|
. "$ROOT_DIR/bin/lib/guardrail-mode.sh"
|
|
17
22
|
# shellcheck source=bin/lib/hook-read-policy.sh
|
|
18
23
|
. "$ROOT_DIR/bin/lib/hook-read-policy.sh"
|
|
19
|
-
load_guardrail_mode_for_hooks "${OPENCLAW_CONFIG_DIR:-$HOME/.claude}"
|
|
24
|
+
load_guardrail_mode_for_hooks "${APORT_CONFIG_DIR:-${OPENCLAW_CONFIG_DIR:-$HOME/.claude}}"
|
|
20
25
|
|
|
21
26
|
GUARDRAIL="$ROOT_DIR/bin/aport-guardrail-bash.sh"
|
|
22
27
|
if [ "${APORT_GUARDRAIL_MODE:-local}" = "api" ]; then
|
|
@@ -136,9 +141,10 @@ case "$TOOL_NAME_NORM" in
|
|
|
136
141
|
esac
|
|
137
142
|
|
|
138
143
|
# Use a per-invocation decision file to avoid race conditions with concurrent tool calls
|
|
139
|
-
HOOK_DECISION_FILE="${OPENCLAW_DECISION_FILE:-}"
|
|
144
|
+
HOOK_DECISION_FILE="${APORT_DECISION_FILE:-${OPENCLAW_DECISION_FILE:-}}"
|
|
140
145
|
if [ -n "$HOOK_DECISION_FILE" ]; then
|
|
141
146
|
HOOK_DECISION_FILE="${HOOK_DECISION_FILE%.json}-$$.json"
|
|
147
|
+
export APORT_DECISION_FILE="$HOOK_DECISION_FILE"
|
|
142
148
|
export OPENCLAW_DECISION_FILE="$HOOK_DECISION_FILE"
|
|
143
149
|
fi
|
|
144
150
|
|
|
@@ -45,8 +45,9 @@ fi
|
|
|
45
45
|
PASSPORT_FILE="${PASSPORT_FILE/#\~/$HOME}"
|
|
46
46
|
|
|
47
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
|
|
48
|
+
if [ -n "${APORT_CONFIG_DIR:-${OPENCLAW_CONFIG_DIR:-}}" ]; then
|
|
49
|
+
CONFIG_DIR="${APORT_CONFIG_DIR:-${OPENCLAW_CONFIG_DIR:-}}"
|
|
50
|
+
CONFIG_DIR="${CONFIG_DIR/#\~/$HOME}"
|
|
50
51
|
else
|
|
51
52
|
CONFIG_DIR="$(dirname "$PASSPORT_FILE")"
|
|
52
53
|
case "$PASSPORT_FILE" in
|
package/bin/aport-cursor-hook.sh
CHANGED
|
@@ -13,6 +13,11 @@ set -e
|
|
|
13
13
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
14
14
|
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
15
15
|
|
|
16
|
+
# Anchor data paths to Cursor config before resolve (hosted/API installs may have no passport.json).
|
|
17
|
+
# shellcheck source=bin/lib/framework-hook-paths.sh
|
|
18
|
+
. "$ROOT_DIR/bin/lib/framework-hook-paths.sh"
|
|
19
|
+
aport_hook_prepare_framework_paths "cursor" "${APORT_CURSOR_CONFIG_DIR:-}" "$HOME/.cursor"
|
|
20
|
+
|
|
16
21
|
# Passport/config: resolver probes ~/.cursor, ~/.openclaw, ~/.aport/*, etc.
|
|
17
22
|
# shellcheck source=bin/aport-resolve-paths.sh
|
|
18
23
|
. "$ROOT_DIR/bin/aport-resolve-paths.sh"
|
|
@@ -20,7 +25,7 @@ ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
|
20
25
|
. "$ROOT_DIR/bin/lib/guardrail-mode.sh"
|
|
21
26
|
# shellcheck source=bin/lib/hook-read-policy.sh
|
|
22
27
|
. "$ROOT_DIR/bin/lib/hook-read-policy.sh"
|
|
23
|
-
load_guardrail_mode_for_hooks "${OPENCLAW_CONFIG_DIR:-$HOME/.cursor}"
|
|
28
|
+
load_guardrail_mode_for_hooks "${APORT_CONFIG_DIR:-${OPENCLAW_CONFIG_DIR:-$HOME/.cursor}}"
|
|
24
29
|
|
|
25
30
|
GUARDRAIL="$ROOT_DIR/bin/aport-guardrail-bash.sh"
|
|
26
31
|
if [ "${APORT_GUARDRAIL_MODE:-local}" = "api" ]; then
|
|
@@ -164,9 +169,10 @@ else
|
|
|
164
169
|
fi
|
|
165
170
|
|
|
166
171
|
# Use a per-invocation decision file to avoid race conditions with concurrent tool calls
|
|
167
|
-
HOOK_DECISION_FILE="${OPENCLAW_DECISION_FILE:-}"
|
|
172
|
+
HOOK_DECISION_FILE="${APORT_DECISION_FILE:-${OPENCLAW_DECISION_FILE:-}}"
|
|
168
173
|
if [ -n "$HOOK_DECISION_FILE" ]; then
|
|
169
174
|
HOOK_DECISION_FILE="${HOOK_DECISION_FILE%.json}-$$.json"
|
|
175
|
+
export APORT_DECISION_FILE="$HOOK_DECISION_FILE"
|
|
170
176
|
export OPENCLAW_DECISION_FILE="$HOOK_DECISION_FILE"
|
|
171
177
|
fi
|
|
172
178
|
|
|
@@ -202,7 +208,7 @@ if [ -n "$HOOK_DECISION_FILE" ] && [ -f "$HOOK_DECISION_FILE" ]; then
|
|
|
202
208
|
fi
|
|
203
209
|
# Fallback: try common config dirs
|
|
204
210
|
if [ "$REASON" = "Policy denied this action." ]; then
|
|
205
|
-
for DEC in "${OPENCLAW_CONFIG_DIR:-$HOME/.cursor}/aport/decision.json" "$HOME/.cursor/aport/decision.json" "$HOME/.openclaw/aport/decision.json"; do
|
|
211
|
+
for DEC in "${APORT_CONFIG_DIR:-${OPENCLAW_CONFIG_DIR:-$HOME/.cursor}}/aport/decision.json" "$HOME/.cursor/aport/decision.json" "$HOME/.openclaw/aport/decision.json"; do
|
|
206
212
|
if [ -f "$DEC" ]; then
|
|
207
213
|
R="$(jq -r '.reasons[0].message // empty' "$DEC" 2> /dev/null)"
|
|
208
214
|
[ -n "$R" ] && REASON="$R" && break
|
|
@@ -63,6 +63,8 @@ if [ -n "$DEBUG_APORT" ]; then
|
|
|
63
63
|
fi
|
|
64
64
|
|
|
65
65
|
# Export environment variables for evaluator (APORT_API_URL, APORT_AGENT_ID, APORT_API_KEY passed through)
|
|
66
|
+
export APORT_PASSPORT_FILE="$PASSPORT_FILE"
|
|
67
|
+
export APORT_DECISION_FILE="$DECISION_FILE"
|
|
66
68
|
export OPENCLAW_PASSPORT_FILE="$PASSPORT_FILE"
|
|
67
69
|
export OPENCLAW_DECISION_FILE="$DECISION_FILE"
|
|
68
70
|
|
|
@@ -5,14 +5,19 @@
|
|
|
5
5
|
# - aport-guardrail-api.sh
|
|
6
6
|
# - aport-status.sh
|
|
7
7
|
# (aport-create-passport.sh uses --output; wrappers set env so children get resolved paths.)
|
|
8
|
-
# Sets
|
|
9
|
-
#
|
|
8
|
+
# Sets canonical APORT_* path variables plus legacy OPENCLAW_* aliases for compatibility.
|
|
9
|
+
# Also sets PASSPORT_FILE, DECISION_FILE, AUDIT_LOG. Passport status (active|suspended|revoked)
|
|
10
|
+
# is source of truth for suspend; no separate file.
|
|
10
11
|
# Caller must ensure APORT_DATA_DIR exists before writing (e.g. mkdir -p "$(dirname "$AUDIT_LOG")").
|
|
11
12
|
|
|
12
13
|
resolve_aport_paths() {
|
|
13
14
|
local config_dir
|
|
14
15
|
local passport_path
|
|
15
16
|
local data_dir
|
|
17
|
+
local explicit_passport="${APORT_PASSPORT_FILE:-${OPENCLAW_PASSPORT_FILE:-}}"
|
|
18
|
+
local explicit_decision="${APORT_DECISION_FILE:-${OPENCLAW_DECISION_FILE:-}}"
|
|
19
|
+
local explicit_audit="${APORT_AUDIT_LOG:-${OPENCLAW_AUDIT_LOG:-}}"
|
|
20
|
+
local explicit_config="${APORT_CONFIG_DIR:-${OPENCLAW_CONFIG_DIR:-}}"
|
|
16
21
|
|
|
17
22
|
# Source validation if available (for validate_passport_path)
|
|
18
23
|
local _resolve_script_dir
|
|
@@ -24,9 +29,14 @@ resolve_aport_paths() {
|
|
|
24
29
|
. "$_resolve_script_dir/../bin/lib/validation.sh" 2> /dev/null || true
|
|
25
30
|
fi
|
|
26
31
|
|
|
32
|
+
[ -n "$explicit_passport" ] && explicit_passport="${explicit_passport/#\~/$HOME}"
|
|
33
|
+
[ -n "$explicit_decision" ] && explicit_decision="${explicit_decision/#\~/$HOME}"
|
|
34
|
+
[ -n "$explicit_audit" ] && explicit_audit="${explicit_audit/#\~/$HOME}"
|
|
35
|
+
[ -n "$explicit_config" ] && explicit_config="${explicit_config/#\~/$HOME}"
|
|
36
|
+
|
|
27
37
|
# 0) AGENTS.md enforcement block (repo-scoped, highest priority after explicit env)
|
|
28
|
-
# Only checked when no explicit env var is set
|
|
29
|
-
if [ -z "$
|
|
38
|
+
# Only checked when no explicit passport env var is set.
|
|
39
|
+
if [ -z "$explicit_passport" ]; then
|
|
30
40
|
local _agentsmd_lib="$_resolve_script_dir/lib/agentsmd.sh"
|
|
31
41
|
[ ! -f "$_agentsmd_lib" ] && _agentsmd_lib="$_resolve_script_dir/../bin/lib/agentsmd.sh"
|
|
32
42
|
if [ -f "$_agentsmd_lib" ]; then
|
|
@@ -39,53 +49,61 @@ resolve_aport_paths() {
|
|
|
39
49
|
if [ -n "$AGENTSMD_PASSPORT" ] && [ -f "$AGENTSMD_PASSPORT" ]; then
|
|
40
50
|
passport_path="$AGENTSMD_PASSPORT"
|
|
41
51
|
data_dir="$(dirname "$AGENTSMD_PASSPORT")"
|
|
52
|
+
explicit_decision="${explicit_decision:-${data_dir}/decision.json}"
|
|
53
|
+
explicit_audit="${explicit_audit:-${data_dir}/audit.log}"
|
|
54
|
+
|
|
55
|
+
export APORT_PASSPORT_FILE="$passport_path"
|
|
42
56
|
export OPENCLAW_PASSPORT_FILE="$passport_path"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
export OPENCLAW_AUDIT_LOG="$
|
|
57
|
+
export APORT_DECISION_FILE="$explicit_decision"
|
|
58
|
+
export OPENCLAW_DECISION_FILE="$explicit_decision"
|
|
59
|
+
export APORT_AUDIT_LOG="$explicit_audit"
|
|
60
|
+
export OPENCLAW_AUDIT_LOG="$explicit_audit"
|
|
61
|
+
|
|
47
62
|
PASSPORT_FILE="$passport_path"
|
|
48
|
-
DECISION_FILE="$
|
|
49
|
-
AUDIT_LOG="$
|
|
63
|
+
DECISION_FILE="$explicit_decision"
|
|
64
|
+
AUDIT_LOG="$explicit_audit"
|
|
50
65
|
return 0
|
|
51
66
|
fi
|
|
52
67
|
fi
|
|
53
68
|
fi
|
|
54
69
|
fi
|
|
55
70
|
|
|
56
|
-
# 1) Explicit path set and file exists
|
|
57
|
-
if [ -n "$
|
|
71
|
+
# 1) Explicit path set and file exists -> use it (plugin or wrapper)
|
|
72
|
+
if [ -n "$explicit_passport" ] && [ -f "$explicit_passport" ]; then
|
|
58
73
|
# Validate env-provided path if validator is available
|
|
59
74
|
if type validate_explicit_passport_path &> /dev/null; then
|
|
60
|
-
if ! validate_explicit_passport_path "$
|
|
61
|
-
echo "[aport] WARN:
|
|
75
|
+
if ! validate_explicit_passport_path "$explicit_passport"; then
|
|
76
|
+
echo "[aport] WARN: APORT_PASSPORT_FILE path failed validation: $explicit_passport" >&2
|
|
62
77
|
fi
|
|
63
78
|
fi
|
|
64
|
-
data_dir="$(dirname "$
|
|
65
|
-
passport_path="$
|
|
66
|
-
# 2) Explicit path set but file missing
|
|
67
|
-
elif [ -n "$
|
|
68
|
-
config_dir="$(cd "$(dirname "$
|
|
79
|
+
data_dir="$(dirname "$explicit_passport")"
|
|
80
|
+
passport_path="$explicit_passport"
|
|
81
|
+
# 2) Explicit path set but file missing -> legacy: try parent dir (e.g. .../openclaw/passport.json)
|
|
82
|
+
elif [ -n "$explicit_passport" ]; then
|
|
83
|
+
config_dir="$(cd "$(dirname "$explicit_passport")/.." 2> /dev/null && pwd)"
|
|
69
84
|
if [ -f "${config_dir}/passport.json" ]; then
|
|
70
85
|
passport_path="${config_dir}/passport.json"
|
|
71
86
|
data_dir="$config_dir"
|
|
72
87
|
else
|
|
73
|
-
passport_path="$
|
|
74
|
-
data_dir="$(dirname "$
|
|
88
|
+
passport_path="$explicit_passport"
|
|
89
|
+
data_dir="$(dirname "$explicit_passport")"
|
|
75
90
|
fi
|
|
76
|
-
# 3)
|
|
91
|
+
# 3) Probe framework paths, or honor APORT_CONFIG_DIR / OPENCLAW_CONFIG_DIR set by framework hooks.
|
|
77
92
|
else
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
93
|
+
if [ -n "$explicit_config" ]; then
|
|
94
|
+
config_dir="$explicit_config"
|
|
95
|
+
else
|
|
96
|
+
config_dir=""
|
|
97
|
+
for candidate in "$HOME/.claude" "$HOME/.cursor" "$HOME/.openclaw" "$HOME/.aport/langchain" "$HOME/.aport/crewai" "$HOME/.aport/deerflow" "$HOME/.n8n"; do
|
|
98
|
+
if [ -f "${candidate}/aport/passport.json" ]; then
|
|
99
|
+
config_dir="$candidate"
|
|
100
|
+
break
|
|
101
|
+
fi
|
|
102
|
+
done
|
|
103
|
+
if [ -z "$config_dir" ]; then
|
|
104
|
+
config_dir="$HOME/.openclaw"
|
|
83
105
|
fi
|
|
84
|
-
done
|
|
85
|
-
if [ -z "$config_dir" ]; then
|
|
86
|
-
config_dir="${OPENCLAW_CONFIG_DIR:-$HOME/.openclaw}"
|
|
87
106
|
fi
|
|
88
|
-
config_dir="${config_dir/#\~/$HOME}"
|
|
89
107
|
if [ -f "${config_dir}/aport/passport.json" ]; then
|
|
90
108
|
passport_path="${config_dir}/aport/passport.json"
|
|
91
109
|
data_dir="${config_dir}/aport"
|
|
@@ -98,19 +116,28 @@ resolve_aport_paths() {
|
|
|
98
116
|
fi
|
|
99
117
|
fi
|
|
100
118
|
|
|
119
|
+
export APORT_PASSPORT_FILE="$passport_path"
|
|
101
120
|
export OPENCLAW_PASSPORT_FILE="$passport_path"
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
121
|
+
|
|
122
|
+
# Preserve explicitly set decision/audit paths (e.g. tests); otherwise use data_dir.
|
|
123
|
+
if [ -n "$explicit_decision" ]; then
|
|
124
|
+
export APORT_DECISION_FILE="$explicit_decision"
|
|
125
|
+
else
|
|
126
|
+
export APORT_DECISION_FILE="${data_dir}/decision.json"
|
|
127
|
+
fi
|
|
128
|
+
export OPENCLAW_DECISION_FILE="$APORT_DECISION_FILE"
|
|
129
|
+
|
|
130
|
+
if [ -n "$explicit_audit" ]; then
|
|
131
|
+
export APORT_AUDIT_LOG="$explicit_audit"
|
|
105
132
|
else
|
|
106
|
-
export
|
|
133
|
+
export APORT_AUDIT_LOG="${data_dir}/audit.log"
|
|
107
134
|
fi
|
|
108
|
-
export OPENCLAW_AUDIT_LOG="$
|
|
135
|
+
export OPENCLAW_AUDIT_LOG="$APORT_AUDIT_LOG"
|
|
109
136
|
|
|
110
|
-
PASSPORT_FILE="$
|
|
111
|
-
DECISION_FILE="$
|
|
112
|
-
AUDIT_LOG="$
|
|
137
|
+
PASSPORT_FILE="$APORT_PASSPORT_FILE"
|
|
138
|
+
DECISION_FILE="$APORT_DECISION_FILE"
|
|
139
|
+
AUDIT_LOG="$APORT_AUDIT_LOG"
|
|
113
140
|
}
|
|
114
141
|
|
|
115
|
-
# When sourced, resolve immediately so callers just use the vars
|
|
142
|
+
# When sourced, resolve immediately so callers just use the vars.
|
|
116
143
|
resolve_aport_paths
|
|
@@ -14,6 +14,8 @@ source "$LIB/config.sh"
|
|
|
14
14
|
source "$LIB/framework-setup.sh"
|
|
15
15
|
# shellcheck source=../lib/guardrail-mode.sh
|
|
16
16
|
source "$LIB/guardrail-mode.sh"
|
|
17
|
+
# shellcheck source=../lib/quick-hosted.sh
|
|
18
|
+
source "$LIB/quick-hosted.sh"
|
|
17
19
|
|
|
18
20
|
run_setup() {
|
|
19
21
|
parse_guardrail_mode_args "$@"
|
|
@@ -28,6 +30,9 @@ run_setup() {
|
|
|
28
30
|
hosted_agent_id="$APORT_HOSTED_AGENT_ID_CLI"
|
|
29
31
|
export APORT_AGENT_ID="$hosted_agent_id"
|
|
30
32
|
log_info "Using hosted passport (agent_id: $hosted_agent_id) — skipping wizard."
|
|
33
|
+
elif aport_maybe_configure_hosted_passport "claude-code" "$config_dir"; then
|
|
34
|
+
hosted_agent_id="$APORT_AGENT_ID"
|
|
35
|
+
log_info "Using hosted passport (agent_id: $hosted_agent_id) — skipping wizard."
|
|
31
36
|
else
|
|
32
37
|
# Check AGENTS.md for enforcement config — skip wizard if already configured
|
|
33
38
|
# shellcheck source=../lib/agentsmd.sh
|
|
@@ -65,6 +70,11 @@ run_setup() {
|
|
|
65
70
|
_write_claude_settings "$SETTINGS_FILE" "$HOOK_SCRIPT"
|
|
66
71
|
chmod 600 "$SETTINGS_FILE"
|
|
67
72
|
|
|
73
|
+
# Audit log is appended by hooks; create empty file so the advertised path exists after install.
|
|
74
|
+
mkdir -p "$config_dir/aport"
|
|
75
|
+
: >> "$config_dir/aport/audit.log"
|
|
76
|
+
chmod 600 "$config_dir/aport/audit.log" 2> /dev/null || true
|
|
77
|
+
|
|
68
78
|
echo ""
|
|
69
79
|
echo " Next steps (Claude Code):"
|
|
70
80
|
echo " ─────────────────────────"
|
|
@@ -78,7 +88,7 @@ run_setup() {
|
|
|
78
88
|
echo " 5. Restart Claude Code so the PreToolUse hook is picked up."
|
|
79
89
|
echo " 6. Tool use will be checked by APort policy (exit 2 = block)."
|
|
80
90
|
echo ""
|
|
81
|
-
echo " Audit log: $config_dir/aport/audit.log"
|
|
91
|
+
echo " Audit log: $config_dir/aport/audit.log (entries added on each policy check)"
|
|
82
92
|
echo ""
|
|
83
93
|
echo " Note: claude --dangerously-skip-permissions bypasses ALL hooks including APort."
|
|
84
94
|
echo " See: docs/frameworks/claude-code.md"
|
package/bin/frameworks/cursor.sh
CHANGED
|
@@ -14,6 +14,8 @@ source "$LIB/config.sh"
|
|
|
14
14
|
source "$LIB/framework-setup.sh"
|
|
15
15
|
# shellcheck source=../lib/guardrail-mode.sh
|
|
16
16
|
source "$LIB/guardrail-mode.sh"
|
|
17
|
+
# shellcheck source=../lib/quick-hosted.sh
|
|
18
|
+
source "$LIB/quick-hosted.sh"
|
|
17
19
|
|
|
18
20
|
run_setup() {
|
|
19
21
|
parse_guardrail_mode_args "$@"
|
|
@@ -29,6 +31,9 @@ run_setup() {
|
|
|
29
31
|
hosted_agent_id="$APORT_HOSTED_AGENT_ID_CLI"
|
|
30
32
|
export APORT_AGENT_ID="$hosted_agent_id"
|
|
31
33
|
log_info "Using hosted passport (agent_id: $hosted_agent_id) — skipping wizard."
|
|
34
|
+
elif aport_maybe_configure_hosted_passport "cursor" "$config_dir"; then
|
|
35
|
+
hosted_agent_id="$APORT_AGENT_ID"
|
|
36
|
+
log_info "Using hosted passport (agent_id: $hosted_agent_id) — skipping wizard."
|
|
32
37
|
else
|
|
33
38
|
# Check AGENTS.md for enforcement config — skip wizard if already configured
|
|
34
39
|
# shellcheck source=../lib/agentsmd.sh
|
|
@@ -102,7 +107,7 @@ run_setup() {
|
|
|
102
107
|
echo " 5. Restart Cursor (or reload window) so hooks are picked up."
|
|
103
108
|
echo " 6. Shell commands and tool use will be checked by APort policy (exit 2 = block)."
|
|
104
109
|
echo ""
|
|
105
|
-
echo "
|
|
110
|
+
echo " For other frameworks like Claude Code, use the dedicated integration: docs/frameworks"
|
|
106
111
|
echo ""
|
|
107
112
|
}
|
|
108
113
|
|
|
@@ -19,6 +19,8 @@ source "$LIB/runtime.sh"
|
|
|
19
19
|
source "$LIB/config.sh"
|
|
20
20
|
# shellcheck source=../lib/guardrail-mode.sh
|
|
21
21
|
source "$LIB/guardrail-mode.sh"
|
|
22
|
+
# shellcheck source=../lib/quick-hosted.sh
|
|
23
|
+
source "$LIB/quick-hosted.sh"
|
|
22
24
|
|
|
23
25
|
framework="${APORT_FRAMEWORK:?APORT_FRAMEWORK must be set by the dispatcher}"
|
|
24
26
|
crewai_integration_mode="${APORT_CREWAI_INTEGRATION_MODE:-compat}"
|
|
@@ -134,6 +136,9 @@ persist_python_framework_config() {
|
|
|
134
136
|
|
|
135
137
|
if [[ "$selected_mode" == "api" ]]; then
|
|
136
138
|
_config_replace_or_append "$config_file" "api_url" "$(_yaml_quote "${selected_api_url:-$DEFAULT_APORT_API_URL}")"
|
|
139
|
+
if [[ -n "${APORT_API_KEY:-}" ]]; then
|
|
140
|
+
_config_replace_or_append "$config_file" "api_key" "$(_yaml_quote "$APORT_API_KEY")"
|
|
141
|
+
fi
|
|
137
142
|
if [[ -n "$hosted_id" ]]; then
|
|
138
143
|
_config_replace_or_append "$config_file" "agent_id" "$(_yaml_quote "$hosted_id")"
|
|
139
144
|
_config_remove_key "$config_file" "passport_path"
|
|
@@ -146,6 +151,7 @@ persist_python_framework_config() {
|
|
|
146
151
|
_config_replace_or_append "$config_file" "passport_path" "$(_yaml_quote "$local_passport_path")"
|
|
147
152
|
fi
|
|
148
153
|
_config_remove_key "$config_file" "agent_id"
|
|
154
|
+
_config_remove_key "$config_file" "api_key"
|
|
149
155
|
fi
|
|
150
156
|
|
|
151
157
|
chmod 600 "$config_file" 2> /dev/null || true
|
|
@@ -166,6 +172,9 @@ run_setup() {
|
|
|
166
172
|
fi
|
|
167
173
|
if [[ -n "$hosted_agent_id" ]]; then
|
|
168
174
|
log_info "Using hosted passport (agent_id: $hosted_agent_id) — skipping wizard."
|
|
175
|
+
elif aport_maybe_configure_hosted_passport "$framework" "$config_dir"; then
|
|
176
|
+
hosted_agent_id="$APORT_AGENT_ID"
|
|
177
|
+
log_info "Using hosted passport (agent_id: $hosted_agent_id) — skipping wizard."
|
|
169
178
|
elif ((${#FORWARD_ARGS[@]} > 0)); then
|
|
170
179
|
run_passport_wizard "${FORWARD_ARGS[@]}"
|
|
171
180
|
else
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Framework hook path normalization.
|
|
3
|
+
# GUI tools can inherit stale legacy OPENCLAW_* variables from another framework
|
|
4
|
+
# session. Hooks should default to their own framework config unless the caller
|
|
5
|
+
# explicitly opts into an external passport path.
|
|
6
|
+
|
|
7
|
+
aport_hook_expand_path() {
|
|
8
|
+
local input="$1"
|
|
9
|
+
printf '%s' "${input/#\~/$HOME}"
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
aport_hook_known_config_owner() {
|
|
13
|
+
local config_dir
|
|
14
|
+
config_dir="$(aport_hook_expand_path "$1")"
|
|
15
|
+
|
|
16
|
+
case "$config_dir" in
|
|
17
|
+
"$HOME/.claude" | "$HOME/.claude/"*) echo "claude-code" ;;
|
|
18
|
+
"$HOME/.cursor" | "$HOME/.cursor/"*) echo "cursor" ;;
|
|
19
|
+
"$HOME/.openclaw" | "$HOME/.openclaw/"*) echo "openclaw" ;;
|
|
20
|
+
"$HOME/.aport/langchain" | "$HOME/.aport/langchain/"*) echo "langchain" ;;
|
|
21
|
+
"$HOME/.aport/crewai" | "$HOME/.aport/crewai/"*) echo "crewai" ;;
|
|
22
|
+
"$HOME/.aport/deerflow" | "$HOME/.aport/deerflow/"*) echo "deerflow" ;;
|
|
23
|
+
"$HOME/.n8n" | "$HOME/.n8n/"*) echo "n8n" ;;
|
|
24
|
+
*) echo "" ;;
|
|
25
|
+
esac
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
aport_hook_prepare_framework_paths() {
|
|
29
|
+
local framework="$1"
|
|
30
|
+
local framework_config_dir="${2:-}"
|
|
31
|
+
local default_config_dir="$3"
|
|
32
|
+
local selected_config_dir owner passport_path decision_path audit_path
|
|
33
|
+
|
|
34
|
+
if [ -n "$framework_config_dir" ]; then
|
|
35
|
+
selected_config_dir="$framework_config_dir"
|
|
36
|
+
else
|
|
37
|
+
selected_config_dir="${APORT_CONFIG_DIR:-${OPENCLAW_CONFIG_DIR:-}}"
|
|
38
|
+
owner="$(aport_hook_known_config_owner "$selected_config_dir")"
|
|
39
|
+
if [ -n "$owner" ] && [ "$owner" != "$framework" ]; then
|
|
40
|
+
selected_config_dir="$default_config_dir"
|
|
41
|
+
fi
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
selected_config_dir="${selected_config_dir:-$default_config_dir}"
|
|
45
|
+
selected_config_dir="$(aport_hook_expand_path "$selected_config_dir")"
|
|
46
|
+
export APORT_CONFIG_DIR="$selected_config_dir"
|
|
47
|
+
export OPENCLAW_CONFIG_DIR="$selected_config_dir"
|
|
48
|
+
|
|
49
|
+
if [ -n "${APORT_PASSPORT_FILE:-${OPENCLAW_PASSPORT_FILE:-}}" ] && [ -z "${APORT_ALLOW_EXTERNAL_PASSPORT_FILE:-}" ]; then
|
|
50
|
+
passport_path="$(aport_hook_expand_path "${APORT_PASSPORT_FILE:-${OPENCLAW_PASSPORT_FILE:-}}")"
|
|
51
|
+
case "$passport_path" in
|
|
52
|
+
"$APORT_CONFIG_DIR"/*)
|
|
53
|
+
export APORT_PASSPORT_FILE="$passport_path"
|
|
54
|
+
export OPENCLAW_PASSPORT_FILE="$passport_path"
|
|
55
|
+
;;
|
|
56
|
+
*)
|
|
57
|
+
unset APORT_PASSPORT_FILE
|
|
58
|
+
unset OPENCLAW_PASSPORT_FILE
|
|
59
|
+
;;
|
|
60
|
+
esac
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
if [ -n "${APORT_ALLOW_EXTERNAL_PASSPORT_FILE:-}" ] && [ -n "${APORT_PASSPORT_FILE:-${OPENCLAW_PASSPORT_FILE:-}}" ]; then
|
|
64
|
+
passport_path="$(aport_hook_expand_path "${APORT_PASSPORT_FILE:-${OPENCLAW_PASSPORT_FILE:-}}")"
|
|
65
|
+
export APORT_PASSPORT_FILE="$passport_path"
|
|
66
|
+
export OPENCLAW_PASSPORT_FILE="$passport_path"
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
if [ -n "${APORT_DECISION_FILE:-${OPENCLAW_DECISION_FILE:-}}" ]; then
|
|
70
|
+
decision_path="$(aport_hook_expand_path "${APORT_DECISION_FILE:-${OPENCLAW_DECISION_FILE:-}}")"
|
|
71
|
+
case "$decision_path" in
|
|
72
|
+
"$APORT_CONFIG_DIR"/*)
|
|
73
|
+
export APORT_DECISION_FILE="$decision_path"
|
|
74
|
+
export OPENCLAW_DECISION_FILE="$decision_path"
|
|
75
|
+
;;
|
|
76
|
+
*)
|
|
77
|
+
unset APORT_DECISION_FILE
|
|
78
|
+
unset OPENCLAW_DECISION_FILE
|
|
79
|
+
;;
|
|
80
|
+
esac
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
if [ -n "${APORT_AUDIT_LOG:-${OPENCLAW_AUDIT_LOG:-}}" ]; then
|
|
84
|
+
audit_path="$(aport_hook_expand_path "${APORT_AUDIT_LOG:-${OPENCLAW_AUDIT_LOG:-}}")"
|
|
85
|
+
case "$audit_path" in
|
|
86
|
+
"$APORT_CONFIG_DIR"/*)
|
|
87
|
+
export APORT_AUDIT_LOG="$audit_path"
|
|
88
|
+
export OPENCLAW_AUDIT_LOG="$audit_path"
|
|
89
|
+
;;
|
|
90
|
+
*)
|
|
91
|
+
unset APORT_AUDIT_LOG
|
|
92
|
+
unset OPENCLAW_AUDIT_LOG
|
|
93
|
+
;;
|
|
94
|
+
esac
|
|
95
|
+
fi
|
|
96
|
+
}
|
|
@@ -9,6 +9,9 @@ parse_guardrail_mode_args() {
|
|
|
9
9
|
APORT_GUARDRAIL_MODE_CLI="${APORT_GUARDRAIL_MODE_CLI:-}"
|
|
10
10
|
APORT_GUARDRAIL_API_URL_CLI="${APORT_GUARDRAIL_API_URL_CLI:-}"
|
|
11
11
|
APORT_HOSTED_AGENT_ID_CLI="${APORT_HOSTED_AGENT_ID_CLI:-}"
|
|
12
|
+
APORT_QUICK_HOSTED_CLI="${APORT_QUICK_HOSTED_CLI:-}"
|
|
13
|
+
APORT_OWNER_EMAIL_CLI="${APORT_OWNER_EMAIL_CLI:-}"
|
|
14
|
+
APORT_ISSUE_URL_CLI="${APORT_ISSUE_URL_CLI:-}"
|
|
12
15
|
APORT_FRAMEWORK_ARGS=()
|
|
13
16
|
|
|
14
17
|
while [[ $# -gt 0 ]]; do
|
|
@@ -35,6 +38,34 @@ parse_guardrail_mode_args() {
|
|
|
35
38
|
APORT_GUARDRAIL_API_URL_CLI="$2"
|
|
36
39
|
shift
|
|
37
40
|
;;
|
|
41
|
+
--quick-hosted | --hosted)
|
|
42
|
+
APORT_QUICK_HOSTED_CLI="1"
|
|
43
|
+
;;
|
|
44
|
+
--non-interactive | --noninteractive)
|
|
45
|
+
export APORT_NONINTERACTIVE=1
|
|
46
|
+
;;
|
|
47
|
+
--email=* | --owner-email=*)
|
|
48
|
+
APORT_OWNER_EMAIL_CLI="${1#*=}"
|
|
49
|
+
;;
|
|
50
|
+
--email | --owner-email)
|
|
51
|
+
if [[ -z "${2:-}" ]]; then
|
|
52
|
+
echo "[aport] ERROR: $1 requires an email value" >&2
|
|
53
|
+
return 1
|
|
54
|
+
fi
|
|
55
|
+
APORT_OWNER_EMAIL_CLI="$2"
|
|
56
|
+
shift
|
|
57
|
+
;;
|
|
58
|
+
--issue-url=*)
|
|
59
|
+
APORT_ISSUE_URL_CLI="${1#*=}"
|
|
60
|
+
;;
|
|
61
|
+
--issue-url)
|
|
62
|
+
if [[ -z "${2:-}" ]]; then
|
|
63
|
+
echo "[aport] ERROR: --issue-url requires a value" >&2
|
|
64
|
+
return 1
|
|
65
|
+
fi
|
|
66
|
+
APORT_ISSUE_URL_CLI="$2"
|
|
67
|
+
shift
|
|
68
|
+
;;
|
|
38
69
|
ap_[a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9])
|
|
39
70
|
APORT_HOSTED_AGENT_ID_CLI="$1"
|
|
40
71
|
;;
|
|
@@ -46,9 +77,16 @@ parse_guardrail_mode_args() {
|
|
|
46
77
|
done
|
|
47
78
|
|
|
48
79
|
export APORT_GUARDRAIL_MODE_CLI APORT_GUARDRAIL_API_URL_CLI APORT_HOSTED_AGENT_ID_CLI
|
|
80
|
+
export APORT_QUICK_HOSTED_CLI APORT_OWNER_EMAIL_CLI APORT_ISSUE_URL_CLI
|
|
49
81
|
return 0
|
|
50
82
|
}
|
|
51
83
|
|
|
84
|
+
write_env_assignment() {
|
|
85
|
+
local key="$1"
|
|
86
|
+
local value="$2"
|
|
87
|
+
printf '%s=%q\n' "$key" "$value"
|
|
88
|
+
}
|
|
89
|
+
|
|
52
90
|
select_guardrail_mode() {
|
|
53
91
|
local framework="$1"
|
|
54
92
|
local hosted_agent_id="${2:-}"
|
|
@@ -132,12 +170,15 @@ write_guardrail_mode_file() {
|
|
|
132
170
|
mkdir -p "$aport_dir"
|
|
133
171
|
|
|
134
172
|
{
|
|
135
|
-
|
|
173
|
+
write_env_assignment "APORT_GUARDRAIL_MODE" "$mode"
|
|
136
174
|
if [[ "$mode" = "api" ]]; then
|
|
137
|
-
|
|
175
|
+
write_env_assignment "APORT_API_URL" "${api_url:-$DEFAULT_APORT_API_URL}"
|
|
138
176
|
fi
|
|
139
177
|
if [[ -n "$hosted_agent_id" ]]; then
|
|
140
|
-
|
|
178
|
+
write_env_assignment "APORT_AGENT_ID" "$hosted_agent_id"
|
|
179
|
+
fi
|
|
180
|
+
if [[ -n "${APORT_API_KEY:-}" ]]; then
|
|
181
|
+
write_env_assignment "APORT_API_KEY" "$APORT_API_KEY"
|
|
141
182
|
fi
|
|
142
183
|
} > "$mode_file"
|
|
143
184
|
chmod 600 "$mode_file" 2> /dev/null || true
|