@aporthq/aport-agent-guardrails 1.0.19 → 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 +16 -15
- 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 +90 -0
- 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/docs/frameworks/openclaw.md +51 -22
- 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/external/aport-spec/LICENSE +1 -1
- package/external/aport-spec/README.md +1 -0
- package/external/aport-spec/conformance/src/ed25519.ts +1 -1
- package/external/aport-spec/oap/CHANGELOG.md +1 -1
- package/external/aport-spec/oap/conformance.md +2 -2
- package/external/aport-spec/oap/oap-spec.md +2 -2
- package/external/aport-spec/oap/security.md +4 -4
- package/external/aport-spec/oap/vc/examples/oap-decision-vc.json +1 -1
- package/external/aport-spec/oap/vc/examples/oap-passport-vc.json +1 -1
- package/external/aport-spec/oap/vc/tools/examples/vc-to-decision.js +1 -1
- package/external/aport-spec/oap/vc/tools/examples/vc-to-passport.js +1 -1
- package/external/aport-spec/oap/vc/tools/src/index.ts +2 -2
- package/external/aport-spec/oap/vc/tools/test-simple.js +2 -2
- package/external/aport-spec/oap/vc/vc-mapping.md +4 -4
- package/external/aport-spec/oap/well-known-schema.json +85 -0
- package/external/aport-spec/well-known.md +203 -0
- package/package.json +3 -2
|
@@ -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
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# OAPGuardrailProvider
|
|
2
|
+
|
|
3
|
+
APort ships a generic `OAPGuardrailProvider` for both Python and TypeScript. It implements whatever `GuardrailProvider` interface the target framework defines, wraps the core `Evaluator`, and works with any framework — no framework-specific code.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Framework defines interface APort implements it
|
|
9
|
+
──────────────────────── ───────────────────
|
|
10
|
+
DeerFlow: GuardrailProvider ←── Python OAPGuardrailProvider
|
|
11
|
+
OpenClaw: GuardrailProvider ←── TypeScript OAPGuardrailProvider
|
|
12
|
+
Your framework: same shape ←── Same class, same language
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
One provider per language. The core evaluation logic (tool mapping, passport loading, local/API mode, audit logging) is shared.
|
|
16
|
+
|
|
17
|
+
## Python
|
|
18
|
+
|
|
19
|
+
**Package:** `aport-agent-guardrails` (pip/uv)
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from aport_guardrails.providers import OAPGuardrailProvider
|
|
23
|
+
|
|
24
|
+
provider = OAPGuardrailProvider(framework="deerflow")
|
|
25
|
+
result = provider.evaluate(request) # sync
|
|
26
|
+
result = await provider.aevaluate(request) # async
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Supported frameworks:** DeerFlow, CrewAI builds with native provider support, and any Python framework with a `GuardrailProvider` protocol.
|
|
30
|
+
|
|
31
|
+
**Config:** `~/.aport/<framework>/config.yaml` (created by `aport setup --framework <name>`)
|
|
32
|
+
|
|
33
|
+
**DeerFlow config.yaml:**
|
|
34
|
+
```yaml
|
|
35
|
+
guardrails:
|
|
36
|
+
enabled: true
|
|
37
|
+
provider:
|
|
38
|
+
use: aport_guardrails.providers.generic:OAPGuardrailProvider
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## TypeScript
|
|
42
|
+
|
|
43
|
+
**Package:** `@aporthq/aport-agent-guardrails-core` (npm)
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import { OAPGuardrailProvider } from "@aporthq/aport-agent-guardrails-core";
|
|
47
|
+
|
|
48
|
+
const provider = new OAPGuardrailProvider({ framework: "openclaw" });
|
|
49
|
+
const decision = await provider.evaluate(request); // async
|
|
50
|
+
const decision = provider.evaluateSync(request); // sync
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Supported frameworks:** OpenClaw, any TypeScript framework with a `GuardrailProvider` interface.
|
|
54
|
+
|
|
55
|
+
**Config:** `~/.openclaw/aport/config.yaml` or `~/.aport/<framework>/config.yaml`
|
|
56
|
+
|
|
57
|
+
**OpenClaw config.yaml:**
|
|
58
|
+
```yaml
|
|
59
|
+
guardrails:
|
|
60
|
+
enabled: true
|
|
61
|
+
provider:
|
|
62
|
+
use: "@aporthq/aport-agent-guardrails-core"
|
|
63
|
+
config:
|
|
64
|
+
framework: "openclaw"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## What the provider does
|
|
68
|
+
|
|
69
|
+
1. **Receives** `{ toolName, toolInput }` from the framework
|
|
70
|
+
2. **Maps** tool name → OAP policy pack ID (e.g. `exec` → `system.command.execute.v1`)
|
|
71
|
+
3. **Loads** passport from local file or hosted agent ID
|
|
72
|
+
4. **Evaluates** locally (bash script, zero network) or via API (hosted evaluator)
|
|
73
|
+
5. **Returns** `{ allow, reasons, policyId }` to the framework
|
|
74
|
+
|
|
75
|
+
## Evaluation modes
|
|
76
|
+
|
|
77
|
+
| Mode | Network | Latency | Use case |
|
|
78
|
+
|---|---|---|---|
|
|
79
|
+
| **Local** (default) | None | ~300ms | Development, CI, air-gapped |
|
|
80
|
+
| **API** | Yes | ~65ms | Production, signed decisions, centralized dashboards |
|
|
81
|
+
|
|
82
|
+
## Adding a new framework
|
|
83
|
+
|
|
84
|
+
1. Check if the framework has a `GuardrailProvider` interface (or equivalent hook)
|
|
85
|
+
2. Use the existing Python or TypeScript `OAPGuardrailProvider` — it's framework-agnostic
|
|
86
|
+
3. Point the framework config at the provider class/package
|
|
87
|
+
4. Run `npx @aporthq/aport-agent-guardrails <framework>` to create a passport
|
|
88
|
+
5. Add a doc at `docs/frameworks/<framework>.md`
|
|
89
|
+
|
|
90
|
+
No new provider code needed. The same `OAPGuardrailProvider` works for any framework in the same language.
|
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
|
|