@randromeda/arbiter-openclaw 0.1.0 → 0.1.2
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/CHANGELOG.md +10 -0
- package/README.md +35 -7
- package/index.js +1 -2
- package/openclaw.plugin.json +8 -0
- package/package.json +1 -1
- package/src/config.js +35 -7
- package/src/guardrail.js +7 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@randromeda/arbiter-openclaw` will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.1.2] - 2026-04-11
|
|
6
|
+
|
|
7
|
+
- Documented the safe canary-path smoke test for OpenClaw using `/tmp/arbiter-deny-test`.
|
|
8
|
+
|
|
9
|
+
## [0.1.1] - 2026-04-03
|
|
10
|
+
|
|
11
|
+
- Added local runtime auto-discovery from `~/.arbiter/config.json`.
|
|
12
|
+
- Added optional `localConfigPath` override in plugin config.
|
|
13
|
+
- Removed environment-variable fallback wiring from plugin runtime setup.
|
|
14
|
+
|
|
5
15
|
## [0.1.0] - 2026-04-02
|
|
6
16
|
|
|
7
17
|
- Initial native OpenClaw plugin implementation.
|
package/README.md
CHANGED
|
@@ -29,7 +29,9 @@ openclaw plugins install ./integrations/openclaw-plugin
|
|
|
29
29
|
|
|
30
30
|
## Config
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
The plugin auto-discovers local runtime settings from `~/.arbiter/config.json` by default.
|
|
33
|
+
|
|
34
|
+
Minimal local config:
|
|
33
35
|
|
|
34
36
|
```json
|
|
35
37
|
{
|
|
@@ -38,10 +40,6 @@ Add plugin config under `plugins.entries.arbiter-openclaw.config` in your OpenCl
|
|
|
38
40
|
"arbiter-openclaw": {
|
|
39
41
|
"enabled": true,
|
|
40
42
|
"config": {
|
|
41
|
-
"arbiterUrl": "http://localhost:8080",
|
|
42
|
-
"tenantId": "tenant-demo",
|
|
43
|
-
"gatewayKey": "gw-key",
|
|
44
|
-
"serviceKey": "svc-key",
|
|
45
43
|
"protectTools": ["exec", "process", "write", "edit", "apply_patch"],
|
|
46
44
|
"recordState": true,
|
|
47
45
|
"failClosed": true
|
|
@@ -52,10 +50,31 @@ Add plugin config under `plugins.entries.arbiter-openclaw.config` in your OpenCl
|
|
|
52
50
|
}
|
|
53
51
|
```
|
|
54
52
|
|
|
53
|
+
Optional explicit config (for non-local or customized setups):
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"plugins": {
|
|
58
|
+
"entries": {
|
|
59
|
+
"arbiter-openclaw": {
|
|
60
|
+
"enabled": true,
|
|
61
|
+
"config": {
|
|
62
|
+
"arbiterUrl": "http://localhost:8080",
|
|
63
|
+
"tenantId": "tenant-demo",
|
|
64
|
+
"gatewayKey": "gw-key",
|
|
65
|
+
"serviceKey": "svc-key"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
55
73
|
Config options:
|
|
56
74
|
|
|
57
|
-
- `arbiterUrl`: Arbiter base URL.
|
|
58
|
-
- `
|
|
75
|
+
- `arbiterUrl`: Arbiter base URL. Defaults from local runtime config when available.
|
|
76
|
+
- `localConfigPath`: Optional override path for local runtime config. Defaults to `~/.arbiter/config.json`.
|
|
77
|
+
- `tenantId`: Tenant ID for canonical requests and state records. Defaults from local runtime config when available.
|
|
59
78
|
- `gatewayKey`: Optional key for intercept routes.
|
|
60
79
|
- `serviceKey`: Optional key for verify/state routes.
|
|
61
80
|
- `actorIdMode`: `agent-id` (default) or `config`.
|
|
@@ -65,6 +84,13 @@ Config options:
|
|
|
65
84
|
- `failClosed`: Block on Arbiter errors (default `true`).
|
|
66
85
|
- `timeoutMs`: Per-request timeout (default `5000`).
|
|
67
86
|
|
|
87
|
+
Local runtime quick setup:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
go run ./cmd/arbiter local init
|
|
91
|
+
go run ./cmd/arbiter local start
|
|
92
|
+
```
|
|
93
|
+
|
|
68
94
|
## Stock OpenClaw Tool Mapping
|
|
69
95
|
|
|
70
96
|
The default protected tools are:
|
|
@@ -77,6 +103,8 @@ The default protected tools are:
|
|
|
77
103
|
|
|
78
104
|
Use Arbiter policy to allow or deny these tools. The included Arbiter filesystem policy denies destructive delete commands and `apply_patch` file-deletion directives.
|
|
79
105
|
|
|
106
|
+
For a safer OpenClaw smoke test, the default policy also denies writes and shell commands that target the canary prefix `/tmp/arbiter-deny-test`. A normal chat prompt such as `Use the exec tool exactly once to run: mkdir -p /tmp/arbiter-deny-test/nested` should trigger an Arbiter block without relying on `rm -rf`.
|
|
107
|
+
|
|
80
108
|
## Development
|
|
81
109
|
|
|
82
110
|
Run plugin tests:
|
package/index.js
CHANGED
|
@@ -9,8 +9,7 @@ export default definePluginEntry({
|
|
|
9
9
|
const guardrail = createArbiterGuardrail({
|
|
10
10
|
pluginConfig: api.pluginConfig,
|
|
11
11
|
logger: api.logger,
|
|
12
|
-
fetchImpl: globalThis.fetch
|
|
13
|
-
env: process.env
|
|
12
|
+
fetchImpl: globalThis.fetch
|
|
14
13
|
});
|
|
15
14
|
|
|
16
15
|
api.on("before_tool_call", guardrail.beforeToolCall);
|
package/openclaw.plugin.json
CHANGED
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
"format": "uri",
|
|
12
12
|
"pattern": "^[Hh][Tt][Tt][Pp][Ss]?://"
|
|
13
13
|
},
|
|
14
|
+
"localConfigPath": {
|
|
15
|
+
"type": "string"
|
|
16
|
+
},
|
|
14
17
|
"tenantId": {
|
|
15
18
|
"type": "string",
|
|
16
19
|
"minLength": 1
|
|
@@ -67,6 +70,11 @@
|
|
|
67
70
|
"label": "Arbiter Base URL",
|
|
68
71
|
"help": "Base URL for the Arbiter interceptor, for example http://localhost:8080."
|
|
69
72
|
},
|
|
73
|
+
"localConfigPath": {
|
|
74
|
+
"label": "Local Runtime Config Path",
|
|
75
|
+
"help": "Optional path to local Arbiter config (defaults to ~/.arbiter/config.json).",
|
|
76
|
+
"advanced": true
|
|
77
|
+
},
|
|
70
78
|
"tenantId": {
|
|
71
79
|
"label": "Tenant ID",
|
|
72
80
|
"help": "Tenant identifier included in canonical requests and state records."
|
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
1
5
|
export const DEFAULT_PROTECT_TOOLS = ["exec", "process", "write", "edit", "apply_patch"];
|
|
2
6
|
export const DEFAULT_TIMEOUT_MS = 5000;
|
|
3
7
|
|
|
@@ -36,15 +40,38 @@ function normalizeToolList(value) {
|
|
|
36
40
|
return tools.length > 0 ? Array.from(new Set(tools)) : DEFAULT_PROTECT_TOOLS.slice();
|
|
37
41
|
}
|
|
38
42
|
|
|
39
|
-
|
|
43
|
+
function readLocalRuntimeConfig(customPath) {
|
|
44
|
+
const location = customPath || path.join(os.homedir(), ".arbiter", "config.json");
|
|
45
|
+
try {
|
|
46
|
+
const raw = fs.readFileSync(location, "utf8");
|
|
47
|
+
const parsed = JSON.parse(raw);
|
|
48
|
+
return asRecord(parsed);
|
|
49
|
+
} catch {
|
|
50
|
+
return {};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function baseURLFromAddress(address) {
|
|
55
|
+
if (!address) {
|
|
56
|
+
return "";
|
|
57
|
+
}
|
|
58
|
+
if (address.startsWith("http://") || address.startsWith("https://")) {
|
|
59
|
+
return address;
|
|
60
|
+
}
|
|
61
|
+
return `http://${address}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function resolvePluginConfig(pluginConfig) {
|
|
40
65
|
const cfg = asRecord(pluginConfig);
|
|
41
|
-
const
|
|
66
|
+
const localConfigPath = asString(cfg.localConfigPath);
|
|
67
|
+
const localConfig = readLocalRuntimeConfig(localConfigPath);
|
|
68
|
+
const localURL = asString(localConfig.base_url) || baseURLFromAddress(asString(localConfig.address));
|
|
42
69
|
|
|
43
|
-
const arbiterUrl = asString(cfg.arbiterUrl) ||
|
|
44
|
-
const tenantId = asString(cfg.tenantId) || asString(
|
|
45
|
-
const gatewayKey = asString(cfg.gatewayKey)
|
|
46
|
-
const serviceKey = asString(cfg.serviceKey)
|
|
47
|
-
const actorId = asString(cfg.actorId)
|
|
70
|
+
const arbiterUrl = asString(cfg.arbiterUrl) || localURL;
|
|
71
|
+
const tenantId = asString(cfg.tenantId) || asString(localConfig.tenant_id);
|
|
72
|
+
const gatewayKey = asString(cfg.gatewayKey);
|
|
73
|
+
const serviceKey = asString(cfg.serviceKey);
|
|
74
|
+
const actorId = asString(cfg.actorId);
|
|
48
75
|
|
|
49
76
|
const actorIdMode = asString(cfg.actorIdMode) === "config" ? "config" : "agent-id";
|
|
50
77
|
const protectTools = normalizeToolList(cfg.protectTools);
|
|
@@ -74,6 +101,7 @@ export function resolvePluginConfig(pluginConfig, env = process.env) {
|
|
|
74
101
|
recordState,
|
|
75
102
|
failClosed,
|
|
76
103
|
timeoutMs,
|
|
104
|
+
localConfigPath,
|
|
77
105
|
missing
|
|
78
106
|
};
|
|
79
107
|
}
|
package/src/guardrail.js
CHANGED
|
@@ -31,7 +31,11 @@ function configError(config) {
|
|
|
31
31
|
if (!config.missing.length) {
|
|
32
32
|
return "";
|
|
33
33
|
}
|
|
34
|
-
|
|
34
|
+
let hint = "";
|
|
35
|
+
if (config.missing.includes("arbiterUrl") || config.missing.includes("tenantId")) {
|
|
36
|
+
hint = " (run `go run ./cmd/arbiter local init` and `go run ./cmd/arbiter local start`, or set arbiterUrl/tenantId explicitly)";
|
|
37
|
+
}
|
|
38
|
+
return `arbiter plugin misconfigured: missing ${config.missing.join(", ")}${hint}`;
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
function serviceHeaders(config) {
|
|
@@ -45,10 +49,9 @@ function gatewayHeaders(config) {
|
|
|
45
49
|
export function createArbiterGuardrail({
|
|
46
50
|
pluginConfig,
|
|
47
51
|
logger,
|
|
48
|
-
fetchImpl = globalThis.fetch
|
|
49
|
-
env = process.env
|
|
52
|
+
fetchImpl = globalThis.fetch
|
|
50
53
|
}) {
|
|
51
|
-
const config = resolvePluginConfig(pluginConfig
|
|
54
|
+
const config = resolvePluginConfig(pluginConfig);
|
|
52
55
|
const protectedTools = new Set(config.protectTools);
|
|
53
56
|
|
|
54
57
|
async function beforeToolCall(event, ctx) {
|