@bastion-ai/openclaw-plugin 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/README.md ADDED
@@ -0,0 +1,186 @@
1
+ # @bastion-ai/openclaw-plugin
2
+
3
+ OpenClaw plugin for [Bastion](https://github.com/Matthieuhakim/Bastion).
4
+
5
+ It ships a `bastion_fetch` tool that sends outbound HTTP requests through Bastion, so Bastion can enforce policy, inject credentials, handle HITL approval, and append audit records. It can also block direct calls to protected URLs on built-in tools like `web_fetch`.
6
+
7
+ ## Compatibility
8
+
9
+ - OpenClaw `2026.3.13+`
10
+ - Node.js `22+`
11
+ - A running Bastion server
12
+
13
+ This plugin targets the current released OpenClaw runtime by registering an explicit tool. It does not rely on unreleased transparent result-injection hooks.
14
+
15
+ ## Installation
16
+
17
+ ### From npm
18
+
19
+ ```bash
20
+ openclaw plugins install @bastion-ai/openclaw-plugin
21
+ ```
22
+
23
+ The installed plugin ID is `bastion-fetch`, so configure it under `plugins.entries["bastion-fetch"]`.
24
+
25
+ ### Local development / pre-publish
26
+
27
+ From the Bastion repo root:
28
+
29
+ ```bash
30
+ npm run build --workspace=packages/openclaw-plugin
31
+ openclaw plugins install -l ./packages/openclaw-plugin
32
+ ```
33
+
34
+ Or install a packed tarball:
35
+
36
+ ```bash
37
+ npm pack --workspace=packages/openclaw-plugin
38
+ openclaw plugins install ./packages/openclaw-plugin/bastion-ai-openclaw-plugin-0.1.0.tgz
39
+ ```
40
+
41
+ ## Bastion Setup
42
+
43
+ 1. Create an agent and save the returned `agentSecret` (`bst_...`):
44
+
45
+ ```bash
46
+ curl -X POST http://localhost:3000/v1/agents \
47
+ -H "Authorization: Bearer $PROJECT_API_KEY" \
48
+ -H "Content-Type: application/json" \
49
+ -d '{"name": "my-openclaw-agent"}'
50
+ ```
51
+
52
+ 2. Store the upstream credential Bastion should inject:
53
+
54
+ ```bash
55
+ curl -X POST http://localhost:3000/v1/credentials \
56
+ -H "Authorization: Bearer $PROJECT_API_KEY" \
57
+ -H "Content-Type: application/json" \
58
+ -d '{"name": "Stripe API Key", "type": "API_KEY", "value": "sk_live_...", "agentId": "<agentId>"}'
59
+ ```
60
+
61
+ 3. Create a policy that allows the action:
62
+
63
+ ```bash
64
+ curl -X POST http://localhost:3000/v1/policies \
65
+ -H "Authorization: Bearer $PROJECT_API_KEY" \
66
+ -H "Content-Type: application/json" \
67
+ -d '{"agentId": "<agentId>", "credentialId": "<credentialId>", "allowedActions": ["stripe.*"]}'
68
+ ```
69
+
70
+ ## OpenClaw Configuration
71
+
72
+ Add this to `openclaw.json`:
73
+
74
+ ```json
75
+ {
76
+ "plugins": {
77
+ "entries": {
78
+ "bastion-fetch": {
79
+ "enabled": true,
80
+ "config": {
81
+ "serverUrl": "http://localhost:3000",
82
+ "agentSecret": { "$env": "BASTION_AGENT_SECRET" },
83
+ "rules": [
84
+ {
85
+ "tool": "web_fetch",
86
+ "urlPattern": "https://api.stripe.com/**",
87
+ "credentialId": "cred_abc123",
88
+ "action": "stripe.charges"
89
+ },
90
+ {
91
+ "tool": "web_fetch",
92
+ "urlPattern": "https://api.github.com/**",
93
+ "credentialId": "cred_def456",
94
+ "action": "github.api",
95
+ "injection": { "location": "header", "key": "Authorization" }
96
+ }
97
+ ],
98
+ "timeout": 30000
99
+ }
100
+ }
101
+ }
102
+ }
103
+ }
104
+ ```
105
+
106
+ Set your agent secret:
107
+
108
+ ```bash
109
+ export BASTION_AGENT_SECRET=bst_...
110
+ ```
111
+
112
+ ## How Users Implement It
113
+
114
+ Agents should call `bastion_fetch` for protected outbound API requests.
115
+
116
+ Example tool call:
117
+
118
+ ```json
119
+ {
120
+ "tool": "bastion_fetch",
121
+ "params": {
122
+ "url": "https://api.stripe.com/v1/charges",
123
+ "method": "POST",
124
+ "body": {
125
+ "amount": 5000,
126
+ "currency": "usd"
127
+ }
128
+ }
129
+ }
130
+ ```
131
+
132
+ The plugin matches the request URL against the configured rules, resolves the Bastion credential/action pair, calls Bastion's `/v1/proxy/execute`, and returns a structured tool result containing:
133
+
134
+ - `status`
135
+ - `headers`
136
+ - `body`
137
+ - `url`
138
+ - `_bastion` metadata (`credentialId`, `action`, `policyDecision`, `durationMs`, optional `hitlRequestId`)
139
+
140
+ If a rule includes `tool`, the plugin also blocks direct calls to that tool for matching URLs. For example, `tool: "web_fetch"` prevents the model from bypassing Bastion for those domains.
141
+
142
+ ## Prompting Guidance
143
+
144
+ In your agent instructions, tell the model:
145
+
146
+ ```text
147
+ Use `bastion_fetch` for requests to protected APIs such as Stripe or GitHub. Do not use `web_fetch` for those domains.
148
+ ```
149
+
150
+ That keeps the workflow deterministic and lets the plugin enforce policy cleanly.
151
+
152
+ ## `agentSecret` formats
153
+
154
+ | Format | Example |
155
+ |--------|---------|
156
+ | Plain string | `"bst_abc123..."` |
157
+ | Environment variable | `{ "$env": "BASTION_AGENT_SECRET" }` |
158
+ | File | `{ "$file": "/run/secrets/bastion_secret" }` |
159
+ | Command | `{ "$exec": "vault read -field=secret secret/bastion" }` |
160
+
161
+ ## Rule Options
162
+
163
+ | Field | Required | Description |
164
+ |-------|----------|-------------|
165
+ | `tool` | No | Built-in tool to block for matching URLs, e.g. `web_fetch` |
166
+ | `urlPattern` | Yes | Glob pattern. `*` matches one path segment, `**` matches any depth |
167
+ | `credentialId` | Yes | Bastion credential ID |
168
+ | `action` | Yes | Action name for Bastion policy evaluation |
169
+ | `injection` | No | Override credential injection (`header` / `query` / `body`) |
170
+ | `params` | No | Dot-paths to extract Bastion policy params, e.g. `{ "amount": "body.amount" }` |
171
+
172
+ Rules are evaluated in order. Put more specific patterns before broader wildcards.
173
+
174
+ ## Troubleshooting
175
+
176
+ **Plugin logs "server is unreachable"**
177
+ Bastion is not running or not reachable from OpenClaw. Start it with `docker compose up -d && npm run dev`.
178
+
179
+ **`bastion_fetch` returns "Blocked by Bastion policy"**
180
+ The agent's policy denied the action. Check Bastion policies or audit entries.
181
+
182
+ **`bastion_fetch` hangs for minutes**
183
+ The request hit a HITL rule and Bastion is waiting for approval. Review pending requests via `GET /v1/hitl/pending`.
184
+
185
+ **Direct `web_fetch` calls are blocked**
186
+ That is expected when a matching rule defines `tool: "web_fetch"`. Use `bastion_fetch` instead.
@@ -0,0 +1,17 @@
1
+ import type { ProxyExecuteResult } from '@bastion-ai/sdk';
2
+ import type { CompiledRule } from './ruleEngine.js';
3
+ export declare class BastionBridge {
4
+ private readonly client;
5
+ private readonly defaultTimeout;
6
+ constructor(serverUrl: string, agentSecret: string, defaultTimeout?: number);
7
+ /**
8
+ * Execute a proxied request through Bastion.
9
+ * Builds the ProxyExecuteInput from the matched rule and tool args, then
10
+ * delegates to the SDK. Throws BastionUnreachableError on network failures
11
+ * and BastionBlockedError when the policy denies the request.
12
+ */
13
+ executeProxy(rule: CompiledRule, toolArgs: Record<string, unknown>): Promise<ProxyExecuteResult>;
14
+ /** Check Bastion server availability. Returns false if unreachable. */
15
+ healthCheck(): Promise<boolean>;
16
+ }
17
+ //# sourceMappingURL=bastionBridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bastionBridge.d.ts","sourceRoot":"","sources":["../src/bastionBridge.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAI1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAKpD,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;IACvC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;gBAE5B,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,cAAc,SAAS;IAK3E;;;;;OAKG;IACG,YAAY,CAChB,IAAI,EAAE,YAAY,EAClB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,OAAO,CAAC,kBAAkB,CAAC;IAyC9B,uEAAuE;IACjE,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;CAQtC"}
@@ -0,0 +1,73 @@
1
+ import { BastionClient } from '@bastion-ai/sdk';
2
+ import { BastionForbiddenError } from '@bastion-ai/sdk';
3
+ import { BastionUnreachableError, BastionBlockedError } from './errors.js';
4
+ import { extractParams } from './ruleEngine.js';
5
+ /** Timeout for HITL-escalated requests (5.5 min — slightly above Bastion's 5-min HITL window). */
6
+ const HITL_CLIENT_TIMEOUT_MS = 330_000;
7
+ export class BastionBridge {
8
+ client;
9
+ defaultTimeout;
10
+ constructor(serverUrl, agentSecret, defaultTimeout = 30_000) {
11
+ this.client = new BastionClient({ baseUrl: serverUrl, apiKey: agentSecret });
12
+ this.defaultTimeout = defaultTimeout;
13
+ }
14
+ /**
15
+ * Execute a proxied request through Bastion.
16
+ * Builds the ProxyExecuteInput from the matched rule and tool args, then
17
+ * delegates to the SDK. Throws BastionUnreachableError on network failures
18
+ * and BastionBlockedError when the policy denies the request.
19
+ */
20
+ async executeProxy(rule, toolArgs) {
21
+ const url = toolArgs['url'];
22
+ const method = typeof toolArgs['method'] === 'string' ? toolArgs['method'] : 'GET';
23
+ const headers = typeof toolArgs['headers'] === 'object' && toolArgs['headers'] !== null
24
+ ? toolArgs['headers']
25
+ : {};
26
+ const body = toolArgs['body'];
27
+ const requestedTimeout = typeof toolArgs['timeout'] === 'number' && toolArgs['timeout'] > 0
28
+ ? toolArgs['timeout']
29
+ : this.defaultTimeout;
30
+ const params = rule.params ? extractParams(toolArgs, rule.params) : undefined;
31
+ // For HITL escalations, Bastion may block up to 5 min — use a longer client timeout.
32
+ const timeout = Math.max(requestedTimeout, HITL_CLIENT_TIMEOUT_MS);
33
+ try {
34
+ return await this.client.execute({
35
+ credentialId: rule.credentialId,
36
+ action: rule.action,
37
+ params: Object.keys(params ?? {}).length > 0 ? params : undefined,
38
+ target: { url, method, headers, body },
39
+ injection: rule.injection,
40
+ timeout,
41
+ });
42
+ }
43
+ catch (error) {
44
+ if (error instanceof BastionForbiddenError) {
45
+ throw new BastionBlockedError(error.message);
46
+ }
47
+ // Network-level failure (TypeError from fetch, or any non-HTTP error)
48
+ if (error instanceof TypeError || isNetworkError(error)) {
49
+ throw new BastionUnreachableError(`Bastion server unreachable: ${error instanceof Error ? error.message : String(error)}`);
50
+ }
51
+ throw error;
52
+ }
53
+ }
54
+ /** Check Bastion server availability. Returns false if unreachable. */
55
+ async healthCheck() {
56
+ try {
57
+ await this.client.health();
58
+ return true;
59
+ }
60
+ catch {
61
+ return false;
62
+ }
63
+ }
64
+ }
65
+ function isNetworkError(error) {
66
+ if (!(error instanceof Error))
67
+ return false;
68
+ // Node fetch throws TypeError for network errors; also check for ECONNREFUSED etc.
69
+ return (error.message.includes('ECONNREFUSED') ||
70
+ error.message.includes('ENOTFOUND') ||
71
+ error.message.includes('fetch failed'));
72
+ }
73
+ //# sourceMappingURL=bastionBridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bastionBridge.js","sourceRoot":"","sources":["../src/bastionBridge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export declare class BastionUnreachableError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export declare class BastionBlockedError extends Error {
5
+ constructor(message: string);
6
+ }
7
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,uBAAwB,SAAQ,KAAK;gBACpC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B"}
package/dist/errors.js ADDED
@@ -0,0 +1,13 @@
1
+ export class BastionUnreachableError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = 'BastionUnreachableError';
5
+ }
6
+ }
7
+ export class BastionBlockedError extends Error {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = 'BastionBlockedError';
11
+ }
12
+ }
13
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IAChD,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IACxC,CAAC;CACF;AAED,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAC5C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACpC,CAAC;CACF"}
@@ -0,0 +1,5 @@
1
+ export { default } from './plugin.js';
2
+ export { BASTION_FETCH_TOOL_NAME } from './plugin.js';
3
+ export type { BastionPluginConfig, BastionFetchResponse, BastionFetchToolInput, InterceptionRule, SecretValue, InjectionConfig, } from './types.js';
4
+ export { BastionUnreachableError, BastionBlockedError } from './errors.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AACtD,YAAY,EACV,mBAAmB,EACnB,oBAAoB,EACpB,qBAAqB,EACrB,gBAAgB,EAChB,WAAW,EACX,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { default } from './plugin.js';
2
+ export { BASTION_FETCH_TOOL_NAME } from './plugin.js';
3
+ export { BastionUnreachableError, BastionBlockedError } from './errors.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAStD,OAAO,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { OpenClawPluginApi } from './types.js';
2
+ export declare const BASTION_FETCH_TOOL_NAME = "bastion_fetch";
3
+ export default function bastionPlugin(api: OpenClawPluginApi): Promise<void>;
4
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,iBAAiB,EAKlB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,uBAAuB,kBAAkB,CAAC;AAqHvD,wBAA8B,aAAa,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgFjF"}
package/dist/plugin.js ADDED
@@ -0,0 +1,177 @@
1
+ import { resolveSecret } from './secretRef.js';
2
+ import { compileRules, matchRule, matchRuleByUrl } from './ruleEngine.js';
3
+ import { adaptToolResult } from './responseAdapter.js';
4
+ import { BastionBridge } from './bastionBridge.js';
5
+ import { BastionUnreachableError, BastionBlockedError } from './errors.js';
6
+ export const BASTION_FETCH_TOOL_NAME = 'bastion_fetch';
7
+ const HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];
8
+ const BASTION_FETCH_TOOL_SCHEMA = {
9
+ type: 'object',
10
+ additionalProperties: false,
11
+ required: ['url'],
12
+ properties: {
13
+ url: {
14
+ type: 'string',
15
+ description: 'Absolute URL to request through Bastion.',
16
+ },
17
+ method: {
18
+ type: 'string',
19
+ enum: [...HTTP_METHODS],
20
+ description: 'HTTP method. Defaults to GET.',
21
+ },
22
+ headers: {
23
+ type: 'object',
24
+ description: 'Optional request headers.',
25
+ additionalProperties: {
26
+ type: 'string',
27
+ },
28
+ },
29
+ body: {
30
+ description: 'Optional JSON request body forwarded through Bastion.',
31
+ },
32
+ timeout: {
33
+ type: 'number',
34
+ minimum: 1,
35
+ description: 'Optional per-request timeout in milliseconds.',
36
+ },
37
+ },
38
+ };
39
+ function validateConfig(config) {
40
+ if (!config || typeof config !== 'object') {
41
+ throw new Error('Bastion plugin: config is required');
42
+ }
43
+ const c = config;
44
+ if (!c['serverUrl'] || typeof c['serverUrl'] !== 'string') {
45
+ throw new Error('Bastion plugin: config.serverUrl is required and must be a string');
46
+ }
47
+ if (c['agentSecret'] === undefined || c['agentSecret'] === null) {
48
+ throw new Error('Bastion plugin: config.agentSecret is required');
49
+ }
50
+ if (!Array.isArray(c['rules']) || c['rules'].length === 0) {
51
+ throw new Error('Bastion plugin: config.rules must be a non-empty array');
52
+ }
53
+ return c;
54
+ }
55
+ function assertRecord(value, label) {
56
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
57
+ throw new Error(`Bastion plugin: ${label} must be an object`);
58
+ }
59
+ return value;
60
+ }
61
+ function normalizeFetchInput(params) {
62
+ const input = assertRecord(params, 'tool params');
63
+ if (typeof input['url'] !== 'string' || input['url'].length === 0) {
64
+ throw new Error('Bastion plugin: url is required');
65
+ }
66
+ const method = input['method'] ? String(input['method']).toUpperCase() : 'GET';
67
+ if (!HTTP_METHODS.includes(method)) {
68
+ throw new Error(`Bastion plugin: method must be one of ${HTTP_METHODS.join(', ')}`);
69
+ }
70
+ let headers;
71
+ if (input['headers'] !== undefined) {
72
+ const rawHeaders = assertRecord(input['headers'], 'headers');
73
+ headers = {};
74
+ for (const [key, value] of Object.entries(rawHeaders)) {
75
+ if (typeof value !== 'string') {
76
+ throw new Error(`Bastion plugin: headers["${key}"] must be a string`);
77
+ }
78
+ headers[key] = value;
79
+ }
80
+ }
81
+ let timeout;
82
+ if (input['timeout'] !== undefined) {
83
+ if (typeof input['timeout'] !== 'number' || input['timeout'] <= 0) {
84
+ throw new Error('Bastion plugin: timeout must be a positive number');
85
+ }
86
+ timeout = input['timeout'];
87
+ }
88
+ return {
89
+ url: input['url'],
90
+ method,
91
+ headers,
92
+ body: input['body'],
93
+ timeout,
94
+ };
95
+ }
96
+ function toToolArgs(input) {
97
+ return input;
98
+ }
99
+ function normalizeExecutionError(error) {
100
+ if (error instanceof BastionBlockedError) {
101
+ return new Error(`Blocked by Bastion policy: ${error.message}`);
102
+ }
103
+ if (error instanceof BastionUnreachableError) {
104
+ return new Error('Bastion server unreachable. Request blocked (fail-closed).');
105
+ }
106
+ return error instanceof Error ? error : new Error(String(error));
107
+ }
108
+ export default async function bastionPlugin(api) {
109
+ // 1. Validate config from the released OpenClaw plugin API surface.
110
+ const pluginConfig = validateConfig(api.pluginConfig);
111
+ // 2. Resolve agent secret
112
+ const agentSecret = await resolveSecret(pluginConfig.agentSecret);
113
+ // 3. Compile rules (glob → regex) at startup
114
+ const compiledRules = compileRules(pluginConfig.rules);
115
+ // 4. Instantiate Bastion bridge
116
+ const bridge = new BastionBridge(pluginConfig.serverUrl, agentSecret, pluginConfig.timeout);
117
+ // 5. Non-blocking health check at startup — just warn if unreachable
118
+ bridge
119
+ .healthCheck()
120
+ .then((ok) => {
121
+ if (!ok) {
122
+ api.logger.warn(`Bastion plugin: server at ${pluginConfig.serverUrl} is unreachable. ` +
123
+ `${BASTION_FETCH_TOOL_NAME} requests will fail closed.`);
124
+ }
125
+ else {
126
+ api.logger.info(`Bastion plugin: connected to ${pluginConfig.serverUrl}`);
127
+ }
128
+ })
129
+ .catch(() => {
130
+ // healthCheck already catches internally — this is just a safety net
131
+ });
132
+ // 6. Register the Bastion-backed tool for current OpenClaw releases.
133
+ api.registerTool({
134
+ name: BASTION_FETCH_TOOL_NAME,
135
+ label: 'Bastion Fetch',
136
+ description: 'Execute outbound HTTP requests through Bastion using configured URL rules.',
137
+ parameters: BASTION_FETCH_TOOL_SCHEMA,
138
+ async execute(_toolCallId, params) {
139
+ const input = normalizeFetchInput(params);
140
+ const toolArgs = toToolArgs(input);
141
+ const rule = matchRuleByUrl(toolArgs, compiledRules);
142
+ if (!rule) {
143
+ throw new Error(`No Bastion rule matches ${input.url}`);
144
+ }
145
+ try {
146
+ const result = await bridge.executeProxy(rule, toolArgs);
147
+ return adaptToolResult(result, input.url);
148
+ }
149
+ catch (error) {
150
+ throw normalizeExecutionError(error);
151
+ }
152
+ },
153
+ });
154
+ // 7. Register a bypass-blocking hook for tools explicitly listed in rules.
155
+ api.on('before_tool_call', (event) => {
156
+ const { toolName, params } = event;
157
+ if (toolName === BASTION_FETCH_TOOL_NAME) {
158
+ return undefined;
159
+ }
160
+ const rule = matchRule(toolName, params, compiledRules);
161
+ if (!rule) {
162
+ return undefined;
163
+ }
164
+ const url = typeof params['url'] === 'string' ? params['url'] : 'the requested URL';
165
+ return {
166
+ block: true,
167
+ blockReason: `Requests to ${url} must use ${BASTION_FETCH_TOOL_NAME} so Bastion can enforce policy and inject credentials.`,
168
+ };
169
+ });
170
+ // 8. Register service for lifecycle awareness
171
+ api.registerService({
172
+ id: 'bastion-fetch',
173
+ start: () => api.logger.info('Bastion fetch plugin active'),
174
+ stop: () => api.logger.info('Bastion fetch plugin stopped'),
175
+ });
176
+ }
177
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAS3E,MAAM,CAAC,MAAM,uBAAuB,GAAG,eAAe,CAAC;AAEvD,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAU,CAAC;AAE3F,MAAM,yBAAyB,GAAG;IAChC,IAAI,EAAE,QAAQ;IACd,oBAAoB,EAAE,KAAK;IAC3B,QAAQ,EAAE,CAAC,KAAK,CAAC;IACjB,UAAU,EAAE;QACV,GAAG,EAAE;YACH,IAAI,EAAE,QAAQ;YACd,WAAW,EAAE,0CAA0C;SACxD;QACD,MAAM,EAAE;YACN,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,CAAC,GAAG,YAAY,CAAC;YACvB,WAAW,EAAE,+BAA+B;SAC7C;QACD,OAAO,EAAE;YACP,IAAI,EAAE,QAAQ;YACd,WAAW,EAAE,2BAA2B;YACxC,oBAAoB,EAAE;gBACpB,IAAI,EAAE,QAAQ;aACf;SACF;QACD,IAAI,EAAE;YACJ,WAAW,EAAE,uDAAuD;SACrE;QACD,OAAO,EAAE;YACP,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,CAAC;YACV,WAAW,EAAE,+CAA+C;SAC7D;KACF;CACgC,CAAC;AAEpC,SAAS,cAAc,CAAC,MAAe;IACrC,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,CAAC;IACD,MAAM,CAAC,GAAG,MAAiC,CAAC;IAE5C,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,OAAO,CAAC,CAAC,WAAW,CAAC,KAAK,QAAQ,EAAE,CAAC;QAC1D,MAAM,IAAI,KAAK,CAAC,mEAAmE,CAAC,CAAC;IACvF,CAAC;IACD,IAAI,CAAC,CAAC,aAAa,CAAC,KAAK,SAAS,IAAI,CAAC,CAAC,aAAa,CAAC,KAAK,IAAI,EAAE,CAAC;QAChE,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;IACpE,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1D,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;IAC5E,CAAC;IAED,OAAO,CAAmC,CAAC;AAC7C,CAAC;AAED,SAAS,YAAY,CAAC,KAAc,EAAE,KAAa;IACjD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,KAAK,CAAC,mBAAmB,KAAK,oBAAoB,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,KAAgC,CAAC;AAC1C,CAAC;AAED,SAAS,mBAAmB,CAAC,MAAe;IAC1C,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAElD,IAAI,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK,QAAQ,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClE,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACrD,CAAC;IAED,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;IAC/E,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,MAAuC,CAAC,EAAE,CAAC;QACpE,MAAM,IAAI,KAAK,CAAC,yCAAyC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACtF,CAAC;IAED,IAAI,OAA2C,CAAC;IAChD,IAAI,KAAK,CAAC,SAAS,CAAC,KAAK,SAAS,EAAE,CAAC;QACnC,MAAM,UAAU,GAAG,YAAY,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC,CAAC;QAC7D,OAAO,GAAG,EAAE,CAAC;QACb,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;YACtD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;gBAC9B,MAAM,IAAI,KAAK,CAAC,4BAA4B,GAAG,qBAAqB,CAAC,CAAC;YACxE,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACvB,CAAC;IACH,CAAC;IAED,IAAI,OAA2B,CAAC;IAChC,IAAI,KAAK,CAAC,SAAS,CAAC,KAAK,SAAS,EAAE,CAAC;QACnC,IAAI,OAAO,KAAK,CAAC,SAAS,CAAC,KAAK,QAAQ,IAAI,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;YAClE,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;QACvE,CAAC;QACD,OAAO,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC;IAC7B,CAAC;IAED,OAAO;QACL,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC;QACjB,MAAM;QACN,OAAO;QACP,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC;QACnB,OAAO;KACR,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,KAA4B;IAC9C,OAAO,KAA2C,CAAC;AACrD,CAAC;AAED,SAAS,uBAAuB,CAAC,KAAc;IAC7C,IAAI,KAAK,YAAY,mBAAmB,EAAE,CAAC;QACzC,OAAO,IAAI,KAAK,CAAC,8BAA8B,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IAClE,CAAC;IACD,IAAI,KAAK,YAAY,uBAAuB,EAAE,CAAC;QAC7C,OAAO,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAC;IACjF,CAAC;IACD,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,aAAa,CAAC,GAAsB;IAChE,oEAAoE;IACpE,MAAM,YAAY,GAAG,cAAc,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAEtD,0BAA0B;IAC1B,MAAM,WAAW,GAAG,MAAM,aAAa,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;IAElE,6CAA6C;IAC7C,MAAM,aAAa,GAAG,YAAY,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;IAEvD,gCAAgC;IAChC,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,YAAY,CAAC,SAAS,EAAE,WAAW,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC;IAE5F,qEAAqE;IACrE,MAAM;SACH,WAAW,EAAE;SACb,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE;QACX,IAAI,CAAC,EAAE,EAAE,CAAC;YACR,GAAG,CAAC,MAAM,CAAC,IAAI,CACb,6BAA6B,YAAY,CAAC,SAAS,mBAAmB;gBACpE,GAAG,uBAAuB,6BAA6B,CAC1D,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,gCAAgC,YAAY,CAAC,SAAS,EAAE,CAAC,CAAC;QAC5E,CAAC;IACH,CAAC,CAAC;SACD,KAAK,CAAC,GAAG,EAAE;QACV,qEAAqE;IACvE,CAAC,CAAC,CAAC;IAEL,qEAAqE;IACrE,GAAG,CAAC,YAAY,CAAC;QACf,IAAI,EAAE,uBAAuB;QAC7B,KAAK,EAAE,eAAe;QACtB,WAAW,EACT,4EAA4E;QAC9E,UAAU,EAAE,yBAAyB;QACrC,KAAK,CAAC,OAAO,CAAC,WAA+B,EAAE,MAAe;YAC5D,MAAM,KAAK,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;YAC1C,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;YACnC,MAAM,IAAI,GAAG,cAAc,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;YAErD,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,MAAM,IAAI,KAAK,CAAC,2BAA2B,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;YAC1D,CAAC;YAED,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;gBACzD,OAAO,eAAe,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;YAC5C,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,uBAAuB,CAAC,KAAK,CAAC,CAAC;YACvC,CAAC;QACH,CAAC;KACF,CAAC,CAAC;IAEH,2EAA2E;IAC3E,GAAG,CAAC,EAAE,CAAC,kBAAkB,EAAE,CAAC,KAAc,EAAoB,EAAE;QAC9D,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,KAAwB,CAAC;QACtD,IAAI,QAAQ,KAAK,uBAAuB,EAAE,CAAC;YACzC,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,MAAM,IAAI,GAAG,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC;QACxD,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,MAAM,GAAG,GAAG,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,mBAAmB,CAAC;QACpF,OAAO;YACL,KAAK,EAAE,IAAI;YACX,WAAW,EAAE,eAAe,GAAG,aAAa,uBAAuB,wDAAwD;SAC5H,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,8CAA8C;IAC9C,GAAG,CAAC,eAAe,CAAC;QAClB,EAAE,EAAE,eAAe;QACnB,KAAK,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,6BAA6B,CAAC;QAC3D,IAAI,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,8BAA8B,CAAC;KAC5D,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { ProxyExecuteResult } from '@bastion-ai/sdk';
2
+ import type { BastionFetchResponse, OpenClawToolResult } from './types.js';
3
+ export declare function buildBastionResponse(result: ProxyExecuteResult, originalUrl: string): BastionFetchResponse;
4
+ /**
5
+ * Adapt the Bastion response into a standard OpenClaw tool result.
6
+ * `details` keeps the structured payload, while `content` gives the model a
7
+ * readable JSON summary in the same turn.
8
+ */
9
+ export declare function adaptToolResult(result: ProxyExecuteResult, originalUrl: string): OpenClawToolResult;
10
+ //# sourceMappingURL=responseAdapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"responseAdapter.d.ts","sourceRoot":"","sources":["../src/responseAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,KAAK,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAE3E,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,kBAAkB,EAC1B,WAAW,EAAE,MAAM,GAClB,oBAAoB,CAgBtB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,kBAAkB,EAC1B,WAAW,EAAE,MAAM,GAClB,kBAAkB,CAYpB"}
@@ -0,0 +1,35 @@
1
+ export function buildBastionResponse(result, originalUrl) {
2
+ return {
3
+ status: result.upstream.status,
4
+ headers: result.upstream.headers,
5
+ body: result.upstream.body ?? null,
6
+ url: originalUrl,
7
+ _bastion: {
8
+ credentialId: result.meta.credentialId,
9
+ action: result.meta.action,
10
+ policyDecision: result.meta.policyDecision,
11
+ durationMs: result.meta.durationMs,
12
+ ...(result.meta.hitlRequestId !== undefined
13
+ ? { hitlRequestId: result.meta.hitlRequestId }
14
+ : {}),
15
+ },
16
+ };
17
+ }
18
+ /**
19
+ * Adapt the Bastion response into a standard OpenClaw tool result.
20
+ * `details` keeps the structured payload, while `content` gives the model a
21
+ * readable JSON summary in the same turn.
22
+ */
23
+ export function adaptToolResult(result, originalUrl) {
24
+ const payload = buildBastionResponse(result, originalUrl);
25
+ return {
26
+ content: [
27
+ {
28
+ type: 'text',
29
+ text: JSON.stringify(payload, null, 2),
30
+ },
31
+ ],
32
+ details: payload,
33
+ };
34
+ }
35
+ //# sourceMappingURL=responseAdapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"responseAdapter.js","sourceRoot":"","sources":["../src/responseAdapter.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,oBAAoB,CAClC,MAA0B,EAC1B,WAAmB;IAEnB,OAAO;QACL,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM;QAC9B,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,OAAO;QAChC,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI,IAAI,IAAI;QAClC,GAAG,EAAE,WAAW;QAChB,QAAQ,EAAE;YACR,YAAY,EAAE,MAAM,CAAC,IAAI,CAAC,YAAY;YACtC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM;YAC1B,cAAc,EAAE,MAAM,CAAC,IAAI,CAAC,cAAc;YAC1C,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU;YAClC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,KAAK,SAAS;gBACzC,CAAC,CAAC,EAAE,aAAa,EAAE,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE;gBAC9C,CAAC,CAAC,EAAE,CAAC;SACR;KACF,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC7B,MAA0B,EAC1B,WAAmB;IAEnB,MAAM,OAAO,GAAG,oBAAoB,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAE1D,OAAO;QACL,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;aACvC;SACF;QACD,OAAO,EAAE,OAAO;KACjB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { InterceptionRule, ParamsMapping } from './types.js';
2
+ export interface CompiledRule extends InterceptionRule {
3
+ urlRegex: RegExp;
4
+ }
5
+ /** Compile all rules, converting urlPattern globs to RegExp at startup. */
6
+ export declare function compileRules(rules: InterceptionRule[]): CompiledRule[];
7
+ /**
8
+ * Find the first matching rule for this tool call.
9
+ * Only rules with an explicit `tool` are considered, since this matcher is
10
+ * used for bypass-blocking hooks on built-in tools like `web_fetch`.
11
+ */
12
+ export declare function matchRule(toolName: string, toolArgs: Record<string, unknown>, rules: CompiledRule[]): CompiledRule | null;
13
+ /**
14
+ * Find the first matching rule by URL only.
15
+ * Used by the plugin's registered `bastion_fetch` tool.
16
+ */
17
+ export declare function matchRuleByUrl(toolArgs: Record<string, unknown>, rules: CompiledRule[]): CompiledRule | null;
18
+ /** Extract Bastion policy params from tool args using a ParamsMapping. */
19
+ export declare function extractParams(args: Record<string, unknown>, mapping: ParamsMapping): {
20
+ amount?: number;
21
+ ip?: string;
22
+ };
23
+ //# sourceMappingURL=ruleEngine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ruleEngine.d.ts","sourceRoot":"","sources":["../src/ruleEngine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAElE,MAAM,WAAW,YAAa,SAAQ,gBAAgB;IACpD,QAAQ,EAAE,MAAM,CAAC;CAClB;AA+BD,2EAA2E;AAC3E,wBAAgB,YAAY,CAAC,KAAK,EAAE,gBAAgB,EAAE,GAAG,YAAY,EAAE,CAKtE;AAMD;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,KAAK,EAAE,YAAY,EAAE,GACpB,YAAY,GAAG,IAAI,CAWrB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,KAAK,EAAE,YAAY,EAAE,GACpB,YAAY,GAAG,IAAI,CAWrB;AAaD,0EAA0E;AAC1E,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,EAAE,aAAa,GACrB;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,CAqBlC"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Converts a glob URL pattern to a RegExp.
3
+ * - `**` matches anything (including path separators)
4
+ * - `*` matches any character except `/`
5
+ * - All other characters are escaped
6
+ */
7
+ function globToRegex(pattern) {
8
+ let regexStr = '';
9
+ for (let i = 0; i < pattern.length; i++) {
10
+ const ch = pattern[i];
11
+ if (ch === '*' && pattern[i + 1] === '*') {
12
+ regexStr += '.*';
13
+ i++; // skip second *
14
+ // skip optional trailing slash after **
15
+ if (pattern[i + 1] === '/') {
16
+ regexStr += '/?';
17
+ i++;
18
+ }
19
+ }
20
+ else if (ch === '*') {
21
+ regexStr += '[^/]*';
22
+ }
23
+ else {
24
+ regexStr += ch.replace(/[.+^${}()|[\]\\]/g, '\\$&');
25
+ }
26
+ }
27
+ return new RegExp(`^${regexStr}$`);
28
+ }
29
+ /** Compile all rules, converting urlPattern globs to RegExp at startup. */
30
+ export function compileRules(rules) {
31
+ return rules.map((rule) => ({
32
+ ...rule,
33
+ urlRegex: globToRegex(rule.urlPattern),
34
+ }));
35
+ }
36
+ function getUrl(toolArgs) {
37
+ return typeof toolArgs['url'] === 'string' ? toolArgs['url'] : null;
38
+ }
39
+ /**
40
+ * Find the first matching rule for this tool call.
41
+ * Only rules with an explicit `tool` are considered, since this matcher is
42
+ * used for bypass-blocking hooks on built-in tools like `web_fetch`.
43
+ */
44
+ export function matchRule(toolName, toolArgs, rules) {
45
+ const url = getUrl(toolArgs);
46
+ if (!url)
47
+ return null;
48
+ for (const rule of rules) {
49
+ if (rule.tool === toolName && rule.urlRegex.test(url)) {
50
+ return rule;
51
+ }
52
+ }
53
+ return null;
54
+ }
55
+ /**
56
+ * Find the first matching rule by URL only.
57
+ * Used by the plugin's registered `bastion_fetch` tool.
58
+ */
59
+ export function matchRuleByUrl(toolArgs, rules) {
60
+ const url = getUrl(toolArgs);
61
+ if (!url)
62
+ return null;
63
+ for (const rule of rules) {
64
+ if (rule.urlRegex.test(url)) {
65
+ return rule;
66
+ }
67
+ }
68
+ return null;
69
+ }
70
+ /** Resolve a dot-path like "body.amount" against an args object. */
71
+ function resolvePath(args, path) {
72
+ const parts = path.split('.');
73
+ let current = args;
74
+ for (const part of parts) {
75
+ if (current === null || typeof current !== 'object')
76
+ return undefined;
77
+ current = current[part];
78
+ }
79
+ return current;
80
+ }
81
+ /** Extract Bastion policy params from tool args using a ParamsMapping. */
82
+ export function extractParams(args, mapping) {
83
+ const result = {};
84
+ if (mapping.amount !== undefined) {
85
+ const raw = resolvePath(args, mapping.amount);
86
+ if (typeof raw === 'number') {
87
+ result.amount = raw;
88
+ }
89
+ else if (typeof raw === 'string') {
90
+ const parsed = parseFloat(raw);
91
+ if (!isNaN(parsed))
92
+ result.amount = parsed;
93
+ }
94
+ }
95
+ if (mapping.ip !== undefined) {
96
+ const raw = resolvePath(args, mapping.ip);
97
+ if (typeof raw === 'string') {
98
+ result.ip = raw;
99
+ }
100
+ }
101
+ return result;
102
+ }
103
+ //# sourceMappingURL=ruleEngine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ruleEngine.js","sourceRoot":"","sources":["../src/ruleEngine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { SecretValue } from './types.js';
2
+ /**
3
+ * Resolves a SecretValue to a plain string at runtime.
4
+ * Supports plain strings, $env, $file, and $exec sources.
5
+ */
6
+ export declare function resolveSecret(ref: SecretValue): Promise<string>;
7
+ //# sourceMappingURL=secretRef.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secretRef.d.ts","sourceRoot":"","sources":["../src/secretRef.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;GAGG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAwBrE"}
@@ -0,0 +1,32 @@
1
+ import { readFileSync } from 'fs';
2
+ import { execSync } from 'child_process';
3
+ /**
4
+ * Resolves a SecretValue to a plain string at runtime.
5
+ * Supports plain strings, $env, $file, and $exec sources.
6
+ */
7
+ export async function resolveSecret(ref) {
8
+ let value;
9
+ if (typeof ref === 'string') {
10
+ value = ref;
11
+ }
12
+ else if ('$env' in ref) {
13
+ const envValue = process.env[ref.$env];
14
+ if (!envValue) {
15
+ throw new Error(`Environment variable "${ref.$env}" is not set or empty`);
16
+ }
17
+ value = envValue;
18
+ }
19
+ else if ('$file' in ref) {
20
+ const contents = readFileSync(ref.$file, 'utf8');
21
+ value = contents.trim();
22
+ }
23
+ else {
24
+ const stdout = execSync(ref.$exec, { encoding: 'utf8' });
25
+ value = stdout.trim();
26
+ }
27
+ if (!value) {
28
+ throw new Error('Resolved secret is empty');
29
+ }
30
+ return value;
31
+ }
32
+ //# sourceMappingURL=secretRef.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secretRef.js","sourceRoot":"","sources":["../src/secretRef.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAgB;IAClD,IAAI,KAAa,CAAC;IAElB,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,KAAK,GAAG,GAAG,CAAC;IACd,CAAC;SAAM,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,yBAAyB,GAAG,CAAC,IAAI,uBAAuB,CAAC,CAAC;QAC5E,CAAC;QACD,KAAK,GAAG,QAAQ,CAAC;IACnB,CAAC;SAAM,IAAI,OAAO,IAAI,GAAG,EAAE,CAAC;QAC1B,MAAM,QAAQ,GAAG,YAAY,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACjD,KAAK,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC;IAC1B,CAAC;SAAM,CAAC;QACN,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QACzD,KAAK,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;IACxB,CAAC;IAED,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;IAC9C,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,111 @@
1
+ export interface OpenClawLogger {
2
+ info(msg: string): void;
3
+ warn(msg: string): void;
4
+ error(msg: string): void;
5
+ }
6
+ export interface OpenClawService {
7
+ id: string;
8
+ start?: () => void;
9
+ stop?: () => void;
10
+ }
11
+ export interface OpenClawToolTextContent {
12
+ type: 'text';
13
+ text: string;
14
+ }
15
+ export interface OpenClawToolResult {
16
+ content: OpenClawToolTextContent[];
17
+ details?: unknown;
18
+ }
19
+ export interface OpenClawToolDefinition {
20
+ name: string;
21
+ label?: string;
22
+ description: string;
23
+ parameters: Record<string, unknown>;
24
+ execute(toolCallId: string | undefined, params: unknown): Promise<OpenClawToolResult> | OpenClawToolResult;
25
+ }
26
+ export interface OpenClawPluginApi {
27
+ pluginConfig?: Record<string, unknown>;
28
+ on(event: string, handler: (...args: unknown[]) => unknown): void;
29
+ registerTool(tool: OpenClawToolDefinition): void;
30
+ registerService(service: OpenClawService): void;
31
+ logger: OpenClawLogger;
32
+ }
33
+ export interface BeforeCallEvent {
34
+ toolName: string;
35
+ params: Record<string, unknown>;
36
+ runId?: string;
37
+ toolCallId?: string;
38
+ }
39
+ export type BeforeCallResult = {
40
+ params?: Record<string, unknown>;
41
+ block?: boolean;
42
+ blockReason?: string;
43
+ } | undefined;
44
+ /** Resolves to a secret string at runtime. */
45
+ export type SecretValue = string | {
46
+ $env: string;
47
+ } | {
48
+ $file: string;
49
+ } | {
50
+ $exec: string;
51
+ };
52
+ export interface InjectionConfig {
53
+ location: 'header' | 'query' | 'body';
54
+ key: string;
55
+ }
56
+ /**
57
+ * Dot-path mappings from tool args into Bastion policy params.
58
+ * e.g. amount: "body.amount" extracts args.body.amount as a number.
59
+ */
60
+ export interface ParamsMapping {
61
+ amount?: string;
62
+ ip?: string;
63
+ }
64
+ export interface InterceptionRule {
65
+ /**
66
+ * Optional tool name to block for this URL pattern.
67
+ * Example: "web_fetch" to prevent bypassing `bastion_fetch`.
68
+ */
69
+ tool?: string;
70
+ /** Glob pattern for the URL (e.g. "https://api.stripe.com/**"). */
71
+ urlPattern: string;
72
+ /** Bastion credential ID to use for this call. */
73
+ credentialId: string;
74
+ /** Bastion action name (e.g. "stripe.charges"). */
75
+ action: string;
76
+ /** Override default credential injection location. */
77
+ injection?: InjectionConfig;
78
+ /** Extract policy params from tool args. */
79
+ params?: ParamsMapping;
80
+ }
81
+ export interface BastionPluginConfig {
82
+ /** URL of the Bastion server (e.g. "http://localhost:3000"). */
83
+ serverUrl: string;
84
+ /** Agent secret (bst_... token) — supports SecretRef pattern. */
85
+ agentSecret: SecretValue;
86
+ /** Ordered list of protected routes. First match wins. */
87
+ rules: InterceptionRule[];
88
+ /** Request timeout in ms. Default: 30000. */
89
+ timeout?: number;
90
+ }
91
+ export interface BastionFetchToolInput {
92
+ url: string;
93
+ method?: string;
94
+ headers?: Record<string, string>;
95
+ body?: unknown;
96
+ timeout?: number;
97
+ }
98
+ export interface BastionFetchResponse {
99
+ status: number;
100
+ headers: Record<string, string>;
101
+ body: unknown | null;
102
+ url: string;
103
+ _bastion: {
104
+ credentialId: string;
105
+ action: string;
106
+ policyDecision: string;
107
+ durationMs: number;
108
+ hitlRequestId?: string;
109
+ };
110
+ }
111
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ // ── OpenClaw Plugin API (minimal surface — aligned to released runtime) ──────
2
+ export {};
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,gFAAgF"}
@@ -0,0 +1,92 @@
1
+ {
2
+ "id": "bastion-fetch",
3
+ "name": "Bastion Fetch",
4
+ "version": "0.1.0",
5
+ "description": "Secure outbound HTTP requests with Bastion — credential vault, policy engine, HITL gate, audit trail",
6
+ "uiHints": {
7
+ "serverUrl": {
8
+ "label": "Bastion Server URL",
9
+ "placeholder": "http://localhost:3000"
10
+ },
11
+ "agentSecret": {
12
+ "label": "Agent Secret",
13
+ "sensitive": true
14
+ },
15
+ "rules": {
16
+ "label": "Protected Routes"
17
+ },
18
+ "timeout": {
19
+ "label": "Default Timeout (ms)",
20
+ "advanced": true
21
+ }
22
+ },
23
+ "configSchema": {
24
+ "type": "object",
25
+ "additionalProperties": false,
26
+ "required": ["serverUrl", "agentSecret", "rules"],
27
+ "properties": {
28
+ "serverUrl": {
29
+ "type": "string",
30
+ "description": "URL of your Bastion server (e.g. http://localhost:3000)"
31
+ },
32
+ "agentSecret": {
33
+ "description": "Agent secret (bst_... token). Supports $env, $file, $exec SecretRef."
34
+ },
35
+ "rules": {
36
+ "type": "array",
37
+ "minItems": 1,
38
+ "description": "Ordered list of protected routes. First match wins.",
39
+ "items": {
40
+ "type": "object",
41
+ "additionalProperties": false,
42
+ "required": ["urlPattern", "credentialId", "action"],
43
+ "properties": {
44
+ "tool": {
45
+ "type": "string",
46
+ "description": "Optional built-in tool to block for matching URLs (e.g. web_fetch)"
47
+ },
48
+ "urlPattern": {
49
+ "type": "string"
50
+ },
51
+ "credentialId": {
52
+ "type": "string"
53
+ },
54
+ "action": {
55
+ "type": "string"
56
+ },
57
+ "injection": {
58
+ "type": "object",
59
+ "additionalProperties": false,
60
+ "properties": {
61
+ "location": {
62
+ "type": "string",
63
+ "enum": ["header", "query", "body"]
64
+ },
65
+ "key": {
66
+ "type": "string"
67
+ }
68
+ }
69
+ },
70
+ "params": {
71
+ "type": "object",
72
+ "additionalProperties": false,
73
+ "properties": {
74
+ "amount": {
75
+ "type": "string"
76
+ },
77
+ "ip": {
78
+ "type": "string"
79
+ }
80
+ }
81
+ }
82
+ }
83
+ }
84
+ },
85
+ "timeout": {
86
+ "type": "number",
87
+ "default": 30000,
88
+ "description": "Default request timeout in milliseconds"
89
+ }
90
+ }
91
+ }
92
+ }
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@bastion-ai/openclaw-plugin",
3
+ "version": "0.1.0",
4
+ "description": "OpenClaw plugin for Bastion — adds a Bastion-backed HTTP tool and blocks direct bypasses for protected URLs",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "homepage": "https://github.com/Matthieuhakim/Bastion#readme",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/Matthieuhakim/Bastion.git",
13
+ "directory": "packages/openclaw-plugin"
14
+ },
15
+ "bugs": {
16
+ "url": "https://github.com/Matthieuhakim/Bastion/issues"
17
+ },
18
+ "keywords": [
19
+ "ai",
20
+ "agents",
21
+ "security",
22
+ "proxy",
23
+ "audit",
24
+ "openclaw",
25
+ "plugin",
26
+ "typescript"
27
+ ],
28
+ "exports": {
29
+ ".": {
30
+ "import": "./dist/index.js",
31
+ "types": "./dist/index.d.ts"
32
+ }
33
+ },
34
+ "openclaw": {
35
+ "extensions": ["./dist/index.js"],
36
+ "install": {
37
+ "npmSpec": "@bastion-ai/openclaw-plugin",
38
+ "defaultChoice": "npm"
39
+ }
40
+ },
41
+ "files": ["dist", "openclaw.plugin.json"],
42
+ "sideEffects": false,
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "engines": {
47
+ "node": ">=22.0.0"
48
+ },
49
+ "peerDependencies": {
50
+ "openclaw": ">=2026.3.13"
51
+ },
52
+ "scripts": {
53
+ "clean": "node --eval \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
54
+ "build": "npm run clean && tsc",
55
+ "dev": "tsc --watch",
56
+ "prepack": "npm run build",
57
+ "test": "vitest run",
58
+ "test:watch": "vitest"
59
+ },
60
+ "dependencies": {
61
+ "@bastion-ai/sdk": "^0.1.0"
62
+ },
63
+ "devDependencies": {
64
+ "typescript": "^5.7.0",
65
+ "vitest": "^3.0.0"
66
+ }
67
+ }