@aporthq/aport-agent-guardrails 1.0.20 β 1.0.21
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 +14 -13
- package/bin/agent-guardrails +57 -17
- package/bin/aport-create-passport.sh +1 -1
- package/bin/aport-guardrail-api.sh +8 -35
- package/bin/aport-guardrail-bash.sh +18 -53
- package/bin/aport-resolve-paths.sh +2 -2
- package/bin/frameworks/generic.sh +59 -3
- package/bin/frameworks/next-steps.d/crewai-native.txt +21 -0
- package/bin/frameworks/next-steps.d/crewai.txt +7 -2
- package/bin/lib/error.sh +3 -3
- package/bin/lib/runtime-manifest.txt +17 -0
- package/bin/lib/runtime.sh +81 -0
- package/bin/lib/tool-mapping.sh +52 -0
- package/bin/lib/validation.sh +25 -15
- package/docs/FRAMEWORK_ROADMAP.md +1 -1
- package/docs/PROVIDER.md +1 -1
- package/docs/RELEASE.md +3 -3
- package/docs/development/ERROR_CODES.md +3 -3
- package/docs/frameworks/crewai.md +102 -68
- package/docs/frameworks/deerflow.md +1 -1
- package/extensions/openclaw-aport/CHANGELOG.md +5 -1
- 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 +3 -2
package/README.md
CHANGED
|
@@ -89,7 +89,7 @@ The security concern is that agent tools and skills can execute sensitive action
|
|
|
89
89
|
|
|
90
90
|
## π Supported frameworks
|
|
91
91
|
|
|
92
|
-
**APort Agent Guardrail** adapters are available per framework; the same passport and policies apply. **Node users:** `npx @aporthq/aport-agent-guardrails` (then choose framework) or `npx @aporthq/aport-agent-guardrails <framework>`. **Python users (LangChain/CrewAI/DeerFlow):** run the same CLI for the wizard and config, then install the
|
|
92
|
+
**APort Agent Guardrail** adapters and providers are available per framework; the same passport and policies apply. **Node users:** `npx @aporthq/aport-agent-guardrails` (then choose framework) or `npx @aporthq/aport-agent-guardrails <framework>`. **Python users (LangChain/CrewAI/DeerFlow):** run the same CLI for the wizard and config, then install the Python package shown in the framework doc.
|
|
93
93
|
|
|
94
94
|
**Two ways to use APort:** (1) **Guardrails (CLI/setup)** β run the installer to create your passport and config; (2) **Core (library)** β use the `OAPGuardrailProvider` ([docs/PROVIDER.md](docs/PROVIDER.md)) in your app so each tool call is verified. One provider per language (Python + TypeScript), works with any framework. Framework docs: [OpenClaw](docs/frameworks/openclaw.md), [Cursor](docs/frameworks/cursor.md), [Claude Code](docs/frameworks/claude-code.md), [LangChain](docs/frameworks/langchain.md), [CrewAI](docs/frameworks/crewai.md), [DeerFlow](docs/frameworks/deerflow.md), [n8n](docs/frameworks/n8n.md).
|
|
95
95
|
|
|
@@ -101,11 +101,12 @@ The security concern is that agent tools and skills can execute sensitive action
|
|
|
101
101
|
| **Cursor** | [docs/frameworks/cursor.md](docs/frameworks/cursor.md) | `beforeShellExecution` / `preToolUse` hooks β writes `~/.cursor/hooks.json`. **Runtime enforcement is the bash hook;** the Node package `@aporthq/aport-agent-guardrails-cursor` is a helper only (Evaluator, `getHookPath()`). | `npx @aporthq/aport-agent-guardrails cursor` |
|
|
102
102
|
| **Claude Code** | [docs/frameworks/claude-code.md](docs/frameworks/claude-code.md) | PreToolUse hook β writes `~/.claude/settings.json` (Claude Code format; not Cursor). | `npx @aporthq/aport-agent-guardrails claude-code` |
|
|
103
103
|
| **LangChain / LangGraph** | [docs/frameworks/langchain.md](docs/frameworks/langchain.md) | **Python:** `APortCallback` (`on_tool_start`) | `npx @aporthq/aport-agent-guardrails langchain` then `pip install aport-agent-guardrails-langchain` + `aport-langchain setup` |
|
|
104
|
-
| **CrewAI** | [docs/frameworks/crewai.md](docs/frameworks/crewai.md) | **Python:**
|
|
104
|
+
| **CrewAI** | [docs/frameworks/crewai.md](docs/frameworks/crewai.md) | **Python:** released hook adapter by default; native `GuardrailProvider` mode for CrewAI builds with native provider support | `npx @aporthq/aport-agent-guardrails crewai` then `pip install aport-agent-guardrails-crewai` + `aport-crewai setup` |
|
|
105
105
|
| **DeerFlow** | [docs/frameworks/deerflow.md](docs/frameworks/deerflow.md) | **Python:** generic OAP provider wiring in DeerFlow config | `npx @aporthq/aport-agent-guardrails deerflow` then follow printed `uv`/config steps |
|
|
106
106
|
| **n8n** | [docs/frameworks/n8n.md](docs/frameworks/n8n.md) | *Coming soon* β custom node and runtime in progress | β |
|
|
107
|
+
| **VoltAgent** | [VoltAgent PR #1171](https://github.com/VoltAgent/voltagent/pull/1171) | *In progress* β pluggable `GuardrailProvider` interface landing upstream in `@voltagent/core` | β |
|
|
107
108
|
|
|
108
|
-
Install via `npx @aporthq/aport-agent-guardrails <framework>` (or choose when prompted). OpenClaw can also use the full installer flow. **For LangChain and
|
|
109
|
+
Install via `npx @aporthq/aport-agent-guardrails <framework>` (or choose when prompted). OpenClaw can also use the full installer flow. **For LangChain, CrewAI, and DeerFlow, the CLI writes config and installs the local runtime into the framework config directory; then install the Python package and wire the provider/callback shown in the framework doc.** **Python** packages are on PyPI; **Node** packages are on npm (same version as the CLI).
|
|
109
110
|
|
|
110
111
|
**Passport path:** Each framework has its own **default** passport path (where that framework stores data): e.g. Cursor β `~/.cursor/aport/passport.json`, OpenClaw β `~/.openclaw/aport/passport.json`, LangChain β `~/.aport/langchain/aport/passport.json`. The passport wizardβs **first question** is βPassport file path [default]:β β press Enter for the framework default or type a different path. In non-interactive mode (e.g. CI) use **`--output /path/to/passport.json`** to choose the path. Roadmap: [docs/FRAMEWORK_ROADMAP.md](docs/FRAMEWORK_ROADMAP.md).
|
|
111
112
|
|
|
@@ -125,15 +126,15 @@ npx @aporthq/aport-agent-guardrails
|
|
|
125
126
|
# or: npx @aporthq/aport-agent-guardrails openclaw | cursor | claude-code | langchain | crewai | deerflow | n8n
|
|
126
127
|
```
|
|
127
128
|
|
|
128
|
-
**Python (LangChain or
|
|
129
|
+
**Python (LangChain, CrewAI, or DeerFlow):** Run the wizard via the Node command above, then install the Python package and follow the printed next steps. Or use the Python CLI to see the exact commands:
|
|
129
130
|
```bash
|
|
130
131
|
pip install aport-agent-guardrails
|
|
131
|
-
aport setup --framework=langchain
|
|
132
|
-
# or --framework=crewai
|
|
132
|
+
aport setup --framework=langchain
|
|
133
|
+
# or --framework=crewai / deerflow
|
|
133
134
|
```
|
|
134
|
-
Then run the printed `npx` command to create passport and config, and the printed
|
|
135
|
+
Then run the printed `npx` command to create passport and config, and the printed Python integration step for your framework.
|
|
135
136
|
|
|
136
|
-
This runs the **passport wizard** and writes config for your framework. Follow the **next steps** printed at the end (e.g. restart Cursor; or for
|
|
137
|
+
This runs the **passport wizard** and writes config for your framework. 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).
|
|
137
138
|
|
|
138
139
|
**2. Hosted passport (optional)** β If you already have an agent_id from [aport.io](https://aport.io), use it to skip the wizard: `npx @aporthq/aport-agent-guardrails openclaw <agent_id>`. See [Hosted passport setup](docs/HOSTED_PASSPORT_SETUP.md).
|
|
139
140
|
|
|
@@ -147,7 +148,7 @@ aport-guardrail system.command.execute '{"command":"rm -rf /"}' # DENY (blocked
|
|
|
147
148
|
```
|
|
148
149
|
*(If you use `npx` without `-g`, run `npx aport-guardrail ...`.)*
|
|
149
150
|
|
|
150
|
-
**Python:** Use the guardrail in your app (e.g. add `APortCallback()` to your LangChain agent,
|
|
151
|
+
**Python:** Use the guardrail in your app (e.g. add `APortCallback()` to your LangChain agent, use `register_aport_guardrail()` for released CrewAI, or `enable_guardrail(OAPGuardrailProvider(...))` for CrewAI builds with native provider support). The guardrail runs on every tool call. To test allow/deny from the shell without Node, use `npx aport-guardrail ...` as above, or see your framework doc for in-app testing.
|
|
151
152
|
|
|
152
153
|
**Check passport status and audit:**
|
|
153
154
|
|
|
@@ -397,14 +398,14 @@ See [Verification methods](docs/VERIFICATION_METHODS.md) for a detailed comparis
|
|
|
397
398
|
| `aport` | OpenClaw one-command setup (passport + plugin + wrappers). Optional: `aport <agent_id>` for hosted passport. |
|
|
398
399
|
| `aport-guardrail` | Run guardrail check from the CLI (e.g. `aport-guardrail system.command.execute '{"command":"ls"}'`). Uses passport from your framework config dir. |
|
|
399
400
|
|
|
400
|
-
**Python:** After `pip install aport-agent-guardrails` you get `aport` (setup helper). For LangChain or CrewAI, install the
|
|
401
|
+
**Python:** After `pip install aport-agent-guardrails` you get `aport` (setup helper). For LangChain or CrewAI, install the framework package and setup:
|
|
401
402
|
|
|
402
403
|
| Command | Purpose |
|
|
403
404
|
|--------|---------|
|
|
404
405
|
| `aport setup --framework=langchain` | Print next-step commands (npx wizard, then `pip install aport-agent-guardrails-langchain`, `aport-langchain setup`). |
|
|
405
|
-
| `aport setup --framework=crewai` |
|
|
406
|
+
| `aport setup --framework=crewai` | Default released CrewAI path: bootstrap config/runtime, then use `pip install aport-agent-guardrails-crewai` and `aport-crewai setup`. |
|
|
407
|
+
| `aport setup --framework=crewai --integration-mode=native` | Native CrewAI path: bootstrap config/runtime, then use `uv add aport-agent-guardrails` and `OAPGuardrailProvider`. |
|
|
406
408
|
| `aport-langchain setup` | LangChain config and wizard (after installing `aport-agent-guardrails-langchain`). |
|
|
407
|
-
| `aport-crewai setup` | CrewAI config and wizard (after installing `aport-agent-guardrails-crewai`). |
|
|
408
409
|
|
|
409
410
|
Use the framework-specific doc for where config and passport live and for any extra steps (e.g. Cursor: restart IDE; LangChain/CrewAI: add callback/hook in code).
|
|
410
411
|
|
|
@@ -429,7 +430,7 @@ Use the framework-specific doc for where config and passport live and for any ex
|
|
|
429
430
|
| β [Cursor](docs/frameworks/cursor.md) | beforeShellExecution / preToolUse hooks, `~/.cursor/hooks.json` |
|
|
430
431
|
| β [Claude Code](docs/frameworks/claude-code.md) | PreToolUse hook, `~/.claude/settings.json` |
|
|
431
432
|
| β [LangChain / LangGraph](docs/frameworks/langchain.md) | `APortCallback` handler |
|
|
432
|
-
| β [CrewAI](docs/frameworks/crewai.md) |
|
|
433
|
+
| β [CrewAI](docs/frameworks/crewai.md) | Released hook adapter by default; native provider mode when available |
|
|
433
434
|
| β [DeerFlow](docs/frameworks/deerflow.md) | Generic provider wiring via DeerFlow `config.yaml` |
|
|
434
435
|
| β [n8n](docs/frameworks/n8n.md) | Custom node, branch on allow/deny |
|
|
435
436
|
| [Framework roadmap](docs/FRAMEWORK_ROADMAP.md) | Support status and roadmap |
|
package/bin/agent-guardrails
CHANGED
|
@@ -28,9 +28,22 @@ SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
|
|
|
28
28
|
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
29
29
|
FRAMEWORKS_DIR="$SCRIPT_DIR/frameworks"
|
|
30
30
|
LIB_DIR="$SCRIPT_DIR/lib"
|
|
31
|
+
SUPPORTED_FRAMEWORKS=(openclaw langchain crewai cursor claude-code deerflow n8n)
|
|
32
|
+
|
|
33
|
+
framework_supported() {
|
|
34
|
+
local candidate="${1:-}"
|
|
35
|
+
local supported
|
|
36
|
+
for supported in "${SUPPORTED_FRAMEWORKS[@]}"; do
|
|
37
|
+
if [[ "$candidate" == "$supported" ]]; then
|
|
38
|
+
return 0
|
|
39
|
+
fi
|
|
40
|
+
done
|
|
41
|
+
return 1
|
|
42
|
+
}
|
|
31
43
|
|
|
32
44
|
# Parse --framework= and -f (skip detection when set)
|
|
33
45
|
framework=""
|
|
46
|
+
integration_mode=""
|
|
34
47
|
REST=()
|
|
35
48
|
while [[ $# -gt 0 ]]; do
|
|
36
49
|
case "$1" in
|
|
@@ -47,6 +60,19 @@ while [[ $# -gt 0 ]]; do
|
|
|
47
60
|
exit 1
|
|
48
61
|
fi
|
|
49
62
|
;;
|
|
63
|
+
--integration-mode=*)
|
|
64
|
+
integration_mode="${1#--integration-mode=}"
|
|
65
|
+
shift
|
|
66
|
+
;;
|
|
67
|
+
--integration-mode)
|
|
68
|
+
if [[ $# -gt 1 ]]; then
|
|
69
|
+
integration_mode="$2"
|
|
70
|
+
shift 2
|
|
71
|
+
else
|
|
72
|
+
echo "[aport] ERROR: --integration-mode requires a value (compat or native)" >&2
|
|
73
|
+
exit 1
|
|
74
|
+
fi
|
|
75
|
+
;;
|
|
50
76
|
*)
|
|
51
77
|
REST+=("$1")
|
|
52
78
|
shift
|
|
@@ -59,16 +85,11 @@ if [[ -z "$framework" ]] && [[ ${#REST[@]} -gt 0 ]]; then
|
|
|
59
85
|
first_arg="${REST[0]}"
|
|
60
86
|
# Check if first arg looks like a framework name (lowercase alphanumeric + hyphen)
|
|
61
87
|
if [[ "$first_arg" =~ ^[a-z0-9-]+$ ]]; then
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
# Remove framework from REST so remaining args are pass-through
|
|
68
|
-
REST=("${REST[@]:1}")
|
|
69
|
-
break
|
|
70
|
-
fi
|
|
71
|
-
done
|
|
88
|
+
if framework_supported "$first_arg"; then
|
|
89
|
+
framework="$first_arg"
|
|
90
|
+
# Remove framework from REST so remaining args are pass-through
|
|
91
|
+
REST=("${REST[@]:1}")
|
|
92
|
+
fi
|
|
72
93
|
fi
|
|
73
94
|
fi
|
|
74
95
|
|
|
@@ -98,7 +119,7 @@ if [[ -z "$framework" ]]; then
|
|
|
98
119
|
echo " βββββββββββββββββββββββββββββββββββββββββ"
|
|
99
120
|
echo " Multiple frameworks detected: $detected_list"
|
|
100
121
|
echo " Choose one: openclaw | langchain | crewai | cursor | claude-code | deerflow | n8n"
|
|
101
|
-
echo " Example: npx @aporthq/agent-guardrails --framework=openclaw"
|
|
122
|
+
echo " Example: npx @aporthq/aport-agent-guardrails --framework=openclaw"
|
|
102
123
|
echo ""
|
|
103
124
|
read -p " Framework [${framework:-openclaw}]: " choice
|
|
104
125
|
framework="${choice:-${framework:-openclaw}}"
|
|
@@ -121,8 +142,8 @@ if [[ -z "$framework" ]]; then
|
|
|
121
142
|
echo " βββββββββββββββββββββββββββββββββββββββββ"
|
|
122
143
|
echo " No framework detected in current directory."
|
|
123
144
|
echo " Choose: openclaw | langchain | crewai | cursor | claude-code | deerflow | n8n"
|
|
124
|
-
echo " Example: npx @aporthq/agent-guardrails openclaw"
|
|
125
|
-
echo " npx @aporthq/agent-guardrails --framework=langchain"
|
|
145
|
+
echo " Example: npx @aporthq/aport-agent-guardrails openclaw"
|
|
146
|
+
echo " npx @aporthq/aport-agent-guardrails --framework=langchain"
|
|
126
147
|
echo ""
|
|
127
148
|
read -p " Framework [openclaw]: " framework
|
|
128
149
|
framework="${framework:-openclaw}"
|
|
@@ -136,6 +157,29 @@ if [[ ! "$framework" =~ ^[a-z0-9-]+$ ]]; then
|
|
|
136
157
|
exit 1
|
|
137
158
|
fi
|
|
138
159
|
|
|
160
|
+
if ! framework_supported "$framework"; then
|
|
161
|
+
echo "[aport] ERROR: Unknown or unsupported framework: $framework" >&2
|
|
162
|
+
echo " Supported: ${SUPPORTED_FRAMEWORKS[*]}" >&2
|
|
163
|
+
exit 1
|
|
164
|
+
fi
|
|
165
|
+
|
|
166
|
+
echo "[aport] Selected framework: $framework" >&2
|
|
167
|
+
|
|
168
|
+
if [[ -n "$integration_mode" ]]; then
|
|
169
|
+
case "$integration_mode" in
|
|
170
|
+
compat|native) ;;
|
|
171
|
+
*)
|
|
172
|
+
echo "[aport] ERROR: Unsupported integration mode: $integration_mode (expected compat or native)" >&2
|
|
173
|
+
exit 1
|
|
174
|
+
;;
|
|
175
|
+
esac
|
|
176
|
+
if [[ "$framework" != "crewai" ]]; then
|
|
177
|
+
echo "[aport] ERROR: --integration-mode is only supported for CrewAI" >&2
|
|
178
|
+
exit 1
|
|
179
|
+
fi
|
|
180
|
+
REST+=("--integration-mode=$integration_mode")
|
|
181
|
+
fi
|
|
182
|
+
|
|
139
183
|
script="$FRAMEWORKS_DIR/${framework}.sh"
|
|
140
184
|
|
|
141
185
|
# n8n: custom node not yet available; warn before running wizard-only script
|
|
@@ -160,7 +204,3 @@ if [[ -x "$generic_script" ]]; then
|
|
|
160
204
|
export APORT_FRAMEWORK="$framework"
|
|
161
205
|
exec "$generic_script" ${REST+"${REST[@]}"}
|
|
162
206
|
fi
|
|
163
|
-
|
|
164
|
-
echo "[aport] ERROR: Unknown or unsupported framework: $framework" >&2
|
|
165
|
-
echo " Supported: openclaw, langchain, crewai, cursor, claude-code, deerflow, n8n" >&2
|
|
166
|
-
exit 1
|
|
@@ -14,6 +14,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
|
14
14
|
# Resolve paths: config_dir/aport/ (new) or config_dir (legacy); same as bash guardrail
|
|
15
15
|
# shellcheck source=bin/aport-resolve-paths.sh
|
|
16
16
|
. "${SCRIPT_DIR}/bin/aport-resolve-paths.sh"
|
|
17
|
+
# shellcheck source=bin/lib/tool-mapping.sh
|
|
18
|
+
. "${SCRIPT_DIR}/bin/lib/tool-mapping.sh"
|
|
17
19
|
|
|
18
20
|
NODE_EVALUATOR="$SCRIPT_DIR/src/evaluator.js"
|
|
19
21
|
|
|
@@ -48,41 +50,12 @@ if [ -z "$APORT_AGENT_ID" ] && [ ! -f "$PASSPORT_FILE" ]; then
|
|
|
48
50
|
exit 1
|
|
49
51
|
fi
|
|
50
52
|
|
|
51
|
-
# Map tool to policy pack ID
|
|
52
|
-
POLICY_ID=""
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
|
53
|
+
# Map tool to policy pack ID from the shared JSON source of truth.
|
|
54
|
+
POLICY_ID="$(resolve_policy_id_from_tool_name "$TOOL_NAME" || true)"
|
|
55
|
+
if [[ -z "$POLICY_ID" ]]; then
|
|
56
|
+
echo "Error: Tool '$TOOL_NAME' is not mapped to a policy pack" >&2
|
|
57
|
+
exit 1
|
|
58
|
+
fi
|
|
86
59
|
|
|
87
60
|
# Call Node.js evaluator with API
|
|
88
61
|
if [ -n "$DEBUG_APORT" ]; then
|
|
@@ -13,6 +13,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
|
13
13
|
# Source validation library for input sanitization
|
|
14
14
|
# shellcheck source=bin/lib/validation.sh
|
|
15
15
|
. "${SCRIPT_DIR}/bin/lib/validation.sh"
|
|
16
|
+
# shellcheck source=bin/lib/tool-mapping.sh
|
|
17
|
+
. "${SCRIPT_DIR}/bin/lib/tool-mapping.sh"
|
|
16
18
|
|
|
17
19
|
# Get script directory to find submodules (external/ per GIT_SUBMODULES_EXPLAINED.md)
|
|
18
20
|
POLICIES_DIR="$SCRIPT_DIR/external/aport-policies"
|
|
@@ -94,11 +96,14 @@ write_decision() {
|
|
|
94
96
|
local deny_message="${4:-Policy evaluation failed}"
|
|
95
97
|
|
|
96
98
|
local decision_id=$(uuidgen 2> /dev/null || echo "local-$(date +%s)")
|
|
97
|
-
local passport_id=$(jq -r '.passport_id // "unknown"' "$PASSPORT_FILE")
|
|
99
|
+
local passport_id=$(jq -r '.passport_id // .agent_id // "unknown"' "$PASSPORT_FILE")
|
|
100
|
+
local agent_id=$(jq -r '.agent_id // .passport_id // "unknown"' "$PASSPORT_FILE")
|
|
98
101
|
local owner_id=$(jq -r '.owner_id // "unknown"' "$PASSPORT_FILE")
|
|
99
102
|
local assurance_level=$(jq -r '.assurance_level // "L0"' "$PASSPORT_FILE")
|
|
100
103
|
local passport_digest=$(compute_passport_digest "$PASSPORT_FILE")
|
|
101
104
|
local issued_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
105
|
+
local created_at="$issued_at"
|
|
106
|
+
local expires_in=3600
|
|
102
107
|
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)
|
|
103
108
|
|
|
104
109
|
# Build reasons array per OAP v1.0 spec
|
|
@@ -126,12 +131,15 @@ write_decision() {
|
|
|
126
131
|
--arg decision_id "$decision_id" \
|
|
127
132
|
--arg policy_id "$policy_id" \
|
|
128
133
|
--arg passport_id "$passport_id" \
|
|
134
|
+
--arg agent_id "$agent_id" \
|
|
129
135
|
--arg owner_id "$owner_id" \
|
|
130
136
|
--arg assurance_level "$assurance_level" \
|
|
131
137
|
--argjson allow "$allow" \
|
|
132
138
|
--argjson reasons "$reasons" \
|
|
133
139
|
--arg issued_at "$issued_at" \
|
|
140
|
+
--arg created_at "$created_at" \
|
|
134
141
|
--arg expires_at "$expires_at" \
|
|
142
|
+
--argjson expires_in "$expires_in" \
|
|
135
143
|
--arg passport_digest "$passport_digest" \
|
|
136
144
|
--arg prev_decision_id "$prev_decision_id" \
|
|
137
145
|
--arg prev_content_hash "$prev_content_hash" \
|
|
@@ -139,12 +147,15 @@ write_decision() {
|
|
|
139
147
|
decision_id: $decision_id,
|
|
140
148
|
policy_id: $policy_id,
|
|
141
149
|
passport_id: $passport_id,
|
|
150
|
+
agent_id: $agent_id,
|
|
142
151
|
owner_id: $owner_id,
|
|
143
152
|
assurance_level: $assurance_level,
|
|
144
153
|
allow: $allow,
|
|
145
154
|
reasons: $reasons,
|
|
146
155
|
issued_at: $issued_at,
|
|
156
|
+
created_at: $created_at,
|
|
147
157
|
expires_at: $expires_at,
|
|
158
|
+
expires_in: $expires_in,
|
|
148
159
|
passport_digest: $passport_digest,
|
|
149
160
|
signature: "local-unsigned",
|
|
150
161
|
kid: "oap:local:dev-key",
|
|
@@ -216,58 +227,12 @@ if [ "$SPEC_VERSION" != "oap/1.0" ]; then
|
|
|
216
227
|
write_decision false "unknown" "oap.passport_version_mismatch" "Passport spec version is '$SPEC_VERSION', expected 'oap/1.0'"
|
|
217
228
|
fi
|
|
218
229
|
|
|
219
|
-
# Map tool to policy pack ID
|
|
220
|
-
POLICY_ID=""
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
exec | exec.run | exec.* | system.* | bash | shell | command)
|
|
226
|
-
POLICY_ID="system.command.execute.v1"
|
|
227
|
-
;;
|
|
228
|
-
gateway | gateway.* | process | process.*)
|
|
229
|
-
# High risk operations - treat as command execution
|
|
230
|
-
POLICY_ID="system.command.execute.v1"
|
|
231
|
-
;;
|
|
232
|
-
message.send | message.* | messaging.* | sms | whatsapp | slack | email)
|
|
233
|
-
POLICY_ID="messaging.message.send.v1"
|
|
234
|
-
;;
|
|
235
|
-
read | file.read | file.read.* | data.file.read | data.file.read.*)
|
|
236
|
-
POLICY_ID="data.file.read.v1"
|
|
237
|
-
;;
|
|
238
|
-
write | edit | file.write | file.write.* | file.edit | file.edit.* | data.file.write | data.file.write.*)
|
|
239
|
-
POLICY_ID="data.file.write.v1"
|
|
240
|
-
;;
|
|
241
|
-
web_fetch | webfetch | web.fetch | web.fetch.* | web_search | websearch | web.search | web.search.*)
|
|
242
|
-
POLICY_ID="web.fetch.v1"
|
|
243
|
-
;;
|
|
244
|
-
browser | browser.* | web.browser | web.browser.*)
|
|
245
|
-
POLICY_ID="web.browser.v1"
|
|
246
|
-
;;
|
|
247
|
-
mcp.*)
|
|
248
|
-
POLICY_ID="mcp.tool.execute.v1"
|
|
249
|
-
;;
|
|
250
|
-
agent.session.* | session.create | session.* | sessions.* | sessions_spawn | sessions_send)
|
|
251
|
-
POLICY_ID="agent.session.create.v1"
|
|
252
|
-
;;
|
|
253
|
-
cron | cron.*)
|
|
254
|
-
# Scheduled tasks - treat as session management
|
|
255
|
-
POLICY_ID="agent.session.create.v1"
|
|
256
|
-
;;
|
|
257
|
-
agent.tool.* | tool.register)
|
|
258
|
-
POLICY_ID="agent.tool.register.v1"
|
|
259
|
-
;;
|
|
260
|
-
payment.refund | refund | payment.charge | charge | payment.* | finance.*)
|
|
261
|
-
POLICY_ID="finance.payment.refund.v1"
|
|
262
|
-
;;
|
|
263
|
-
database.write | database.insert | database.update | database.delete | data.export | data.export.*)
|
|
264
|
-
POLICY_ID="data.export.create.v1"
|
|
265
|
-
;;
|
|
266
|
-
*)
|
|
267
|
-
# Unknown tool - deny by default for security
|
|
268
|
-
write_decision false "unknown" "oap.unknown_capability" "Tool '$TOOL_NAME' is not mapped to a policy pack"
|
|
269
|
-
;;
|
|
270
|
-
esac
|
|
230
|
+
# Map tool to policy pack ID from the shared JSON source of truth.
|
|
231
|
+
POLICY_ID="$(resolve_policy_id_from_tool_name "$TOOL_NAME" || true)"
|
|
232
|
+
if [[ -z "$POLICY_ID" ]]; then
|
|
233
|
+
# Unknown tool - deny by default for security.
|
|
234
|
+
write_decision false "unknown" "oap.unknown_capability" "Tool '$TOOL_NAME' is not mapped to a policy pack"
|
|
235
|
+
fi
|
|
271
236
|
|
|
272
237
|
# Capability-specific context summary for audit log (command, recipient, repo/branch, file_path, etc.)
|
|
273
238
|
CONTEXT_SUMMARY=""
|
|
@@ -56,8 +56,8 @@ resolve_aport_paths() {
|
|
|
56
56
|
# 1) Explicit path set and file exists β use it (plugin or wrapper)
|
|
57
57
|
if [ -n "${OPENCLAW_PASSPORT_FILE:-}" ] && [ -f "$OPENCLAW_PASSPORT_FILE" ]; then
|
|
58
58
|
# Validate env-provided path if validator is available
|
|
59
|
-
if type
|
|
60
|
-
if !
|
|
59
|
+
if type validate_explicit_passport_path &> /dev/null; then
|
|
60
|
+
if ! validate_explicit_passport_path "$OPENCLAW_PASSPORT_FILE"; then
|
|
61
61
|
echo "[aport] WARN: OPENCLAW_PASSPORT_FILE path failed validation: $OPENCLAW_PASSPORT_FILE" >&2
|
|
62
62
|
fi
|
|
63
63
|
fi
|
|
@@ -13,23 +13,79 @@ FRAMEWORKS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-.}")" && pwd)"
|
|
|
13
13
|
source "$LIB/common.sh"
|
|
14
14
|
# shellcheck source=../lib/passport.sh
|
|
15
15
|
source "$LIB/passport.sh"
|
|
16
|
+
# shellcheck source=../lib/runtime.sh
|
|
17
|
+
source "$LIB/runtime.sh"
|
|
16
18
|
# shellcheck source=../lib/config.sh
|
|
17
19
|
source "$LIB/config.sh"
|
|
18
20
|
|
|
19
21
|
framework="${APORT_FRAMEWORK:?APORT_FRAMEWORK must be set by the dispatcher}"
|
|
22
|
+
crewai_integration_mode="${APORT_CREWAI_INTEGRATION_MODE:-compat}"
|
|
23
|
+
|
|
24
|
+
FORWARD_ARGS=()
|
|
25
|
+
while [[ $# -gt 0 ]]; do
|
|
26
|
+
case "$1" in
|
|
27
|
+
--integration-mode=*)
|
|
28
|
+
crewai_integration_mode="${1#--integration-mode=}"
|
|
29
|
+
;;
|
|
30
|
+
--integration-mode)
|
|
31
|
+
if [[ $# -lt 2 ]]; then
|
|
32
|
+
log_error "--integration-mode requires compat or native"
|
|
33
|
+
exit 1
|
|
34
|
+
fi
|
|
35
|
+
crewai_integration_mode="$2"
|
|
36
|
+
shift
|
|
37
|
+
;;
|
|
38
|
+
*)
|
|
39
|
+
FORWARD_ARGS+=("$1")
|
|
40
|
+
;;
|
|
41
|
+
esac
|
|
42
|
+
shift
|
|
43
|
+
done
|
|
44
|
+
|
|
45
|
+
case "$crewai_integration_mode" in
|
|
46
|
+
compat | native) ;;
|
|
47
|
+
*)
|
|
48
|
+
log_error "Unsupported CrewAI integration mode: $crewai_integration_mode"
|
|
49
|
+
exit 1
|
|
50
|
+
;;
|
|
51
|
+
esac
|
|
52
|
+
|
|
53
|
+
if [[ "$framework" != "crewai" && "$crewai_integration_mode" != "compat" ]]; then
|
|
54
|
+
log_error "--integration-mode is only supported for CrewAI"
|
|
55
|
+
exit 1
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
resolve_next_steps_file() {
|
|
59
|
+
if [[ "$framework" == "crewai" ]]; then
|
|
60
|
+
if [[ "$crewai_integration_mode" == "native" ]]; then
|
|
61
|
+
echo "$FRAMEWORKS_DIR/next-steps.d/crewai-native.txt"
|
|
62
|
+
return
|
|
63
|
+
fi
|
|
64
|
+
fi
|
|
65
|
+
echo "$FRAMEWORKS_DIR/next-steps.d/$framework.txt"
|
|
66
|
+
}
|
|
20
67
|
|
|
21
68
|
run_setup() {
|
|
22
69
|
log_info "Setting up APort guardrails for $framework..."
|
|
23
70
|
config_dir="$(write_config_template "$framework")"
|
|
24
71
|
mkdir -p "$config_dir/aport"
|
|
25
72
|
chmod 700 "$config_dir/aport"
|
|
73
|
+
install_runtime_tree "$config_dir"
|
|
26
74
|
export APORT_FRAMEWORK="$framework"
|
|
27
|
-
|
|
75
|
+
if [[ "$framework" == "crewai" ]]; then
|
|
76
|
+
log_info "CrewAI integration mode: $crewai_integration_mode"
|
|
77
|
+
fi
|
|
78
|
+
if ((${#FORWARD_ARGS[@]} > 0)); then
|
|
79
|
+
run_passport_wizard "${FORWARD_ARGS[@]}"
|
|
80
|
+
else
|
|
81
|
+
run_passport_wizard
|
|
82
|
+
fi
|
|
28
83
|
# Harden permissions on passport (contains policy/capabilities)
|
|
29
84
|
[ -f "$config_dir/aport/passport.json" ] && chmod 600 "$config_dir/aport/passport.json"
|
|
85
|
+
log_info "Local runtime installed at: $config_dir/aport/runtime"
|
|
30
86
|
|
|
31
87
|
# Print framework-specific next steps from data file
|
|
32
|
-
next_steps="$
|
|
88
|
+
next_steps="$(resolve_next_steps_file)"
|
|
33
89
|
if [ -f "$next_steps" ]; then
|
|
34
90
|
echo ""
|
|
35
91
|
# Substitute $config_dir in the template
|
|
@@ -57,4 +113,4 @@ run_setup() {
|
|
|
57
113
|
fi
|
|
58
114
|
}
|
|
59
115
|
|
|
60
|
-
run_setup
|
|
116
|
+
run_setup
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Next steps (CrewAI native provider mode):
|
|
2
|
+
βββββββββββββββββββββββββββββββββββββββ
|
|
3
|
+
Requires a CrewAI build with native guardrail provider support.
|
|
4
|
+
|
|
5
|
+
1. Install the Python runtime package:
|
|
6
|
+
uv add aport-agent-guardrails
|
|
7
|
+
2. Config written to: $config_dir
|
|
8
|
+
3. Enable the native provider before running your crew:
|
|
9
|
+
|
|
10
|
+
from crewai.hooks import enable_guardrail
|
|
11
|
+
from aport_guardrails.providers import OAPGuardrailProvider
|
|
12
|
+
enable_guardrail(
|
|
13
|
+
OAPGuardrailProvider(framework="crewai", config_path="$config_dir/config.yaml"),
|
|
14
|
+
fail_closed=True,
|
|
15
|
+
)
|
|
16
|
+
crew.kickoff()
|
|
17
|
+
|
|
18
|
+
Released CrewAI compatibility mode:
|
|
19
|
+
rerun without --integration-mode=native
|
|
20
|
+
|
|
21
|
+
See: docs/frameworks/crewai.md
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
Next steps (CrewAI):
|
|
2
2
|
βββββββββββββββββββ
|
|
3
|
-
|
|
3
|
+
Default mode targets released CrewAI without native guardrail provider support.
|
|
4
|
+
|
|
5
|
+
1. Install the CrewAI adapter package:
|
|
4
6
|
pip install aport-agent-guardrails-crewai
|
|
5
7
|
aport-crewai setup
|
|
6
8
|
2. Config written to: $config_dir
|
|
@@ -10,4 +12,7 @@
|
|
|
10
12
|
register_aport_guardrail()
|
|
11
13
|
crew.kickoff()
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
Optional native mode:
|
|
16
|
+
rerun with --integration-mode=native
|
|
17
|
+
|
|
18
|
+
See: docs/frameworks/crewai.md
|
package/bin/lib/error.sh
CHANGED
|
@@ -182,13 +182,13 @@ get_resolution() {
|
|
|
182
182
|
echo "Reduce context data size by removing unnecessary fields or summarizing large data. Default limit: 100KB."
|
|
183
183
|
;;
|
|
184
184
|
"$ERROR_PATH_NOT_ALLOWED")
|
|
185
|
-
echo "Use standard
|
|
185
|
+
echo "Use standard framework config directories such as ~/.openclaw/, ~/.aport/, ~/.cursor/, ~/.claude/, ~/.n8n/, or /tmp/aport-*. Contact administrator to add custom allowed directories."
|
|
186
186
|
;;
|
|
187
187
|
"$ERROR_PATH_TRAVERSAL")
|
|
188
188
|
echo "Use absolute paths without parent directory references (../ or /..). This is a security feature to prevent path traversal attacks."
|
|
189
189
|
;;
|
|
190
190
|
"$ERROR_PASSPORT_NOT_FOUND")
|
|
191
|
-
echo "Create a passport by running: npx @aporthq/aport-agent-guardrails
|
|
191
|
+
echo "Create a passport by running: npx @aporthq/aport-agent-guardrails <framework>. See: https://github.com/aporthq/aport-agent-guardrails/tree/main/docs/frameworks"
|
|
192
192
|
;;
|
|
193
193
|
"$ERROR_PASSPORT_MISSING_CAP")
|
|
194
194
|
echo "Request capability be added to passport or generate new passport with required capabilities."
|
|
@@ -206,7 +206,7 @@ get_resolution() {
|
|
|
206
206
|
echo "Wait for rate limit to reset, reduce request frequency, or use local evaluation mode instead of API mode."
|
|
207
207
|
;;
|
|
208
208
|
"$ERROR_MISCONFIGURED")
|
|
209
|
-
echo "Run setup: npx @aporthq/aport-agent-guardrails <framework>. Check
|
|
209
|
+
echo "Run setup: npx @aporthq/aport-agent-guardrails <framework>. Check your framework config directory for aport/passport.json and aport/runtime/bin/aport-guardrail.sh."
|
|
210
210
|
;;
|
|
211
211
|
*)
|
|
212
212
|
echo ""
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
file bin/aport-create-passport.sh
|
|
2
|
+
file bin/aport-guardrail.sh
|
|
3
|
+
file bin/aport-guardrail-bash.sh
|
|
4
|
+
file bin/aport-guardrail-api.sh
|
|
5
|
+
file bin/aport-guardrail-v2.sh
|
|
6
|
+
file bin/aport-resolve-paths.sh
|
|
7
|
+
file bin/aport-status.sh
|
|
8
|
+
file bin/lib/common.sh
|
|
9
|
+
file bin/lib/config.sh
|
|
10
|
+
file bin/lib/passport.sh
|
|
11
|
+
file bin/lib/tool-mapping.sh
|
|
12
|
+
file bin/lib/validation.sh
|
|
13
|
+
file python/aport_guardrails/core/tool-pack-mapping.json
|
|
14
|
+
file src/evaluator.js
|
|
15
|
+
tree external/aport-policies
|
|
16
|
+
file external/aport-spec/oap/passport-schema.json
|
|
17
|
+
mkdir local-overrides/policies
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Runtime asset installation for local APort evaluation.
|
|
3
|
+
# Installs a self-contained shell runtime under <config_dir>/aport/runtime
|
|
4
|
+
# from the repo/npm package sources in bin/, external/, and src/.
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
# shellcheck source=./common.sh
|
|
9
|
+
source "$(dirname "${BASH_SOURCE[0]:-.}")/common.sh"
|
|
10
|
+
|
|
11
|
+
MANIFEST_FILE="$(cd "$(dirname "${BASH_SOURCE[0]:-.}")" && pwd)/runtime-manifest.txt"
|
|
12
|
+
|
|
13
|
+
_runtime_copy_file() {
|
|
14
|
+
local runtime_dir="$1"
|
|
15
|
+
local rel_path="$2"
|
|
16
|
+
local src="$ROOT_DIR/$rel_path"
|
|
17
|
+
local dest="$runtime_dir/$rel_path"
|
|
18
|
+
|
|
19
|
+
if [[ ! -f "$src" ]]; then
|
|
20
|
+
log_error "Runtime source missing: $src"
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
mkdir -p "$(dirname "$dest")"
|
|
25
|
+
cp "$src" "$dest"
|
|
26
|
+
chmod +x "$dest" 2> /dev/null || true
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
_runtime_copy_tree() {
|
|
30
|
+
local runtime_dir="$1"
|
|
31
|
+
local rel_path="$2"
|
|
32
|
+
local src="$ROOT_DIR/$rel_path"
|
|
33
|
+
local dest="$runtime_dir/$rel_path"
|
|
34
|
+
|
|
35
|
+
if [[ ! -d "$src" ]]; then
|
|
36
|
+
log_error "Runtime source directory missing: $src"
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
rm -rf "$dest"
|
|
41
|
+
mkdir -p "$(dirname "$dest")"
|
|
42
|
+
cp -R "$src" "$dest"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
install_runtime_tree() {
|
|
46
|
+
local config_dir="$1"
|
|
47
|
+
local runtime_dir="$config_dir/aport/runtime"
|
|
48
|
+
local kind=""
|
|
49
|
+
local rel_path=""
|
|
50
|
+
|
|
51
|
+
mkdir -p "$runtime_dir"
|
|
52
|
+
|
|
53
|
+
if [[ ! -f "$MANIFEST_FILE" ]]; then
|
|
54
|
+
log_error "Runtime manifest missing: $MANIFEST_FILE"
|
|
55
|
+
exit 1
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
while read -r kind rel_path; do
|
|
59
|
+
[[ -z "$kind" ]] && continue
|
|
60
|
+
[[ "$kind" == \#* ]] && continue
|
|
61
|
+
case "$kind" in
|
|
62
|
+
file)
|
|
63
|
+
_runtime_copy_file "$runtime_dir" "$rel_path"
|
|
64
|
+
;;
|
|
65
|
+
tree)
|
|
66
|
+
_runtime_copy_tree "$runtime_dir" "$rel_path"
|
|
67
|
+
;;
|
|
68
|
+
mkdir)
|
|
69
|
+
mkdir -p "$runtime_dir/$rel_path"
|
|
70
|
+
;;
|
|
71
|
+
*)
|
|
72
|
+
log_error "Unsupported runtime manifest entry: $kind $rel_path"
|
|
73
|
+
exit 1
|
|
74
|
+
;;
|
|
75
|
+
esac
|
|
76
|
+
done < "$MANIFEST_FILE"
|
|
77
|
+
|
|
78
|
+
chmod -R u+rwX "$runtime_dir" 2> /dev/null || true
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export -f install_runtime_tree
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
_aport_tool_mapping_file() {
|
|
4
|
+
local lib_dir
|
|
5
|
+
lib_dir="$(cd "$(dirname "${BASH_SOURCE[0]:-.}")" && pwd)"
|
|
6
|
+
|
|
7
|
+
local candidates=(
|
|
8
|
+
"$lib_dir/../../python/aport_guardrails/core/tool-pack-mapping.json"
|
|
9
|
+
"$lib_dir/../python/aport_guardrails/core/tool-pack-mapping.json"
|
|
10
|
+
"$lib_dir/../../aport/runtime/python/aport_guardrails/core/tool-pack-mapping.json"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
local candidate=""
|
|
14
|
+
for candidate in "${candidates[@]}"; do
|
|
15
|
+
if [[ -f "$candidate" ]]; then
|
|
16
|
+
printf '%s\n' "$candidate"
|
|
17
|
+
return 0
|
|
18
|
+
fi
|
|
19
|
+
done
|
|
20
|
+
return 1
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
resolve_policy_id_from_tool_name() {
|
|
24
|
+
local tool_name="${1:-}"
|
|
25
|
+
local mapping_file=""
|
|
26
|
+
local normalized=""
|
|
27
|
+
local policy_id=""
|
|
28
|
+
|
|
29
|
+
normalized="$(printf '%s' "$tool_name" | tr '[:upper:]' '[:lower:]')"
|
|
30
|
+
mapping_file="$(_aport_tool_mapping_file)" || return 1
|
|
31
|
+
|
|
32
|
+
policy_id="$(
|
|
33
|
+
jq -r --arg tool "$normalized" '
|
|
34
|
+
first(
|
|
35
|
+
.rules[]?
|
|
36
|
+
| select(
|
|
37
|
+
(.prefixes // [] | any(. as $prefix | $tool | startswith($prefix)))
|
|
38
|
+
or
|
|
39
|
+
(.substrings // [] | any(. as $substring | $tool | contains($substring)))
|
|
40
|
+
)
|
|
41
|
+
| .pack
|
|
42
|
+
) // empty
|
|
43
|
+
' "$mapping_file"
|
|
44
|
+
)"
|
|
45
|
+
|
|
46
|
+
if [[ -n "$policy_id" && "$policy_id" != "null" ]]; then
|
|
47
|
+
printf '%s\n' "$policy_id"
|
|
48
|
+
return 0
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
return 1
|
|
52
|
+
}
|
package/bin/lib/validation.sh
CHANGED
|
@@ -58,9 +58,9 @@ validate_tool_name() {
|
|
|
58
58
|
return 0
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
# Validate
|
|
62
|
-
# Returns 0 if
|
|
63
|
-
|
|
61
|
+
# Validate explicit operator-provided passport paths.
|
|
62
|
+
# Returns 0 if the path is hygienic, 1 if it contains dangerous constructs.
|
|
63
|
+
validate_explicit_passport_path() {
|
|
64
64
|
local path="$1"
|
|
65
65
|
|
|
66
66
|
# Check for empty
|
|
@@ -68,11 +68,28 @@ validate_passport_path() {
|
|
|
68
68
|
return 1
|
|
69
69
|
fi
|
|
70
70
|
|
|
71
|
+
# Check for path traversal attempts
|
|
72
|
+
if echo "$path" | grep -qE '\.\./|/\.\./|/\.\.$'; then
|
|
73
|
+
return 1
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
return 0
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# Validate auto-discovered/default passport paths against trusted framework dirs.
|
|
80
|
+
# Returns 0 if safe, 1 if potentially dangerous.
|
|
81
|
+
validate_passport_path() {
|
|
82
|
+
local path="$1"
|
|
83
|
+
|
|
84
|
+
if ! validate_explicit_passport_path "$path"; then
|
|
85
|
+
return 1
|
|
86
|
+
fi
|
|
87
|
+
|
|
71
88
|
# Expand to absolute path
|
|
72
89
|
local abs_path
|
|
73
90
|
abs_path=$(readlink -f "$path" 2> /dev/null || realpath "$path" 2> /dev/null || echo "$path")
|
|
74
91
|
|
|
75
|
-
# Allowed base directories for
|
|
92
|
+
# Allowed base directories for auto-discovery
|
|
76
93
|
local allowed_bases=(
|
|
77
94
|
"$HOME/.openclaw"
|
|
78
95
|
"$HOME/.aport"
|
|
@@ -82,7 +99,6 @@ validate_passport_path() {
|
|
|
82
99
|
"/tmp/aport-"
|
|
83
100
|
)
|
|
84
101
|
|
|
85
|
-
# Check if path starts with any allowed base
|
|
86
102
|
local is_allowed=false
|
|
87
103
|
for base in "${allowed_bases[@]}"; do
|
|
88
104
|
case "$abs_path" in
|
|
@@ -90,6 +106,10 @@ validate_passport_path() {
|
|
|
90
106
|
is_allowed=true
|
|
91
107
|
break
|
|
92
108
|
;;
|
|
109
|
+
"/private$base"*)
|
|
110
|
+
is_allowed=true
|
|
111
|
+
break
|
|
112
|
+
;;
|
|
93
113
|
esac
|
|
94
114
|
done
|
|
95
115
|
|
|
@@ -97,16 +117,6 @@ validate_passport_path() {
|
|
|
97
117
|
return 1
|
|
98
118
|
fi
|
|
99
119
|
|
|
100
|
-
# Check for path traversal attempts
|
|
101
|
-
if echo "$path" | grep -qE '\.\./|/\.\./|/\.\.$'; then
|
|
102
|
-
return 1
|
|
103
|
-
fi
|
|
104
|
-
|
|
105
|
-
# Check for null bytes
|
|
106
|
-
if echo "$path" | grep -qF $'\0'; then
|
|
107
|
-
return 1
|
|
108
|
-
fi
|
|
109
|
-
|
|
110
120
|
return 0
|
|
111
121
|
}
|
|
112
122
|
|
|
@@ -9,7 +9,7 @@ Public developer view of supported frameworks and roadmap. Details per framework
|
|
|
9
9
|
| **OpenClaw** | Shipped | Full: plugin, wizard, local/API | [openclaw.md](frameworks/openclaw.md) | `npx @aporthq/aport-agent-guardrails openclaw` |
|
|
10
10
|
| **Cursor** | Shipped | Full: hooks installer + script | [cursor.md](frameworks/cursor.md) | `npx @aporthq/aport-agent-guardrails cursor` |
|
|
11
11
|
| **LangChain / LangGraph** | Shipped | **Python only:** callback, `aport-langchain setup` | [langchain.md](frameworks/langchain.md) | `npx @aporthq/aport-agent-guardrails langchain` then `pip install aport-agent-guardrails-langchain` + `aport-langchain setup` |
|
|
12
|
-
| **CrewAI** | Shipped | **Python
|
|
12
|
+
| **CrewAI** | Shipped | **Python:** released hook adapter by default; native provider mode when available | [crewai.md](frameworks/crewai.md) | `npx @aporthq/aport-agent-guardrails crewai` then `pip install aport-agent-guardrails-crewai` + `aport-crewai setup` |
|
|
13
13
|
|
|
14
14
|
**Coming soon:** n8n β custom node and runtime in progress ([n8n.md](frameworks/n8n.md)). Not listed in CLI options until shipped.
|
|
15
15
|
|
package/docs/PROVIDER.md
CHANGED
|
@@ -26,7 +26,7 @@ result = provider.evaluate(request) # sync
|
|
|
26
26
|
result = await provider.aevaluate(request) # async
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
**Supported frameworks:** DeerFlow, any Python framework with a `GuardrailProvider` protocol.
|
|
29
|
+
**Supported frameworks:** DeerFlow, CrewAI builds with native provider support, and any Python framework with a `GuardrailProvider` protocol.
|
|
30
30
|
|
|
31
31
|
**Config:** `~/.aport/<framework>/config.yaml` (created by `aport setup --framework <name>`)
|
|
32
32
|
|
package/docs/RELEASE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Release process and version policy
|
|
2
2
|
|
|
3
|
-
**Current release:** 1.0.
|
|
3
|
+
**Current release:** 1.0.21 (see [CHANGELOG.md](../CHANGELOG.md)).
|
|
4
4
|
|
|
5
5
|
We keep **one version number** across all published packages (Node core, Python core, and every framework adapter). That avoids βcore is 1.2 but CLI is 0.9β and keeps the story simple for users and support.
|
|
6
6
|
|
|
@@ -32,7 +32,7 @@ So: **root = CLI/setup**; **core = library**. We publish core so that (1) the ad
|
|
|
32
32
|
## 2. Tooling
|
|
33
33
|
|
|
34
34
|
- **Changesets** (Node): fixed mode so all workspace packages are in one βfixedβ group and get the same version on release.
|
|
35
|
-
- **sync-version script**: after `changeset version`,
|
|
35
|
+
- **sync-version script**: after `changeset version`, reads the canonical version from the fixed workspace group and propagates it to the root CLI package, Python packages, manifests, lockfiles, and release docs.
|
|
36
36
|
|
|
37
37
|
---
|
|
38
38
|
|
|
@@ -43,7 +43,7 @@ So: **root = CLI/setup**; **core = library**. We publish core so that (1) the ad
|
|
|
43
43
|
```bash
|
|
44
44
|
npm run version
|
|
45
45
|
```
|
|
46
|
-
This runs `changeset version`
|
|
46
|
+
This runs `changeset version` for the fixed workspace group, then `node scripts/sync-version.mjs` to align the root CLI package, Python packages, lockfiles, manifests, and release docs to that same version.
|
|
47
47
|
3. **Commit** the version bump and changelog updates (e.g. βchore(release): 1.3.0β).
|
|
48
48
|
4. **Tag and push** β this triggers the release workflow and publishes both npm and PyPI:
|
|
49
49
|
```bash
|
|
@@ -516,10 +516,10 @@ sudo yum install jq
|
|
|
516
516
|
**Resolution**:
|
|
517
517
|
```bash
|
|
518
518
|
# Check passport exists
|
|
519
|
-
ls -la ~/.openclaw/passport.json
|
|
519
|
+
ls -la ~/.openclaw/aport/passport.json
|
|
520
520
|
|
|
521
|
-
# Check
|
|
522
|
-
ls -la ~/.openclaw
|
|
521
|
+
# Check local runtime exists
|
|
522
|
+
ls -la ~/.openclaw/aport/runtime/bin/aport-guardrail.sh
|
|
523
523
|
|
|
524
524
|
# Run setup if missing
|
|
525
525
|
npx @aporthq/aport-agent-guardrails openclaw
|
|
@@ -1,51 +1,55 @@
|
|
|
1
|
-
# APort Agent
|
|
1
|
+
# APort Agent Guardrails β CrewAI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
APort supports CrewAI in two ways:
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
1. **Released CrewAI compatibility mode** via the existing `before_tool_call` adapter
|
|
6
|
+
2. **Native provider mode** for CrewAI builds that expose a native guardrail provider seam
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- **Multi-task crews:** The same hook runs for every tool call across all tasks and agents, so multi-task crews are supported by default.
|
|
8
|
+
The default bootstrap path targets released CrewAI so it works today without waiting
|
|
9
|
+
for upstream provider support.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
## Option 1: Released CrewAI Compatibility Mode
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
- **Config:** `~/.aport/crewai/config.yaml` or `.aport/config.yaml` (see [Verification methods](../VERIFICATION_METHODS.md))
|
|
13
|
+
This is the default mode.
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
Bootstrap config, passport, and local runtime with the Python-native CLI:
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
| **Core (library)** | The **evaluator** and **before-tool-call hook** in your code. Calls policy + passport to allow/deny each tool call. | Integrating into your app: register the hook so CrewAI blocks tool runs when policy denies. |
|
|
22
|
-
|
|
23
|
-
You typically use **both**: run the CLI once to create passport and config, then use the library in your CrewAI app so every tool call is verified.
|
|
17
|
+
```bash
|
|
18
|
+
uvx --from aport-agent-guardrails aport setup --framework=crewai
|
|
19
|
+
```
|
|
24
20
|
|
|
25
|
-
|
|
21
|
+
Or use the Node bootstrap if you prefer:
|
|
26
22
|
|
|
27
|
-
|
|
23
|
+
```bash
|
|
24
|
+
npx -y @aporthq/aport-agent-guardrails crewai
|
|
25
|
+
```
|
|
28
26
|
|
|
29
|
-
|
|
27
|
+
For CI or other non-interactive environments with the Python-native CLI:
|
|
30
28
|
|
|
31
29
|
```bash
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
APORT_CREWAI_CONFIG_DIR=.aport/crewai \
|
|
31
|
+
uvx --from aport-agent-guardrails aport setup \
|
|
32
|
+
--framework=crewai \
|
|
33
|
+
--ci
|
|
35
34
|
```
|
|
36
35
|
|
|
37
|
-
|
|
36
|
+
The equivalent Node bootstrap is:
|
|
38
37
|
|
|
39
38
|
```bash
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
APORT_CREWAI_CONFIG_DIR=.aport/crewai \
|
|
40
|
+
npx -y @aporthq/aport-agent-guardrails crewai \
|
|
41
|
+
--output .aport/crewai/aport/passport.json \
|
|
42
|
+
--non-interactive
|
|
42
43
|
```
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
Install the released CrewAI adapter:
|
|
45
46
|
|
|
46
|
-
|
|
47
|
+
```bash
|
|
48
|
+
pip install aport-agent-guardrails-crewai
|
|
49
|
+
aport-crewai setup
|
|
50
|
+
```
|
|
47
51
|
|
|
48
|
-
|
|
52
|
+
Register the hook before your crew runs:
|
|
49
53
|
|
|
50
54
|
```python
|
|
51
55
|
from aport_guardrails_crewai import register_aport_guardrail
|
|
@@ -54,66 +58,96 @@ register_aport_guardrail()
|
|
|
54
58
|
crew.kickoff()
|
|
55
59
|
```
|
|
56
60
|
|
|
57
|
-
|
|
61
|
+
This path works with released CrewAI because it plugs directly into CrewAI's existing
|
|
62
|
+
`before_tool_call` hook behavior.
|
|
58
63
|
|
|
59
|
-
|
|
60
|
-
from aport_guardrails_crewai import with_aport_guardrail
|
|
64
|
+
## Option 2: Native Provider Mode
|
|
61
65
|
|
|
62
|
-
|
|
63
|
-
def main():
|
|
64
|
-
crew.kickoff()
|
|
66
|
+
Use this only with a CrewAI build that exposes the native guardrail provider API.
|
|
65
67
|
|
|
66
|
-
|
|
68
|
+
Bootstrap with native-mode instructions using the Python-native CLI:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
uvx --from aport-agent-guardrails aport setup \
|
|
72
|
+
--framework=crewai \
|
|
73
|
+
--integration-mode=native
|
|
67
74
|
```
|
|
68
75
|
|
|
69
|
-
|
|
76
|
+
The equivalent Node bootstrap is:
|
|
70
77
|
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
|
|
78
|
+
```bash
|
|
79
|
+
npx -y @aporthq/aport-agent-guardrails crewai --integration-mode=native
|
|
80
|
+
```
|
|
74
81
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
82
|
+
Install the Python runtime package:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
uv add aport-agent-guardrails
|
|
78
86
|
```
|
|
79
87
|
|
|
80
|
-
|
|
88
|
+
Enable the provider before your crew runs:
|
|
81
89
|
|
|
82
|
-
```
|
|
83
|
-
|
|
90
|
+
```python
|
|
91
|
+
from crewai.hooks import enable_guardrail
|
|
92
|
+
from aport_guardrails.providers import OAPGuardrailProvider
|
|
84
93
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
94
|
+
enable_guardrail(
|
|
95
|
+
OAPGuardrailProvider(
|
|
96
|
+
framework="crewai",
|
|
97
|
+
config_path="~/.aport/crewai/config.yaml",
|
|
98
|
+
),
|
|
99
|
+
fail_closed=True,
|
|
100
|
+
)
|
|
91
101
|
|
|
92
|
-
|
|
93
|
-
withAPortGuardrail(() => {
|
|
94
|
-
crew.kickoff();
|
|
95
|
-
});
|
|
102
|
+
crew.kickoff()
|
|
96
103
|
```
|
|
97
104
|
|
|
98
|
-
|
|
105
|
+
If you bootstrap into a project-local config directory, point `config_path` at that
|
|
106
|
+
file instead.
|
|
107
|
+
|
|
108
|
+
## What The Bootstrap Installs
|
|
99
109
|
|
|
100
|
-
|
|
110
|
+
Both modes write:
|
|
101
111
|
|
|
102
|
-
|
|
112
|
+
- `~/.aport/crewai/config.yaml`
|
|
113
|
+
- `~/.aport/crewai/aport/passport.json`
|
|
114
|
+
- `~/.aport/crewai/aport/runtime/...`
|
|
103
115
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
- **`fail_open_on_api_error`**: Set to `true` in config to allow tool execution when the APort API is unreachable (genuine policy denials are never overridden). Default: `false` (fail-closed).
|
|
116
|
+
The compatibility and native modes share the same APort config and local runtime.
|
|
117
|
+
Only the CrewAI integration layer changes.
|
|
107
118
|
|
|
108
|
-
##
|
|
119
|
+
## How APort Fits
|
|
109
120
|
|
|
110
|
-
|
|
121
|
+
- **Compatibility mode:** APort adapts to CrewAI's existing hook surface.
|
|
122
|
+
- **Native mode:** CrewAI defines the seam, and APort plugs in as an external
|
|
123
|
+
`OAPGuardrailProvider`.
|
|
111
124
|
|
|
112
|
-
|
|
125
|
+
That keeps CrewAI vendor-neutral while letting Open Agent Passport remain the
|
|
126
|
+
portable policy/passport format across frameworks.
|
|
113
127
|
|
|
114
|
-
|
|
115
|
-
- **Unit tests:** [python/crewai_adapter/tests/test_hook.py](../../python/crewai_adapter/tests/test_hook.py) β hook return value and context with mocked evaluator.
|
|
128
|
+
## Configuration
|
|
116
129
|
|
|
117
|
-
|
|
130
|
+
The provider and adapter read the standard APort config:
|
|
131
|
+
|
|
132
|
+
- `mode: local` for the local shell evaluator
|
|
133
|
+
- `mode: api` for hosted evaluation
|
|
134
|
+
- `agent_id` for hosted passports
|
|
135
|
+
- `passport_path` for explicit local passport paths
|
|
136
|
+
- `guardrail_script` to override the local evaluator script path
|
|
137
|
+
- `audit_log` to enable or disable audit logging
|
|
138
|
+
|
|
139
|
+
With the default bootstrap, you usually do not need to set `guardrail_script`
|
|
140
|
+
manually because the local runtime is installed under the framework config
|
|
141
|
+
directory.
|
|
142
|
+
|
|
143
|
+
## Validation
|
|
144
|
+
|
|
145
|
+
After bootstrap, you can smoke-test the local evaluator directly:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
~/.aport/crewai/aport/runtime/bin/aport-guardrail.sh \
|
|
149
|
+
system.command.execute \
|
|
150
|
+
'{"command":"git status"}'
|
|
151
|
+
```
|
|
118
152
|
|
|
119
|
-
|
|
153
|
+
Exit code `0` means allow. Exit code `1` means deny.
|
|
@@ -107,7 +107,7 @@ guardrails:
|
|
|
107
107
|
| `bash`, `write_file`, `str_replace` | `system.command.execute.v1` |
|
|
108
108
|
| `web_search`, `web_fetch`, `image_search` | `web.fetch.v1` |
|
|
109
109
|
| `read_file`, `ls` | `data.file.read.v1` |
|
|
110
|
-
| `present_file`, `view_image` | `data.
|
|
110
|
+
| `present_file`, `view_image` | `data.file.read.v1` |
|
|
111
111
|
| `ask_clarification`, `task` | `agent.session.create.v1` |
|
|
112
112
|
| MCP tools (dynamic) | `mcp.tool.execute.v1` |
|
|
113
113
|
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# Changelog - APort OpenClaw Plugin
|
|
2
2
|
|
|
3
|
+
## 1.0.21
|
|
4
|
+
|
|
3
5
|
All notable changes to the APort OpenClaw plugin will be documented in this file.
|
|
4
6
|
|
|
5
|
-
## [1.
|
|
7
|
+
## [1.0.21] - 2026-02-19
|
|
6
8
|
|
|
7
9
|
### Changed - OpenClaw 2026.2 Compatibility
|
|
8
10
|
|
|
@@ -24,6 +26,7 @@ All notable changes to the APort OpenClaw plugin will be documented in this file
|
|
|
24
26
|
### Technical Details
|
|
25
27
|
|
|
26
28
|
**Old Architecture (< 2026.2):**
|
|
29
|
+
|
|
27
30
|
```javascript
|
|
28
31
|
export default function (api) {
|
|
29
32
|
api.on("before_tool_call", async (event, ctx) => { ... });
|
|
@@ -31,6 +34,7 @@ export default function (api) {
|
|
|
31
34
|
```
|
|
32
35
|
|
|
33
36
|
**New Architecture (>= 2026.2):**
|
|
37
|
+
|
|
34
38
|
```typescript
|
|
35
39
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
36
40
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "openclaw-aport",
|
|
3
3
|
"name": "APort Guardrails",
|
|
4
4
|
"description": "Deterministic pre-action authorization via APort policy enforcement. Registers before_tool_call to block disallowed tools.",
|
|
5
|
-
"version": "1.0.
|
|
5
|
+
"version": "1.0.21",
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
|
8
8
|
"additionalProperties": false,
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aporthq/openclaw-aport",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.21",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@aporthq/openclaw-aport",
|
|
9
|
-
"version": "1.0.
|
|
9
|
+
"version": "1.0.21",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"@types/node": "^18.0.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aporthq/aport-agent-guardrails",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.21",
|
|
4
4
|
"description": "Policy enforcement guardrails for OpenClaw-compatible agent frameworks",
|
|
5
5
|
"workspaces": [
|
|
6
6
|
"packages/*",
|
|
@@ -20,8 +20,9 @@
|
|
|
20
20
|
"sync-submodules": "git submodule update --init --recursive",
|
|
21
21
|
"sync-submodules:latest": "git submodule update --init --remote --recursive",
|
|
22
22
|
"changeset": "changeset",
|
|
23
|
-
"version": "changeset version && node scripts/sync-version.mjs",
|
|
23
|
+
"version": "CI=1 changeset version && node scripts/sync-version.mjs",
|
|
24
24
|
"sync-version": "node scripts/sync-version.mjs",
|
|
25
|
+
"prepush:check": "bash scripts/pre-push-check.sh",
|
|
25
26
|
"build": "npm run build:core && npm run build --workspaces --if-present",
|
|
26
27
|
"build:core": "npm run build -w @aporthq/aport-agent-guardrails-core",
|
|
27
28
|
"release:version": "npm run version",
|