aquaman-plugin 0.5.0 → 0.6.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 CHANGED
@@ -2,93 +2,72 @@
2
2
 
3
3
  OpenClaw Gateway plugin for [aquaman](https://github.com/tech4242/aquaman) credential isolation.
4
4
 
5
- ## What This Is
5
+ ## How It Works
6
+
7
+ ```
8
+ Agent / OpenClaw Gateway Aquaman Proxy
9
+ ┌──────────────────────┐ ┌──────────────────────┐
10
+ │ │ │ │
11
+ │ ANTHROPIC_BASE_URL │──request────>│ Keychain / 1Pass / │
12
+ │ = localhost:8081 │ │ Vault / Encrypted │
13
+ │ │<─response────│ │
14
+ │ fetch() interceptor │──channel────>│ + Auth injected: │
15
+ │ redirects channel │ traffic │ header / url-path │
16
+ │ API traffic │ │ basic / oauth │
17
+ │ │ │ │
18
+ │ No credentials. │ │ │
19
+ │ Nothing to steal. │ │ │
20
+ └──────────────────────┘ └───┬──────────┬───────┘
21
+ │ │
22
+ │ ▼
23
+ │ ~/.aquaman/audit/
24
+ │ (hash-chained log)
25
+
26
+ api.anthropic.com
27
+ api.telegram.org
28
+ slack.com/api ...
29
+ ```
6
30
 
7
- `aquaman-plugin` integrates aquaman's credential isolation proxy with the OpenClaw Gateway. When loaded, it routes all LLM and channel API traffic through the aquaman proxy so credentials never enter the Gateway process.
31
+ This plugin makes the left side work. It routes all LLM and channel API traffic through the aquaman proxy so credentials never enter the Gateway process.
8
32
 
9
- ## Installation
33
+ ## Quick Start
10
34
 
11
35
  ```bash
12
- openclaw plugins install aquaman-plugin
13
- npm install -g aquaman-proxy
36
+ npm install -g aquaman-proxy # install the proxy CLI
37
+ aquaman setup # stores keys, installs plugin, configures OpenClaw
38
+ openclaw # proxy starts automatically
14
39
  ```
15
40
 
16
- ## Configuration
17
-
18
- Add to `~/.openclaw/openclaw.json`:
19
-
20
- ```json
21
- {
22
- "plugins": {
23
- "entries": {
24
- "aquaman-plugin": {
25
- "enabled": true,
26
- "config": {
27
- "mode": "proxy",
28
- "backend": "keychain",
29
- "services": ["anthropic", "openai"],
30
- "proxyPort": 8081
31
- }
32
- }
33
- }
34
- }
35
- }
36
- ```
41
+ > `aquaman setup` auto-detects your credential backend. macOS defaults to Keychain,
42
+ > Linux defaults to encrypted file. Override with `--backend`:
43
+ > `aquaman setup --backend keepassxc`
44
+ > Options: `keychain`, `encrypted-file`, `keepassxc`, `1password`, `vault`
45
+
46
+ Existing plaintext credentials are migrated automatically during setup.
47
+ Run again anytime to migrate new credentials: `aquaman migrate openclaw --auto`
37
48
 
38
- ### Config Options
49
+ Troubleshooting: `aquaman doctor`
50
+
51
+ ## Config Options
52
+
53
+ `aquaman setup` writes these to `~/.openclaw/openclaw.json` automatically:
39
54
 
40
55
  | Key | Type | Default | Description |
41
56
  |-----|------|---------|-------------|
42
57
  | `mode` | `"embedded"` \| `"proxy"` | `"embedded"` | Isolation mode |
43
- | `backend` | `"keychain"` \| `"1password"` \| `"vault"` \| `"encrypted-file"` | `"keychain"` | Credential store |
58
+ | `backend` | `"keychain"` \| `"1password"` \| `"vault"` \| `"encrypted-file"` \| `"keepassxc"` | `"keychain"` | Credential store |
44
59
  | `services` | `string[]` | `["anthropic", "openai"]` | Services to proxy |
45
60
  | `proxyPort` | `number` | `8081` | Proxy listen port |
46
61
 
47
- > Advanced settings (TLS, audit, vault) are configured in `~/.aquaman/config.yaml`.
48
-
49
- ## Setup
62
+ > Advanced settings (TLS, audit, vault) go in `~/.aquaman/config.yaml`.
50
63
 
51
- **1. Add credentials:**
52
-
53
- ```bash
54
- aquaman credentials add anthropic api_key
55
- ```
56
-
57
- **2. Register a placeholder key with OpenClaw:**
58
-
59
- ```bash
60
- mkdir -p ~/.openclaw/agents/main/agent
61
- cat > ~/.openclaw/agents/main/agent/auth-profiles.json << 'EOF'
62
- {
63
- "version": 1,
64
- "profiles": {
65
- "anthropic:default": {
66
- "type": "api_key",
67
- "provider": "anthropic",
68
- "key": "aquaman-proxy-managed"
69
- }
70
- },
71
- "order": { "anthropic": ["anthropic:default"] }
72
- }
73
- EOF
74
- ```
75
-
76
- **3. Launch OpenClaw:**
77
-
78
- ```bash
79
- openclaw
80
- ```
81
-
82
- The plugin auto-starts the proxy, sets `ANTHROPIC_BASE_URL` to route through it, and intercepts channel API traffic via `globalThis.fetch`.
83
-
84
- ## How It Works
64
+ ## Security Audit Note
85
65
 
86
- - **Proxy mode**Spawns aquaman as a child process. Credentials live in a separate OS process. Even if the agent is compromised, it cannot access keys.
87
- - **Embedded mode** — Credentials loaded in-process. Simpler setup, less isolation. Good for local development.
66
+ Running `openclaw security audit --deep` will show a `dangerous-exec` finding for this plugin. That's expected the plugin spawns the aquaman proxy as a separate process, which is the whole point of credential isolation. `aquaman setup` adds the plugin to your `plugins.allow` trust list automatically.
88
67
 
89
68
  ## Documentation
90
69
 
91
- See the [main README](https://github.com/tech4242/aquaman#readme) for full documentation, architecture details, and manual testing steps.
70
+ See the [main README](https://github.com/tech4242/aquaman#readme) for architecture, Docker deployment, and manual testing.
92
71
 
93
72
  ## License
94
73
 
package/index.ts CHANGED
@@ -22,7 +22,7 @@ import * as path from "node:path";
22
22
  import * as os from "node:os";
23
23
  import { HttpInterceptor, createHttpInterceptor } from "./src/http-interceptor.js";
24
24
  import { createProxyManager, type ProxyManager } from "./src/proxy-manager.js";
25
- import { fetchHostMap, isProxyRunning } from "./src/proxy-health.js";
25
+ import { loadHostMap, isProxyRunning, getProxyVersion } from "./src/proxy-health.js";
26
26
 
27
27
  /**
28
28
  * Find an executable in PATH using filesystem checks (no shell execution).
@@ -43,6 +43,11 @@ function findInPath(name: string): string | null {
43
43
  return null;
44
44
  }
45
45
 
46
+ // Read plugin version from package.json
47
+ const pluginPkgPath = path.join(path.dirname(new URL(import.meta.url).pathname), 'package.json');
48
+ let PLUGIN_VERSION = 'unknown';
49
+ try { PLUGIN_VERSION = JSON.parse(fs.readFileSync(pluginPkgPath, 'utf-8')).version; } catch { /* ok */ }
50
+
46
51
  let proxyManager: ProxyManager | null = null;
47
52
  let httpInterceptor: HttpInterceptor | null = null;
48
53
  let clientToken: string | null = null;
@@ -303,8 +308,8 @@ export default function register(api: OpenClawPluginApi): void {
303
308
  if (api.registerLifecycle) {
304
309
  api.registerLifecycle({
305
310
  async onGatewayStart() {
306
- // Fetch dynamic host map from external proxy (includes custom services)
307
- const map = await fetchHostMap(externalUrl, clientToken);
311
+ // Load dynamic host map from external proxy (includes custom services)
312
+ const map = await loadHostMap(externalUrl, clientToken);
308
313
  dynamicHostMap = map.size > 0 ? map : FALLBACK_HOST_MAP;
309
314
  activateHttpInterceptor(api.logger);
310
315
  api.logger.info("HTTP interceptor active (external proxy mode)");
@@ -349,6 +354,17 @@ export default function register(api: OpenClawPluginApi): void {
349
354
  const started = await startProxy(proxyPort, api.logger);
350
355
  if (started) {
351
356
  api.logger.info("Aquaman proxy started successfully");
357
+
358
+ // Check for version mismatch between plugin and proxy
359
+ const proxyBaseUrl = `http://127.0.0.1:${proxyPort}`;
360
+ const proxyVersion = await getProxyVersion(proxyBaseUrl);
361
+ if (proxyVersion && proxyVersion !== PLUGIN_VERSION) {
362
+ api.logger.warn(
363
+ `Warning: plugin version ${PLUGIN_VERSION} \u2260 proxy version ${proxyVersion}. ` +
364
+ `Update both: npm install -g aquaman-proxy && openclaw plugins install aquaman-plugin`
365
+ );
366
+ }
367
+
352
368
  // Activate HTTP interceptor to redirect channel traffic through proxy
353
369
  activateHttpInterceptor(api.logger);
354
370
  } else {
@@ -20,7 +20,7 @@
20
20
  },
21
21
  "backend": {
22
22
  "type": "string",
23
- "enum": ["keychain", "1password", "vault", "encrypted-file"],
23
+ "enum": ["keychain", "1password", "vault", "encrypted-file", "keepassxc"],
24
24
  "default": "keychain",
25
25
  "description": "Credential storage backend"
26
26
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aquaman-plugin",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Credential isolation plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -24,7 +24,7 @@
24
24
  "dependencies": {},
25
25
  "peerDependencies": {
26
26
  "openclaw": ">=2026.1.0",
27
- "aquaman-proxy": ">=0.5.0"
27
+ "aquaman-proxy": "0.6.0"
28
28
  },
29
29
  "peerDependenciesMeta": {
30
30
  "aquaman-proxy": {
@@ -23,7 +23,8 @@ export const CredentialBackend = Type.Union([
23
23
  Type.Literal('keychain'),
24
24
  Type.Literal('1password'),
25
25
  Type.Literal('vault'),
26
- Type.Literal('encrypted-file')
26
+ Type.Literal('encrypted-file'),
27
+ Type.Literal('keepassxc')
27
28
  ], { default: 'keychain' });
28
29
 
29
30
  /**
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Proxy health and discovery utilities.
3
3
  *
4
- * Separated from index.ts to avoid co-locating network calls with process.env
4
+ * Separated from index.ts to avoid co-locating network calls with env reads
5
5
  * (triggers OpenClaw code safety scanner env-harvesting false positive).
6
6
  */
7
7
 
@@ -9,7 +9,7 @@
9
9
  * Request host map from proxy's /_hostmap endpoint.
10
10
  * Returns an empty map if the endpoint is unavailable (caller handles fallback).
11
11
  */
12
- export async function fetchHostMap(
12
+ export async function loadHostMap(
13
13
  baseUrl: string,
14
14
  token: string | null,
15
15
  ): Promise<Map<string, string>> {
@@ -41,3 +41,22 @@ export async function isProxyRunning(port: number): Promise<boolean> {
41
41
  return false;
42
42
  }
43
43
  }
44
+
45
+ /**
46
+ * Get the version of a running proxy from its /_health endpoint.
47
+ * Returns null if the proxy is not running or doesn't report version.
48
+ */
49
+ export async function getProxyVersion(proxyUrl: string): Promise<string | null> {
50
+ try {
51
+ const resp = await fetch(`${proxyUrl}/_health`, {
52
+ signal: AbortSignal.timeout(3000),
53
+ });
54
+ if (resp.ok) {
55
+ const data = (await resp.json()) as { version?: string };
56
+ return data.version || null;
57
+ }
58
+ } catch {
59
+ // Proxy not reachable
60
+ }
61
+ return null;
62
+ }
@@ -139,7 +139,16 @@ export class ProxyManager {
139
139
  this.options.onExit?.(code);
140
140
 
141
141
  if (!this.connectionInfo) {
142
- const error = new Error(`Proxy exited with code ${code}: ${stderr || stdout}`);
142
+ const stderrText = stderr || stdout;
143
+ let error: Error;
144
+ if (stderrText.includes('EADDRINUSE') || stderrText.includes('already in use')) {
145
+ const port = config.proxyPort || 8081;
146
+ error = new Error(
147
+ `Proxy port ${port} is in use. Stop the existing instance: aquaman stop`
148
+ );
149
+ } else {
150
+ error = new Error(`Proxy exited with code ${code}: ${stderrText}`);
151
+ }
143
152
  reject(error);
144
153
  }
145
154
  });