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.
Files changed (136) hide show
  1. package/.github/workflows/release.yml +76 -0
  2. package/.github/workflows/tests.yml +34 -0
  3. package/.markdownlint.yaml +5 -0
  4. package/.pre-commit-config.yaml +100 -0
  5. package/README.md +405 -0
  6. package/dist/src/adapter.d.ts +17 -0
  7. package/dist/src/adapter.js +36 -0
  8. package/dist/src/authority-client.d.ts +21 -0
  9. package/dist/src/authority-client.js +22 -0
  10. package/dist/src/circuit-breaker.d.ts +86 -0
  11. package/dist/src/circuit-breaker.js +174 -0
  12. package/dist/src/config.d.ts +8 -0
  13. package/dist/src/config.js +7 -0
  14. package/dist/src/control-plane-sync.d.ts +57 -0
  15. package/dist/src/control-plane-sync.js +99 -0
  16. package/dist/src/errors.d.ts +6 -0
  17. package/dist/src/errors.js +6 -0
  18. package/dist/src/index.d.ts +12 -0
  19. package/dist/src/index.js +12 -0
  20. package/dist/src/non-web-evidence.d.ts +46 -0
  21. package/dist/src/non-web-evidence.js +54 -0
  22. package/dist/src/openclaw-hooks.d.ts +27 -0
  23. package/dist/src/openclaw-hooks.js +54 -0
  24. package/dist/src/openclaw-plugin-api.d.ts +18 -0
  25. package/dist/src/openclaw-plugin-api.js +17 -0
  26. package/dist/src/provider.d.ts +48 -0
  27. package/dist/src/provider.js +154 -0
  28. package/dist/src/runtime-integration.d.ts +20 -0
  29. package/dist/src/runtime-integration.js +43 -0
  30. package/dist/src/web-evidence.d.ts +48 -0
  31. package/dist/src/web-evidence.js +49 -0
  32. package/dist/tests/adapter.test.d.ts +1 -0
  33. package/dist/tests/adapter.test.js +63 -0
  34. package/dist/tests/audit-event-e2e.test.d.ts +1 -0
  35. package/dist/tests/audit-event-e2e.test.js +209 -0
  36. package/dist/tests/authority-client.test.d.ts +1 -0
  37. package/dist/tests/authority-client.test.js +46 -0
  38. package/dist/tests/circuit-breaker.test.d.ts +1 -0
  39. package/dist/tests/circuit-breaker.test.js +200 -0
  40. package/dist/tests/control-plane-sync.test.d.ts +1 -0
  41. package/dist/tests/control-plane-sync.test.js +90 -0
  42. package/dist/tests/hack-vs-fix-demo.test.d.ts +1 -0
  43. package/dist/tests/hack-vs-fix-demo.test.js +36 -0
  44. package/dist/tests/jwks-rotation.test.d.ts +1 -0
  45. package/dist/tests/jwks-rotation.test.js +232 -0
  46. package/dist/tests/load-latency.test.d.ts +1 -0
  47. package/dist/tests/load-latency.test.js +175 -0
  48. package/dist/tests/multi-tenant-isolation.test.d.ts +1 -0
  49. package/dist/tests/multi-tenant-isolation.test.js +146 -0
  50. package/dist/tests/non-web-evidence.test.d.ts +1 -0
  51. package/dist/tests/non-web-evidence.test.js +139 -0
  52. package/dist/tests/openclaw-hooks.test.d.ts +1 -0
  53. package/dist/tests/openclaw-hooks.test.js +38 -0
  54. package/dist/tests/openclaw-plugin-api.test.d.ts +1 -0
  55. package/dist/tests/openclaw-plugin-api.test.js +40 -0
  56. package/dist/tests/provider.test.d.ts +1 -0
  57. package/dist/tests/provider.test.js +190 -0
  58. package/dist/tests/runtime-integration.test.d.ts +1 -0
  59. package/dist/tests/runtime-integration.test.js +57 -0
  60. package/dist/tests/web-evidence.test.d.ts +1 -0
  61. package/dist/tests/web-evidence.test.js +89 -0
  62. package/docs/MIGRATION_GUIDE.md +405 -0
  63. package/docs/OPERATIONAL_RUNBOOK.md +389 -0
  64. package/docs/PRODUCTION_READINESS.md +134 -0
  65. package/docs/SLO_THRESHOLDS.md +193 -0
  66. package/examples/README.md +171 -0
  67. package/examples/docker/Dockerfile.test +16 -0
  68. package/examples/docker/README.md +48 -0
  69. package/examples/docker/docker-compose.test.yml +16 -0
  70. package/examples/non-web-evidence-demo.ts +184 -0
  71. package/examples/openclaw-plugin-smoke/index.ts +30 -0
  72. package/examples/openclaw-plugin-smoke/openclaw.plugin.json +11 -0
  73. package/examples/openclaw-plugin-smoke/package.json +9 -0
  74. package/examples/openclaw_integration_example.py +41 -0
  75. package/examples/policy/README.md +165 -0
  76. package/examples/policy/approved-hosts.yaml +137 -0
  77. package/examples/policy/dev-workflow.yaml +206 -0
  78. package/examples/policy/policy.example.yaml +17 -0
  79. package/examples/policy/production-strict.yaml +97 -0
  80. package/examples/policy/sensitive-paths.yaml +114 -0
  81. package/examples/policy/source-trust.yaml +129 -0
  82. package/examples/policy/workspace-isolation.yaml +51 -0
  83. package/examples/runtime_registry_example.py +75 -0
  84. package/package.json +27 -0
  85. package/pyproject.toml +41 -0
  86. package/src/adapter.ts +45 -0
  87. package/src/authority-client.ts +50 -0
  88. package/src/circuit-breaker.ts +245 -0
  89. package/src/config.ts +15 -0
  90. package/src/control-plane-sync.ts +159 -0
  91. package/src/errors.ts +5 -0
  92. package/src/index.ts +12 -0
  93. package/src/non-web-evidence.ts +116 -0
  94. package/src/openclaw-hooks.ts +76 -0
  95. package/src/openclaw-plugin-api.ts +51 -0
  96. package/src/openclaw_predicate_provider/__init__.py +16 -0
  97. package/src/openclaw_predicate_provider/__main__.py +5 -0
  98. package/src/openclaw_predicate_provider/adapter.py +84 -0
  99. package/src/openclaw_predicate_provider/agentidentity_backend.py +78 -0
  100. package/src/openclaw_predicate_provider/cli.py +160 -0
  101. package/src/openclaw_predicate_provider/config.py +42 -0
  102. package/src/openclaw_predicate_provider/errors.py +13 -0
  103. package/src/openclaw_predicate_provider/integrations/__init__.py +5 -0
  104. package/src/openclaw_predicate_provider/integrations/openclaw_runtime.py +74 -0
  105. package/src/openclaw_predicate_provider/models.py +19 -0
  106. package/src/openclaw_predicate_provider/openclaw_hooks.py +75 -0
  107. package/src/openclaw_predicate_provider/provider.py +69 -0
  108. package/src/openclaw_predicate_provider/py.typed +1 -0
  109. package/src/openclaw_predicate_provider/sidecar.py +59 -0
  110. package/src/provider.ts +220 -0
  111. package/src/runtime-integration.ts +68 -0
  112. package/src/web-evidence.ts +95 -0
  113. package/tests/adapter.test.ts +76 -0
  114. package/tests/audit-event-e2e.test.ts +258 -0
  115. package/tests/authority-client.test.ts +52 -0
  116. package/tests/circuit-breaker.test.ts +266 -0
  117. package/tests/conftest.py +9 -0
  118. package/tests/control-plane-sync.test.ts +114 -0
  119. package/tests/hack-vs-fix-demo.test.ts +44 -0
  120. package/tests/jwks-rotation.test.ts +274 -0
  121. package/tests/load-latency.test.ts +214 -0
  122. package/tests/multi-tenant-isolation.test.ts +183 -0
  123. package/tests/non-web-evidence.test.ts +168 -0
  124. package/tests/openclaw-hooks.test.ts +46 -0
  125. package/tests/openclaw-plugin-api.test.ts +50 -0
  126. package/tests/provider.test.ts +227 -0
  127. package/tests/runtime-integration.test.ts +70 -0
  128. package/tests/test_adapter.py +46 -0
  129. package/tests/test_cli.py +26 -0
  130. package/tests/test_openclaw_hooks.py +53 -0
  131. package/tests/test_provider.py +59 -0
  132. package/tests/test_runtime_integration.py +77 -0
  133. package/tests/test_sidecar_client.py +198 -0
  134. package/tests/web-evidence.test.ts +113 -0
  135. package/tsconfig.json +14 -0
  136. 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,9 @@
1
+ {
2
+ "name": "openclaw-predicate-provider-smoke-plugin",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "type": "module",
6
+ "openclaw": {
7
+ "extensions": ["./index.ts"]
8
+ }
9
+ }
@@ -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