@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.
Files changed (42) hide show
  1. package/README.md +16 -15
  2. package/bin/agent-guardrails +57 -17
  3. package/bin/aport-create-passport.sh +1 -1
  4. package/bin/aport-guardrail-api.sh +8 -35
  5. package/bin/aport-guardrail-bash.sh +18 -53
  6. package/bin/aport-resolve-paths.sh +2 -2
  7. package/bin/frameworks/generic.sh +59 -3
  8. package/bin/frameworks/next-steps.d/crewai-native.txt +21 -0
  9. package/bin/frameworks/next-steps.d/crewai.txt +7 -2
  10. package/bin/lib/error.sh +3 -3
  11. package/bin/lib/runtime-manifest.txt +17 -0
  12. package/bin/lib/runtime.sh +81 -0
  13. package/bin/lib/tool-mapping.sh +52 -0
  14. package/bin/lib/validation.sh +25 -15
  15. package/docs/FRAMEWORK_ROADMAP.md +1 -1
  16. package/docs/PROVIDER.md +90 -0
  17. package/docs/RELEASE.md +3 -3
  18. package/docs/development/ERROR_CODES.md +3 -3
  19. package/docs/frameworks/crewai.md +102 -68
  20. package/docs/frameworks/deerflow.md +1 -1
  21. package/docs/frameworks/openclaw.md +51 -22
  22. package/extensions/openclaw-aport/CHANGELOG.md +5 -1
  23. package/extensions/openclaw-aport/openclaw.plugin.json +1 -1
  24. package/extensions/openclaw-aport/package-lock.json +2 -2
  25. package/extensions/openclaw-aport/package.json +1 -1
  26. package/external/aport-spec/LICENSE +1 -1
  27. package/external/aport-spec/README.md +1 -0
  28. package/external/aport-spec/conformance/src/ed25519.ts +1 -1
  29. package/external/aport-spec/oap/CHANGELOG.md +1 -1
  30. package/external/aport-spec/oap/conformance.md +2 -2
  31. package/external/aport-spec/oap/oap-spec.md +2 -2
  32. package/external/aport-spec/oap/security.md +4 -4
  33. package/external/aport-spec/oap/vc/examples/oap-decision-vc.json +1 -1
  34. package/external/aport-spec/oap/vc/examples/oap-passport-vc.json +1 -1
  35. package/external/aport-spec/oap/vc/tools/examples/vc-to-decision.js +1 -1
  36. package/external/aport-spec/oap/vc/tools/examples/vc-to-passport.js +1 -1
  37. package/external/aport-spec/oap/vc/tools/src/index.ts +2 -2
  38. package/external/aport-spec/oap/vc/tools/test-simple.js +2 -2
  39. package/external/aport-spec/oap/vc/vc-mapping.md +4 -4
  40. package/external/aport-spec/oap/well-known-schema.json +85 -0
  41. package/external/aport-spec/well-known.md +203 -0
  42. 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
+ }
@@ -58,9 +58,9 @@ validate_tool_name() {
58
58
  return 0
59
59
  }
60
60
 
61
- # Validate passport path is within allowed directories
62
- # Returns 0 if safe, 1 if potentially dangerous
63
- validate_passport_path() {
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 passport storage
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 only:** hook, decorator, `aport-crewai setup` | [crewai.md](frameworks/crewai.md) | `npx @aporthq/aport-agent-guardrails crewai` then `pip install aport-agent-guardrails-crewai` + `aport-crewai setup` |
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
 
@@ -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.18 (see [CHANGELOG.md](../CHANGELOG.md)).
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`, copies the new version from root `package.json` into all Python `pyproject.toml` and `aport_guardrails/__init__.py`.
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` (updates all Node `package.json` and CHANGELOGs) then `node scripts/sync-version.mjs` (updates Python packages to the same 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 guardrail script exists
522
- ls -la ~/.openclaw/.skills/aport-guardrail.sh
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 Guardrail — CrewAI
1
+ # APort Agent Guardrails — CrewAI
2
2
 
3
- CrewAI supports **tool call hooks** that run before (and after) every tool execution. The **APort Agent Guardrail for CrewAI** plugs into the **before tool call** hook: we verify the tool and parameters against your passport and policy; if the decision is deny, we return `False` and CrewAI blocks execution. This matches CrewAI’s [Tool Call Hooks](https://docs.crewai.com/en/learn/tool-hooks) model.
3
+ APort supports CrewAI in two ways:
4
4
 
5
- ## How CrewAI agent guardrails work
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
- - **Hooks:** CrewAI runs **before_tool_call** hooks before every tool execution. The hook receives a `ToolCallHookContext` (tool name, tool input, agent, task, crew). Returning `False` blocks execution; `True` or `None` allows it.
8
- - **Registration:** You can register a hook **globally** with `register_before_tool_call_hook()`, or use the `@before_tool_call` decorator, or use **crew-scoped** `@before_tool_call_crew` on a `@CrewBase` class.
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
- Our adapter provides a function that fits this API: it calls the APort evaluator (sync) and returns `False` on deny, `None` on allow. You register it once before running your crew.
11
+ ## Option 1: Released CrewAI Compatibility Mode
12
12
 
13
- - **Integration:** CrewAI `before_tool_call` hook (global or crew-scoped)
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
- ## Two ways to use APort
15
+ Bootstrap config, passport, and local runtime with the Python-native CLI:
17
16
 
18
- | Use case | What it is | When to use it |
19
- |----------|------------|----------------|
20
- | **Guardrails (CLI/setup)** | One-line installer: runs the **passport wizard**, writes config, prints next steps. Does not run your app. | Getting started: create passport and config so the library can find them. |
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
- ## Setup (Guardrails — create passport and config)
23
+ ```bash
24
+ npx -y @aporthq/aport-agent-guardrails crewai
25
+ ```
28
26
 
29
- **Python**
27
+ For CI or other non-interactive environments with the Python-native CLI:
30
28
 
31
29
  ```bash
32
- npx @aporthq/aport-agent-guardrails crewai # wizard + config (optional)
33
- pip install aport-agent-guardrails-crewai
34
- aport-crewai setup
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
- **Node**
36
+ The equivalent Node bootstrap is:
38
37
 
39
38
  ```bash
40
- npx @aporthq/aport-agent-guardrails crewai # wizard + config
41
- npm install @aporthq/aport-agent-guardrails-crewai # beforeToolCall, withAPortGuardrail (depends on -core)
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
- `aport-crewai setup` (Python) writes config to `~/.aport/crewai/`, runs the passport wizard (or use `--ci` / `--no-wizard` for non-interactive), and prints next steps.
45
+ Install the released CrewAI adapter:
45
46
 
46
- ## Using the library (Core) in your app
47
+ ```bash
48
+ pip install aport-agent-guardrails-crewai
49
+ aport-crewai setup
50
+ ```
47
51
 
48
- **Python — Option 1: Register the hook before kickoff**
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
- **Python Option 2: Decorator on your entry point**
61
+ This path works with released CrewAI because it plugs directly into CrewAI's existing
62
+ `before_tool_call` hook behavior.
58
63
 
59
- ```python
60
- from aport_guardrails_crewai import with_aport_guardrail
64
+ ## Option 2: Native Provider Mode
61
65
 
62
- @with_aport_guardrail
63
- def main():
64
- crew.kickoff()
66
+ Use this only with a CrewAI build that exposes the native guardrail provider API.
65
67
 
66
- main()
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
- **Python Option 3: Use the hook with `@before_tool_call`**
76
+ The equivalent Node bootstrap is:
70
77
 
71
- ```python
72
- from crewai.hooks import before_tool_call
73
- from aport_guardrails_crewai import aport_guardrail_before_tool_call
78
+ ```bash
79
+ npx -y @aporthq/aport-agent-guardrails crewai --integration-mode=native
80
+ ```
74
81
 
75
- @before_tool_call
76
- def my_guardrail(context):
77
- return aport_guardrail_before_tool_call(context)
82
+ Install the Python runtime package:
83
+
84
+ ```bash
85
+ uv add aport-agent-guardrails
78
86
  ```
79
87
 
80
- **Node:** Call `beforeToolCall` in your flow before each tool run (CrewAI Node SDK does not expose a global hook). Return `false` to block, `null` to allow. Or wrap your entry point with `withAPortGuardrail(fn)`.
88
+ Enable the provider before your crew runs:
81
89
 
82
- ```ts
83
- import { beforeToolCall, withAPortGuardrail } from '@aporthq/aport-agent-guardrails-crewai';
90
+ ```python
91
+ from crewai.hooks import enable_guardrail
92
+ from aport_guardrails.providers import OAPGuardrailProvider
84
93
 
85
- // In your tool-call flow, before executing a tool:
86
- const result = beforeToolCall({ tool_name: 'run_command', tool_input: { command: 'ls' } });
87
- if (result === false) {
88
- // Block this tool call
89
- return;
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
- // Or wrap your crew kickoff so guardrail is in scope:
93
- withAPortGuardrail(() => {
94
- crew.kickoff();
95
- });
102
+ crew.kickoff()
96
103
  ```
97
104
 
98
- ### How tool parameters are handled
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
- The Node middleware automatically spreads object tool inputs into the verification context. For example, `{ tool_input: { file_path: "/tmp/data.txt" } }` will pass `file_path` at the top level of the context, ensuring policies like `data.file.read.v1` receive required fields for validation.
110
+ Both modes write:
101
111
 
102
- ## Config
112
+ - `~/.aport/crewai/config.yaml`
113
+ - `~/.aport/crewai/aport/passport.json`
114
+ - `~/.aport/crewai/aport/runtime/...`
103
115
 
104
- - **Config path:** `~/.aport/crewai/config.yaml`, or `.aport/config.yaml` in the project root.
105
- - **Mode:** `api` (default for production) or `local` (bash evaluator, no network). Same options as [LangChain](langchain.md) and OpenClaw.
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
- ## Suspend (kill switch)
119
+ ## How APort Fits
109
120
 
110
- Same as all frameworks: **passport is the source of truth**. Local: set passport `status` to `suspended` (or `active` to resume). API: suspend the passport in [APort](https://aport.io); all agents using that passport deny within ≤30s.
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
- ## Example and tests
125
+ That keeps CrewAI vendor-neutral while letting Open Agent Passport remain the
126
+ portable policy/passport format across frameworks.
113
127
 
114
- - **Example:** [examples/crewai/run_with_guardrail.py](../../examples/crewai/run_with_guardrail.py) — temp config, ALLOW then DENY, `register_aport_guardrail()`.
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
- ## Status
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
- Implemented (Story D). **APort Agent Guardrail for CrewAI.** Package: `aport-agent-guardrails-crewai`; CLI: `aport-crewai setup`; hook: `aport_guardrail_before_tool_call` / `register_aport_guardrail` / `with_aport_guardrail`.
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.export.create.v1` |
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