predicate-claw 0.1.0
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/.github/workflows/release.yml +76 -0
- package/.github/workflows/tests.yml +34 -0
- package/.markdownlint.yaml +5 -0
- package/.pre-commit-config.yaml +100 -0
- package/README.md +405 -0
- package/dist/src/adapter.d.ts +17 -0
- package/dist/src/adapter.js +36 -0
- package/dist/src/authority-client.d.ts +21 -0
- package/dist/src/authority-client.js +22 -0
- package/dist/src/circuit-breaker.d.ts +86 -0
- package/dist/src/circuit-breaker.js +174 -0
- package/dist/src/config.d.ts +8 -0
- package/dist/src/config.js +7 -0
- package/dist/src/control-plane-sync.d.ts +57 -0
- package/dist/src/control-plane-sync.js +99 -0
- package/dist/src/errors.d.ts +6 -0
- package/dist/src/errors.js +6 -0
- package/dist/src/index.d.ts +12 -0
- package/dist/src/index.js +12 -0
- package/dist/src/non-web-evidence.d.ts +46 -0
- package/dist/src/non-web-evidence.js +54 -0
- package/dist/src/openclaw-hooks.d.ts +27 -0
- package/dist/src/openclaw-hooks.js +54 -0
- package/dist/src/openclaw-plugin-api.d.ts +18 -0
- package/dist/src/openclaw-plugin-api.js +17 -0
- package/dist/src/provider.d.ts +48 -0
- package/dist/src/provider.js +154 -0
- package/dist/src/runtime-integration.d.ts +20 -0
- package/dist/src/runtime-integration.js +43 -0
- package/dist/src/web-evidence.d.ts +48 -0
- package/dist/src/web-evidence.js +49 -0
- package/dist/tests/adapter.test.d.ts +1 -0
- package/dist/tests/adapter.test.js +63 -0
- package/dist/tests/audit-event-e2e.test.d.ts +1 -0
- package/dist/tests/audit-event-e2e.test.js +209 -0
- package/dist/tests/authority-client.test.d.ts +1 -0
- package/dist/tests/authority-client.test.js +46 -0
- package/dist/tests/circuit-breaker.test.d.ts +1 -0
- package/dist/tests/circuit-breaker.test.js +200 -0
- package/dist/tests/control-plane-sync.test.d.ts +1 -0
- package/dist/tests/control-plane-sync.test.js +90 -0
- package/dist/tests/hack-vs-fix-demo.test.d.ts +1 -0
- package/dist/tests/hack-vs-fix-demo.test.js +36 -0
- package/dist/tests/jwks-rotation.test.d.ts +1 -0
- package/dist/tests/jwks-rotation.test.js +232 -0
- package/dist/tests/load-latency.test.d.ts +1 -0
- package/dist/tests/load-latency.test.js +175 -0
- package/dist/tests/multi-tenant-isolation.test.d.ts +1 -0
- package/dist/tests/multi-tenant-isolation.test.js +146 -0
- package/dist/tests/non-web-evidence.test.d.ts +1 -0
- package/dist/tests/non-web-evidence.test.js +139 -0
- package/dist/tests/openclaw-hooks.test.d.ts +1 -0
- package/dist/tests/openclaw-hooks.test.js +38 -0
- package/dist/tests/openclaw-plugin-api.test.d.ts +1 -0
- package/dist/tests/openclaw-plugin-api.test.js +40 -0
- package/dist/tests/provider.test.d.ts +1 -0
- package/dist/tests/provider.test.js +190 -0
- package/dist/tests/runtime-integration.test.d.ts +1 -0
- package/dist/tests/runtime-integration.test.js +57 -0
- package/dist/tests/web-evidence.test.d.ts +1 -0
- package/dist/tests/web-evidence.test.js +89 -0
- package/docs/MIGRATION_GUIDE.md +405 -0
- package/docs/OPERATIONAL_RUNBOOK.md +389 -0
- package/docs/PRODUCTION_READINESS.md +134 -0
- package/docs/SLO_THRESHOLDS.md +193 -0
- package/examples/README.md +171 -0
- package/examples/docker/Dockerfile.test +16 -0
- package/examples/docker/README.md +48 -0
- package/examples/docker/docker-compose.test.yml +16 -0
- package/examples/non-web-evidence-demo.ts +184 -0
- package/examples/openclaw-plugin-smoke/index.ts +30 -0
- package/examples/openclaw-plugin-smoke/openclaw.plugin.json +11 -0
- package/examples/openclaw-plugin-smoke/package.json +9 -0
- package/examples/openclaw_integration_example.py +41 -0
- package/examples/policy/README.md +165 -0
- package/examples/policy/approved-hosts.yaml +137 -0
- package/examples/policy/dev-workflow.yaml +206 -0
- package/examples/policy/policy.example.yaml +17 -0
- package/examples/policy/production-strict.yaml +97 -0
- package/examples/policy/sensitive-paths.yaml +114 -0
- package/examples/policy/source-trust.yaml +129 -0
- package/examples/policy/workspace-isolation.yaml +51 -0
- package/examples/runtime_registry_example.py +75 -0
- package/package.json +27 -0
- package/pyproject.toml +41 -0
- package/src/adapter.ts +45 -0
- package/src/authority-client.ts +50 -0
- package/src/circuit-breaker.ts +245 -0
- package/src/config.ts +15 -0
- package/src/control-plane-sync.ts +159 -0
- package/src/errors.ts +5 -0
- package/src/index.ts +12 -0
- package/src/non-web-evidence.ts +116 -0
- package/src/openclaw-hooks.ts +76 -0
- package/src/openclaw-plugin-api.ts +51 -0
- package/src/openclaw_predicate_provider/__init__.py +16 -0
- package/src/openclaw_predicate_provider/__main__.py +5 -0
- package/src/openclaw_predicate_provider/adapter.py +84 -0
- package/src/openclaw_predicate_provider/agentidentity_backend.py +78 -0
- package/src/openclaw_predicate_provider/cli.py +160 -0
- package/src/openclaw_predicate_provider/config.py +42 -0
- package/src/openclaw_predicate_provider/errors.py +13 -0
- package/src/openclaw_predicate_provider/integrations/__init__.py +5 -0
- package/src/openclaw_predicate_provider/integrations/openclaw_runtime.py +74 -0
- package/src/openclaw_predicate_provider/models.py +19 -0
- package/src/openclaw_predicate_provider/openclaw_hooks.py +75 -0
- package/src/openclaw_predicate_provider/provider.py +69 -0
- package/src/openclaw_predicate_provider/py.typed +1 -0
- package/src/openclaw_predicate_provider/sidecar.py +59 -0
- package/src/provider.ts +220 -0
- package/src/runtime-integration.ts +68 -0
- package/src/web-evidence.ts +95 -0
- package/tests/adapter.test.ts +76 -0
- package/tests/audit-event-e2e.test.ts +258 -0
- package/tests/authority-client.test.ts +52 -0
- package/tests/circuit-breaker.test.ts +266 -0
- package/tests/conftest.py +9 -0
- package/tests/control-plane-sync.test.ts +114 -0
- package/tests/hack-vs-fix-demo.test.ts +44 -0
- package/tests/jwks-rotation.test.ts +274 -0
- package/tests/load-latency.test.ts +214 -0
- package/tests/multi-tenant-isolation.test.ts +183 -0
- package/tests/non-web-evidence.test.ts +168 -0
- package/tests/openclaw-hooks.test.ts +46 -0
- package/tests/openclaw-plugin-api.test.ts +50 -0
- package/tests/provider.test.ts +227 -0
- package/tests/runtime-integration.test.ts +70 -0
- package/tests/test_adapter.py +46 -0
- package/tests/test_cli.py +26 -0
- package/tests/test_openclaw_hooks.py +53 -0
- package/tests/test_provider.py +59 -0
- package/tests/test_runtime_integration.py +77 -0
- package/tests/test_sidecar_client.py +198 -0
- package/tests/web-evidence.test.ts +113 -0
- package/tsconfig.json +14 -0
- package/vitest.config.ts +7 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# OpenClaw Predicate Provider Examples
|
|
2
|
+
|
|
3
|
+
This directory contains examples and test harnesses for the OpenClaw Predicate Provider.
|
|
4
|
+
|
|
5
|
+
## Hack vs Fix Demo
|
|
6
|
+
|
|
7
|
+
This demo shows the OpenClaw prompt-injection risk path and the
|
|
8
|
+
`PredicateClaw` protection path.
|
|
9
|
+
|
|
10
|
+
### Goal
|
|
11
|
+
|
|
12
|
+
Demonstrate that:
|
|
13
|
+
|
|
14
|
+
1. An unguarded tool call can read a sensitive file when prompted from an
|
|
15
|
+
untrusted source, and
|
|
16
|
+
2. The Predicate-guarded path blocks the same action with deterministic policy.
|
|
17
|
+
|
|
18
|
+
### Scenario in Plain Language
|
|
19
|
+
|
|
20
|
+
- **Hack path:** Injected context (`source: untrusted_dm`) attempts
|
|
21
|
+
`fs.read` on `~/.ssh/id_rsa` and succeeds when unguarded.
|
|
22
|
+
- **Fix path:** Same action goes through `GuardedProvider + ToolAdapter`,
|
|
23
|
+
maps to Predicate action/resource contract, and receives deny decision.
|
|
24
|
+
|
|
25
|
+
### Fast Local Run
|
|
26
|
+
|
|
27
|
+
From `PredicateClaw/`:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm test -- tests/hack-vs-fix-demo.test.ts
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Expected:
|
|
34
|
+
|
|
35
|
+
- Test passes
|
|
36
|
+
- Unguarded branch returns sensitive payload string
|
|
37
|
+
- Guarded branch throws `ActionDeniedError` with deny reason
|
|
38
|
+
`deny_sensitive_read_from_untrusted_context`
|
|
39
|
+
|
|
40
|
+
## Docker Adversarial Testing
|
|
41
|
+
|
|
42
|
+
### Why Docker?
|
|
43
|
+
|
|
44
|
+
Running adversarial tests (simulating prompt injection attacks like "read my
|
|
45
|
+
SSH keys" or "curl malware") directly on your machine is risky. If the provider
|
|
46
|
+
has a bug, the attack could execute. Docker isolates failures to the container.
|
|
47
|
+
|
|
48
|
+
### Quick Start
|
|
49
|
+
|
|
50
|
+
From the `PredicateClaw/` directory:
|
|
51
|
+
|
|
52
|
+
**Option 1: Docker Compose (recommended)**
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Run the "Hack vs Fix" demo test
|
|
56
|
+
docker compose -f examples/docker/docker-compose.test.yml run --rm provider-demo
|
|
57
|
+
|
|
58
|
+
# Run full CI checks (typecheck + all tests)
|
|
59
|
+
docker compose -f examples/docker/docker-compose.test.yml run --rm provider-ci
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Option 2: Build and run directly**
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# Build the test image
|
|
66
|
+
docker build -t openclaw-provider-test -f examples/docker/Dockerfile.test .
|
|
67
|
+
|
|
68
|
+
# Run demo test
|
|
69
|
+
docker run --rm -it openclaw-provider-test npm run test:demo
|
|
70
|
+
|
|
71
|
+
# Run full CI
|
|
72
|
+
docker run --rm -it openclaw-provider-test npm run test:ci
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Expected Output
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
RUN v4.x.x /app
|
|
79
|
+
|
|
80
|
+
✓ tests/hack-vs-fix-demo.test.ts (1 test)
|
|
81
|
+
✓ shows unguarded exfil path and guarded deny path
|
|
82
|
+
|
|
83
|
+
Test Files 1 passed (1)
|
|
84
|
+
Tests 1 passed (1)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The test verifies:
|
|
88
|
+
|
|
89
|
+
- Unguarded call returns sensitive data
|
|
90
|
+
- Guarded call throws `ActionDeniedError`
|
|
91
|
+
- Deny reason is stable and auditable
|
|
92
|
+
|
|
93
|
+
### Testing with a Live Sidecar
|
|
94
|
+
|
|
95
|
+
To test against a real `predicate-authorityd` sidecar (not mocked):
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# Start sidecar on host
|
|
99
|
+
predicate-authorityd --port 9090
|
|
100
|
+
|
|
101
|
+
# Run container with host network access
|
|
102
|
+
docker run --rm -it --network=host openclaw-provider-test npm test
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The `--network=host` lets the container reach `localhost:9090` where your
|
|
106
|
+
sidecar runs.
|
|
107
|
+
|
|
108
|
+
## Video Recording Checklist (for Launch Asset)
|
|
109
|
+
|
|
110
|
+
1. Show baseline unguarded action succeeds for sensitive read.
|
|
111
|
+
2. Show guarded provider enabled with identical prompt/context.
|
|
112
|
+
3. Show deny result and user-facing blocked message.
|
|
113
|
+
4. Show test command and green output as reproducible evidence.
|
|
114
|
+
|
|
115
|
+
## Non-Web Evidence Provider Demo
|
|
116
|
+
|
|
117
|
+
Demonstrates terminal and desktop accessibility evidence providers with canonical
|
|
118
|
+
hashing for reproducible `state_hash` computation.
|
|
119
|
+
|
|
120
|
+
### Run the Demo
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
npx tsx examples/non-web-evidence-demo.ts
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### What It Shows
|
|
127
|
+
|
|
128
|
+
1. **Terminal Evidence** - Captures command-line state with:
|
|
129
|
+
- Path normalization (`/workspace/./src/../src` → `/workspace/src`)
|
|
130
|
+
- Whitespace collapsing (`git status` → `git status`)
|
|
131
|
+
- ANSI code stripping (removes color codes)
|
|
132
|
+
- Timestamp normalization (`[12:34:56]` → `[TIMESTAMP]`)
|
|
133
|
+
- Secret redaction (environment variables like `AWS_SECRET_KEY`)
|
|
134
|
+
|
|
135
|
+
2. **Desktop Evidence** - Captures accessibility tree state with:
|
|
136
|
+
- App name normalization
|
|
137
|
+
- UI tree text normalization
|
|
138
|
+
- Whitespace handling
|
|
139
|
+
|
|
140
|
+
3. **Hash Stability** - Proves that minor variations produce identical hashes
|
|
141
|
+
when canonicalization is enabled.
|
|
142
|
+
|
|
143
|
+
### API Usage
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
import {
|
|
147
|
+
OpenClawTerminalEvidenceProvider,
|
|
148
|
+
buildTerminalEvidenceFromProvider,
|
|
149
|
+
} from "predicate-claw";
|
|
150
|
+
|
|
151
|
+
const provider = new OpenClawTerminalEvidenceProvider(() => ({
|
|
152
|
+
sessionId: "my-session",
|
|
153
|
+
cwd: process.cwd(),
|
|
154
|
+
command: "npm test",
|
|
155
|
+
transcript: "...",
|
|
156
|
+
}));
|
|
157
|
+
|
|
158
|
+
const evidence = await buildTerminalEvidenceFromProvider(provider, {
|
|
159
|
+
useCanonicalHash: true, // default
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
console.log(evidence.state_hash); // sha256:...
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Other Examples
|
|
166
|
+
|
|
167
|
+
- `openclaw_integration_example.py` - Python integration example
|
|
168
|
+
- `runtime_registry_example.py` - Runtime registration example
|
|
169
|
+
- `openclaw-plugin-smoke/` - OpenClaw plugin smoke test
|
|
170
|
+
- `policy/` - Example policy files
|
|
171
|
+
- `docker/` - Docker test harness files
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
FROM node:22-slim
|
|
2
|
+
|
|
3
|
+
WORKDIR /app
|
|
4
|
+
|
|
5
|
+
# Install Node dependencies first for better layer caching.
|
|
6
|
+
COPY package.json package-lock.json ./
|
|
7
|
+
RUN npm ci
|
|
8
|
+
|
|
9
|
+
# Copy TypeScript sources, tests, and examples used by demo/test scripts.
|
|
10
|
+
COPY tsconfig.json vitest.config.ts ./
|
|
11
|
+
COPY src ./src
|
|
12
|
+
COPY tests ./tests
|
|
13
|
+
COPY examples ./examples
|
|
14
|
+
|
|
15
|
+
# Default to the reproducible Hack-vs-Fix demo test.
|
|
16
|
+
CMD ["npm", "run", "test:demo"]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Docker Adversarial Test Harness
|
|
2
|
+
|
|
3
|
+
Use this TypeScript-first container setup for isolated provider demo/testing.
|
|
4
|
+
|
|
5
|
+
## Option 1: Run with Docker Compose (recommended)
|
|
6
|
+
|
|
7
|
+
From `openclaw-predicate-provider/`:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
docker compose -f examples/docker/docker-compose.test.yml run --rm provider-demo
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
This runs the reproducible Hack-vs-Fix demo test (`npm run test:demo`) in an
|
|
14
|
+
isolated container.
|
|
15
|
+
|
|
16
|
+
Run full CI-equivalent checks in container:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
docker compose -f examples/docker/docker-compose.test.yml run --rm provider-ci
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
This runs `npm run test:ci` (`typecheck` + full test suite).
|
|
23
|
+
|
|
24
|
+
## Option 2: Build and run image directly
|
|
25
|
+
|
|
26
|
+
Build:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
docker build -t openclaw-provider-test -f examples/docker/Dockerfile.test .
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Run demo test:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
docker run --rm -it openclaw-provider-test npm run test:demo
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Run full checks:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
docker run --rm -it openclaw-provider-test npm run test:ci
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Expected behavior
|
|
45
|
+
|
|
46
|
+
- Demo test reproduces "Hack vs Fix" flow and passes.
|
|
47
|
+
- Denied actions surface stable reason codes.
|
|
48
|
+
- Sensitive resource values are redacted in audit export telemetry.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
services:
|
|
2
|
+
provider-demo:
|
|
3
|
+
build:
|
|
4
|
+
context: ../..
|
|
5
|
+
dockerfile: examples/docker/Dockerfile.test
|
|
6
|
+
environment:
|
|
7
|
+
NODE_ENV: test
|
|
8
|
+
command: ["npm", "run", "test:demo"]
|
|
9
|
+
|
|
10
|
+
provider-ci:
|
|
11
|
+
build:
|
|
12
|
+
context: ../..
|
|
13
|
+
dockerfile: examples/docker/Dockerfile.test
|
|
14
|
+
environment:
|
|
15
|
+
NODE_ENV: test
|
|
16
|
+
command: ["npm", "run", "test:ci"]
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Non-Web Evidence Provider Demo
|
|
3
|
+
*
|
|
4
|
+
* This example demonstrates how to use the terminal and desktop accessibility
|
|
5
|
+
* evidence providers with canonical hashing for reproducible state_hash computation.
|
|
6
|
+
*
|
|
7
|
+
* The canonicalization ensures that minor variations (ANSI codes, timestamps,
|
|
8
|
+
* whitespace) don't break hash verification.
|
|
9
|
+
*
|
|
10
|
+
* Run with: npx tsx examples/non-web-evidence-demo.ts
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
OpenClawTerminalEvidenceProvider,
|
|
15
|
+
OpenClawDesktopAccessibilityEvidenceProvider,
|
|
16
|
+
buildTerminalEvidenceFromProvider,
|
|
17
|
+
buildDesktopEvidenceFromProvider,
|
|
18
|
+
type TerminalRuntimeContext,
|
|
19
|
+
type DesktopRuntimeContext,
|
|
20
|
+
} from "../src/index.js";
|
|
21
|
+
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// Terminal Evidence Demo
|
|
24
|
+
// ============================================================================
|
|
25
|
+
|
|
26
|
+
async function demoTerminalEvidence(): Promise<void> {
|
|
27
|
+
console.log("=".repeat(60));
|
|
28
|
+
console.log("Terminal Evidence Provider Demo");
|
|
29
|
+
console.log("=".repeat(60));
|
|
30
|
+
|
|
31
|
+
// Simulated terminal runtime context (in real usage, capture from actual terminal)
|
|
32
|
+
const terminalContext: TerminalRuntimeContext = {
|
|
33
|
+
sessionId: "demo-session-001",
|
|
34
|
+
terminalId: "term-1",
|
|
35
|
+
cwd: "/workspace/./src/../src", // Will be normalized to /workspace/src
|
|
36
|
+
command: "git status ", // Extra whitespace will be collapsed
|
|
37
|
+
// Transcript with ANSI codes and timestamps that will be normalized
|
|
38
|
+
transcript: `\x1b[32m[12:34:56]\x1b[0m Running git status...
|
|
39
|
+
On branch main
|
|
40
|
+
Your branch is up to date with 'origin/main'.
|
|
41
|
+
|
|
42
|
+
\x1b[33mnothing to commit, working tree clean\x1b[0m`,
|
|
43
|
+
env: {
|
|
44
|
+
HOME: "/home/user",
|
|
45
|
+
PATH: "/usr/bin:/bin",
|
|
46
|
+
AWS_SECRET_KEY: "should-be-redacted", // Secrets are redacted
|
|
47
|
+
EDITOR: "vim",
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Create provider with capture function
|
|
52
|
+
const terminalProvider = new OpenClawTerminalEvidenceProvider(
|
|
53
|
+
() => terminalContext,
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// Build evidence with canonical hashing (default)
|
|
57
|
+
const terminalEvidence = await buildTerminalEvidenceFromProvider(
|
|
58
|
+
terminalProvider,
|
|
59
|
+
{ useCanonicalHash: true },
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
console.log("\nTerminal Evidence:");
|
|
63
|
+
console.log(JSON.stringify(terminalEvidence, null, 2));
|
|
64
|
+
|
|
65
|
+
// Demonstrate hash stability - same content with different formatting
|
|
66
|
+
const terminalContext2: TerminalRuntimeContext = {
|
|
67
|
+
...terminalContext,
|
|
68
|
+
cwd: "/workspace/src", // Same path after normalization
|
|
69
|
+
command: "git status", // Same command after whitespace collapse
|
|
70
|
+
// Same content without ANSI codes and different timestamps
|
|
71
|
+
transcript: `[09:00:00] Running git status...
|
|
72
|
+
On branch main
|
|
73
|
+
Your branch is up to date with 'origin/main'.
|
|
74
|
+
|
|
75
|
+
nothing to commit, working tree clean`,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const terminalProvider2 = new OpenClawTerminalEvidenceProvider(
|
|
79
|
+
() => terminalContext2,
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const terminalEvidence2 = await buildTerminalEvidenceFromProvider(
|
|
83
|
+
terminalProvider2,
|
|
84
|
+
{ useCanonicalHash: true },
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
console.log("\nTerminal Evidence (normalized variant):");
|
|
88
|
+
console.log(JSON.stringify(terminalEvidence2, null, 2));
|
|
89
|
+
|
|
90
|
+
const hashesMatch = terminalEvidence.state_hash === terminalEvidence2.state_hash;
|
|
91
|
+
console.log(`\nHashes match: ${hashesMatch ? "YES (canonicalization working)" : "NO"}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ============================================================================
|
|
95
|
+
// Desktop Accessibility Evidence Demo
|
|
96
|
+
// ============================================================================
|
|
97
|
+
|
|
98
|
+
async function demoDesktopEvidence(): Promise<void> {
|
|
99
|
+
console.log("\n" + "=".repeat(60));
|
|
100
|
+
console.log("Desktop Accessibility Evidence Provider Demo");
|
|
101
|
+
console.log("=".repeat(60));
|
|
102
|
+
|
|
103
|
+
// Simulated desktop accessibility context
|
|
104
|
+
const desktopContext: DesktopRuntimeContext = {
|
|
105
|
+
appName: " Visual Studio Code ", // Will be trimmed and normalized
|
|
106
|
+
windowTitle: "main.ts - my-project",
|
|
107
|
+
focusedRole: "editor",
|
|
108
|
+
focusedName: "Text Editor",
|
|
109
|
+
// Simulated UI tree text (in real usage, capture from OS accessibility API)
|
|
110
|
+
uiTreeText: `
|
|
111
|
+
Window: Visual Studio Code
|
|
112
|
+
Toolbar:
|
|
113
|
+
Button: New File
|
|
114
|
+
Button: Save
|
|
115
|
+
Editor:
|
|
116
|
+
TextArea: main.ts content
|
|
117
|
+
StatusBar:
|
|
118
|
+
Label: Ln 42, Col 15
|
|
119
|
+
`,
|
|
120
|
+
confidence: 0.95,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Create provider with capture function
|
|
124
|
+
const desktopProvider = new OpenClawDesktopAccessibilityEvidenceProvider(
|
|
125
|
+
() => desktopContext,
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
// Build evidence with canonical hashing
|
|
129
|
+
const desktopEvidence = await buildDesktopEvidenceFromProvider(
|
|
130
|
+
desktopProvider,
|
|
131
|
+
{ useCanonicalHash: true },
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
console.log("\nDesktop Evidence:");
|
|
135
|
+
console.log(JSON.stringify(desktopEvidence, null, 2));
|
|
136
|
+
|
|
137
|
+
// Demonstrate hash stability with whitespace variations
|
|
138
|
+
const desktopContext2: DesktopRuntimeContext = {
|
|
139
|
+
...desktopContext,
|
|
140
|
+
appName: "Visual Studio Code", // Same after normalization
|
|
141
|
+
// Same content with different whitespace
|
|
142
|
+
uiTreeText: `Window: Visual Studio Code
|
|
143
|
+
Toolbar:
|
|
144
|
+
Button: New File
|
|
145
|
+
Button: Save
|
|
146
|
+
Editor:
|
|
147
|
+
TextArea: main.ts content
|
|
148
|
+
StatusBar:
|
|
149
|
+
Label: Ln 42, Col 15`,
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const desktopProvider2 = new OpenClawDesktopAccessibilityEvidenceProvider(
|
|
153
|
+
() => desktopContext2,
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const desktopEvidence2 = await buildDesktopEvidenceFromProvider(
|
|
157
|
+
desktopProvider2,
|
|
158
|
+
{ useCanonicalHash: true },
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
console.log("\nDesktop Evidence (normalized variant):");
|
|
162
|
+
console.log(JSON.stringify(desktopEvidence2, null, 2));
|
|
163
|
+
|
|
164
|
+
const hashesMatch = desktopEvidence.state_hash === desktopEvidence2.state_hash;
|
|
165
|
+
console.log(`\nHashes match: ${hashesMatch ? "YES (canonicalization working)" : "NO"}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ============================================================================
|
|
169
|
+
// Main
|
|
170
|
+
// ============================================================================
|
|
171
|
+
|
|
172
|
+
async function main(): Promise<void> {
|
|
173
|
+
console.log("OpenClaw Non-Web Evidence Provider Demo");
|
|
174
|
+
console.log("Demonstrates canonical hashing for terminal and desktop contexts\n");
|
|
175
|
+
|
|
176
|
+
await demoTerminalEvidence();
|
|
177
|
+
await demoDesktopEvidence();
|
|
178
|
+
|
|
179
|
+
console.log("\n" + "=".repeat(60));
|
|
180
|
+
console.log("Demo complete!");
|
|
181
|
+
console.log("=".repeat(60));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { registerOpenClawPredicateTools } from "../../dist/src/index.js";
|
|
2
|
+
|
|
3
|
+
export default function register(api: {
|
|
4
|
+
registerTool: (
|
|
5
|
+
tool: {
|
|
6
|
+
name: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
execute: (id: string, params: Record<string, unknown>) => Promise<unknown>;
|
|
9
|
+
},
|
|
10
|
+
options?: { optional?: boolean },
|
|
11
|
+
) => void;
|
|
12
|
+
}) {
|
|
13
|
+
registerOpenClawPredicateTools(api, {
|
|
14
|
+
async executeCmdRun(args) {
|
|
15
|
+
return {
|
|
16
|
+
content: [{ type: "text", text: `smoke cmd: ${String(args.command ?? "")}` }],
|
|
17
|
+
};
|
|
18
|
+
},
|
|
19
|
+
async executeFsReadFile(args) {
|
|
20
|
+
return {
|
|
21
|
+
content: [{ type: "text", text: `smoke fs: ${String(args.path ?? "")}` }],
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
async executeHttpRequest(args) {
|
|
25
|
+
return {
|
|
26
|
+
content: [{ type: "text", text: `smoke http: ${String(args.url ?? "")}` }],
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "openclaw-predicate-provider-smoke-plugin",
|
|
3
|
+
"name": "Predicate Smoke Plugin",
|
|
4
|
+
"description": "Local smoke plugin that registers Predicate-guarded OpenClaw tools.",
|
|
5
|
+
"version": "0.0.1",
|
|
6
|
+
"configSchema": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Minimal OpenClaw integration sketch using ToolAdapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from openclaw_predicate_provider import GuardedProvider, ProviderConfig, ToolAdapter
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
async def fake_cmd_tool(args: dict[str, Any]) -> dict[str, Any]:
|
|
12
|
+
"""Represents the original OpenClaw cmd.run handler."""
|
|
13
|
+
return {"ok": True, "ran": args.get("command", "")}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def main() -> None:
|
|
17
|
+
config = ProviderConfig(
|
|
18
|
+
sidecar_authorize_url="http://127.0.0.1:4000/v1/authorize",
|
|
19
|
+
request_timeout_ms=300,
|
|
20
|
+
fail_closed=True,
|
|
21
|
+
)
|
|
22
|
+
provider = GuardedProvider(principal="openclaw-agent-local", config=config)
|
|
23
|
+
adapter = ToolAdapter(provider)
|
|
24
|
+
|
|
25
|
+
context = {
|
|
26
|
+
"source": "trusted_ui",
|
|
27
|
+
"session_id": "demo-session",
|
|
28
|
+
"tenant_id": "local-dev",
|
|
29
|
+
}
|
|
30
|
+
args = {"command": "echo hello"}
|
|
31
|
+
|
|
32
|
+
result = await adapter.run_shell(
|
|
33
|
+
args=args,
|
|
34
|
+
context=context,
|
|
35
|
+
execute=fake_cmd_tool,
|
|
36
|
+
)
|
|
37
|
+
print(result)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
if __name__ == "__main__":
|
|
41
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# Policy Starter Pack
|
|
2
|
+
|
|
3
|
+
Ready-to-use policy templates for common OpenClaw security scenarios.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
1. Copy the relevant policy file to your sidecar config directory
|
|
8
|
+
2. Customize paths and hosts for your environment
|
|
9
|
+
3. Restart the sidecar to load the new policy
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
cp examples/policy/workspace-isolation.yaml ~/.predicate/policies/
|
|
13
|
+
predicate-authorityd --policy-dir ~/.predicate/policies/
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Available Policies
|
|
17
|
+
|
|
18
|
+
### 1. Workspace Isolation (`workspace-isolation.yaml`)
|
|
19
|
+
|
|
20
|
+
Restricts file operations to a specific project directory. Ideal for:
|
|
21
|
+
- Development agents working on a single project
|
|
22
|
+
- CI/CD agents with bounded scope
|
|
23
|
+
- Sandboxed coding assistants
|
|
24
|
+
|
|
25
|
+
### 2. Sensitive Path Blocking (`sensitive-paths.yaml`)
|
|
26
|
+
|
|
27
|
+
Blocks access to common sensitive paths:
|
|
28
|
+
- SSH keys (`~/.ssh/*`)
|
|
29
|
+
- Cloud credentials (`~/.aws/*`, `~/.gcloud/*`, `~/.azure/*`)
|
|
30
|
+
- System configs (`/etc/*`)
|
|
31
|
+
- Environment files (`.env`, `.env.*`)
|
|
32
|
+
|
|
33
|
+
### 3. Source-Based Trust (`source-trust.yaml`)
|
|
34
|
+
|
|
35
|
+
Different rules based on request source:
|
|
36
|
+
- `trusted_ui` - Direct user interaction, more permissive
|
|
37
|
+
- `untrusted_dm` - External messages, restrictive
|
|
38
|
+
- `web_content` - Web page content, very restrictive
|
|
39
|
+
|
|
40
|
+
### 4. Approved Hosts (`approved-hosts.yaml`)
|
|
41
|
+
|
|
42
|
+
Allowlist for outbound HTTP requests:
|
|
43
|
+
- Internal APIs
|
|
44
|
+
- Known SaaS endpoints
|
|
45
|
+
- Package registries
|
|
46
|
+
|
|
47
|
+
### 5. Development Workflow (`dev-workflow.yaml`)
|
|
48
|
+
|
|
49
|
+
Balanced policy for development agents:
|
|
50
|
+
- Allow git, npm, cargo, etc.
|
|
51
|
+
- Allow localhost HTTP
|
|
52
|
+
- Block production endpoints
|
|
53
|
+
- Block destructive commands
|
|
54
|
+
|
|
55
|
+
### 6. Production Strict (`production-strict.yaml`)
|
|
56
|
+
|
|
57
|
+
Maximum security for production agents:
|
|
58
|
+
- Explicit allowlist only
|
|
59
|
+
- No shell execution
|
|
60
|
+
- Audit all decisions
|
|
61
|
+
|
|
62
|
+
## Policy Syntax
|
|
63
|
+
|
|
64
|
+
Policies use YAML format with the following structure:
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
version: 1
|
|
68
|
+
|
|
69
|
+
# Global defaults
|
|
70
|
+
defaults:
|
|
71
|
+
effect: deny # deny-by-default recommended
|
|
72
|
+
|
|
73
|
+
# Rule definitions (evaluated in order)
|
|
74
|
+
rules:
|
|
75
|
+
- id: unique_rule_id
|
|
76
|
+
effect: allow | deny
|
|
77
|
+
action: action.type | action.*
|
|
78
|
+
resource: path/pattern | [list, of, patterns]
|
|
79
|
+
when: # Optional conditions
|
|
80
|
+
source: trusted_ui
|
|
81
|
+
tenant_id: tenant-123
|
|
82
|
+
|
|
83
|
+
# Metadata
|
|
84
|
+
metadata:
|
|
85
|
+
name: Policy Name
|
|
86
|
+
description: What this policy does
|
|
87
|
+
version: 1.0.0
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Condition Reference
|
|
91
|
+
|
|
92
|
+
### Source Labels
|
|
93
|
+
|
|
94
|
+
| Source | Description | Trust Level |
|
|
95
|
+
|--------|-------------|-------------|
|
|
96
|
+
| `trusted_ui` | Direct user input from trusted UI | High |
|
|
97
|
+
| `trusted_api` | Authenticated API request | High |
|
|
98
|
+
| `untrusted_dm` | External message (DM, email) | Low |
|
|
99
|
+
| `web_content` | Content from web pages | Very Low |
|
|
100
|
+
| `system` | Internal system call | High |
|
|
101
|
+
|
|
102
|
+
### Actions
|
|
103
|
+
|
|
104
|
+
| Action | Description |
|
|
105
|
+
|--------|-------------|
|
|
106
|
+
| `shell.execute` | Run shell command |
|
|
107
|
+
| `fs.read` | Read file |
|
|
108
|
+
| `fs.write` | Write file |
|
|
109
|
+
| `net.http` | HTTP request |
|
|
110
|
+
|
|
111
|
+
### Resource Patterns
|
|
112
|
+
|
|
113
|
+
- Exact match: `/path/to/file`
|
|
114
|
+
- Glob: `/workspace/**/*.ts`
|
|
115
|
+
- Home expansion: `~/.ssh/*`
|
|
116
|
+
- List: `["/etc/*", "/var/*"]`
|
|
117
|
+
|
|
118
|
+
## Combining Policies
|
|
119
|
+
|
|
120
|
+
Policies can be split across multiple files. The sidecar merges them:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
~/.predicate/policies/
|
|
124
|
+
├── base.yaml # Global defaults
|
|
125
|
+
├── workspace.yaml # Project-specific rules
|
|
126
|
+
└── team-overrides.yaml # Team customizations
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Rules are evaluated in filename order. Later files can override earlier ones.
|
|
130
|
+
|
|
131
|
+
## Testing Policies
|
|
132
|
+
|
|
133
|
+
Use the policy tester to validate rules before deployment:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# Test a specific authorization request
|
|
137
|
+
predicate-authorityd policy test \
|
|
138
|
+
--policy examples/policy/workspace-isolation.yaml \
|
|
139
|
+
--principal "agent:test" \
|
|
140
|
+
--action "fs.read" \
|
|
141
|
+
--resource "/workspace/src/main.ts" \
|
|
142
|
+
--context '{"source": "trusted_ui"}'
|
|
143
|
+
|
|
144
|
+
# Expected output:
|
|
145
|
+
# Decision: ALLOW
|
|
146
|
+
# Matched rule: allow_workspace_reads
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Migration from Other Systems
|
|
150
|
+
|
|
151
|
+
### From OpenClaw Sandbox
|
|
152
|
+
|
|
153
|
+
If currently using OpenClaw's built-in sandbox:
|
|
154
|
+
|
|
155
|
+
1. Start with `workspace-isolation.yaml`
|
|
156
|
+
2. Add your existing sandbox paths to the allow list
|
|
157
|
+
3. Run in audit mode first to catch missing rules
|
|
158
|
+
|
|
159
|
+
### From HITL-only
|
|
160
|
+
|
|
161
|
+
If currently using human-in-the-loop for all sensitive actions:
|
|
162
|
+
|
|
163
|
+
1. Start with `production-strict.yaml`
|
|
164
|
+
2. Gradually add allow rules for common patterns
|
|
165
|
+
3. Keep HITL for truly exceptional cases
|