aquaman-proxy 0.13.0 → 0.14.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 +1 -1
- package/dist/cli/index.js +163 -127
- package/dist/cli/index.js.map +1 -1
- package/dist/core/audit/logger.js +1 -1
- package/dist/core/audit/logger.js.map +1 -1
- package/dist/core/credentials/backends/onepassword.js +1 -1
- package/dist/core/credentials/backends/onepassword.js.map +1 -1
- package/dist/core/credentials/backends/systemd-creds.js +1 -1
- package/dist/core/credentials/backends/systemd-creds.js.map +1 -1
- package/dist/core/credentials/caching-store.d.ts +43 -0
- package/dist/core/credentials/caching-store.d.ts.map +1 -0
- package/dist/core/credentials/caching-store.js +77 -0
- package/dist/core/credentials/caching-store.js.map +1 -0
- package/dist/core/credentials/index.d.ts +1 -0
- package/dist/core/credentials/index.d.ts.map +1 -1
- package/dist/core/credentials/index.js +1 -0
- package/dist/core/credentials/index.js.map +1 -1
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/types.d.ts +9 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/utils/config.d.ts +17 -0
- package/dist/core/utils/config.d.ts.map +1 -1
- package/dist/core/utils/config.js +33 -1
- package/dist/core/utils/config.js.map +1 -1
- package/dist/core/utils/index.d.ts +1 -1
- package/dist/core/utils/index.d.ts.map +1 -1
- package/dist/core/utils/index.js +1 -1
- package/dist/core/utils/index.js.map +1 -1
- package/dist/hermes/config-writer.d.ts +10 -0
- package/dist/hermes/config-writer.d.ts.map +1 -1
- package/dist/hermes/config-writer.js +31 -0
- package/dist/hermes/config-writer.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/openclaw/integration.d.ts.map +1 -1
- package/dist/openclaw/integration.js +4 -0
- package/dist/openclaw/integration.js.map +1 -1
- package/dist/openclaw/secretref.d.ts +74 -0
- package/dist/openclaw/secretref.d.ts.map +1 -0
- package/dist/openclaw/secretref.js +139 -0
- package/dist/openclaw/secretref.js.map +1 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ Agent / OpenClaw / Coding Agent Aquaman Proxy
|
|
|
18
18
|
│ No credentials. │ proxy.sock │ │
|
|
19
19
|
│ No open ports. │ (chmod 0o600) │ │
|
|
20
20
|
│ Nothing to steal. │ │ │
|
|
21
|
-
└──────────────────────┘
|
|
21
|
+
└──────────────────────┘ └──┬──────────┬────────┘
|
|
22
22
|
│ │
|
|
23
23
|
│ ▼
|
|
24
24
|
│ ~/.aquaman/audit/
|
package/dist/cli/index.js
CHANGED
|
@@ -12,12 +12,14 @@ import { Command, Help } from 'commander';
|
|
|
12
12
|
import * as path from 'node:path';
|
|
13
13
|
import * as fs from 'node:fs';
|
|
14
14
|
import * as http from 'node:http';
|
|
15
|
-
import { loadConfig, getConfigDir, ensureConfigDir, getDefaultConfig, expandPath, createAuditLogger, createCredentialStore, saveConfig, generateLoopbackToken, DEFAULT_LOOPBACK_PORT } from '../core/index.js';
|
|
15
|
+
import { loadConfig, getConfigDir, ensureConfigDir, getDefaultConfig, expandPath, createAuditLogger, createCredentialStore, saveConfig, generateLoopbackToken, DEFAULT_LOOPBACK_PORT, wrapWithCache, resolveCacheTtl } from '../core/index.js';
|
|
16
16
|
import { fileURLToPath } from 'node:url';
|
|
17
17
|
import { createCredentialProxy } from '../daemon.js';
|
|
18
18
|
import { createServiceRegistry, ServiceRegistry } from '../service-registry.js';
|
|
19
19
|
import { createOpenClawIntegration, authProfilesAreSqliteOnly } from '../openclaw/integration.js';
|
|
20
|
+
import { supportsSecretRefIntegrations, wireSecretRefProviders, secretRefWiringStatus } from '../openclaw/secretref.js';
|
|
20
21
|
import { createHermesIntegration, detectHermes } from '../hermes/integration.js';
|
|
22
|
+
import { managedScopeShadowedKeys, HERMES_MANAGED_ENV_PATH } from '../hermes/config-writer.js';
|
|
21
23
|
import { loadPolicyFromConfig, validatePolicyConfig, getDefaultPolicyPresets, matchPolicy } from '../request-policy.js';
|
|
22
24
|
import { stringify as yamlStringify, parse as yamlParse } from 'yaml';
|
|
23
25
|
// Read version from package.json (single source of truth)
|
|
@@ -38,6 +40,49 @@ function validateCredName(label, value) {
|
|
|
38
40
|
process.exit(1);
|
|
39
41
|
}
|
|
40
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Soft hint for 1Password users running in biometric/desktop-app mode: every
|
|
45
|
+
* `op` spawn prompts, which makes unattended agents unusable. The daemon cache
|
|
46
|
+
* (v0.13.1+) bounds it to one prompt per TTL per credential; a service account
|
|
47
|
+
* removes prompts entirely. Printed by `aquaman doctor` and deep doctors.
|
|
48
|
+
*/
|
|
49
|
+
function printOnePasswordModeHint(config, indent = ' ') {
|
|
50
|
+
if (config.credentials.backend !== '1password')
|
|
51
|
+
return;
|
|
52
|
+
if (process.env['OP_SERVICE_ACCOUNT_TOKEN'] || process.env['OP_CONNECT_HOST'])
|
|
53
|
+
return;
|
|
54
|
+
const vault = config.credentials.onePasswordVault || 'aquaman';
|
|
55
|
+
const ttl = resolveCacheTtl(config);
|
|
56
|
+
const cacheNote = ttl > 0
|
|
57
|
+
? `the daemon cache bounds this to one per ${Math.round(ttl / 60)} min per credential`
|
|
58
|
+
: 'and the credential cache is disabled (cacheTtlSeconds: 0)';
|
|
59
|
+
console.log(`${indent}• 1Password is in biometric/desktop-app mode — each vault read prompts (${cacheNote}).`);
|
|
60
|
+
console.log(`${indent} For zero prompts, create a service account scoped to the "${vault}" vault (create the`);
|
|
61
|
+
console.log(`${indent} vault + credentials as a human first, or grant write_items) and start the daemon with`);
|
|
62
|
+
console.log(`${indent} OP_SERVICE_ACCOUNT_TOKEN. If OP_CONNECT_HOST/OP_CONNECT_TOKEN are set they take precedence.`);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Map config.credentials onto CredentialStoreOptions — the single source for
|
|
66
|
+
* every createCredentialStore() call in this CLI. Env fallbacks mirror
|
|
67
|
+
* applyEnvOverrides() (belt and braces for callers that build partial configs).
|
|
68
|
+
*/
|
|
69
|
+
function storeOptionsFromConfig(config) {
|
|
70
|
+
return {
|
|
71
|
+
backend: config.credentials.backend,
|
|
72
|
+
encryptionPassword: config.credentials.encryptionPassword || process.env['AQUAMAN_ENCRYPTION_PASSWORD'] || process.env['AQUAMAN_KEEPASS_PASSWORD'],
|
|
73
|
+
vaultAddress: config.credentials.vaultAddress || process.env['VAULT_ADDR'],
|
|
74
|
+
vaultToken: config.credentials.vaultToken || process.env['VAULT_TOKEN'],
|
|
75
|
+
vaultNamespace: config.credentials.vaultNamespace,
|
|
76
|
+
vaultMountPath: config.credentials.vaultMountPath,
|
|
77
|
+
onePasswordVault: config.credentials.onePasswordVault,
|
|
78
|
+
onePasswordAccount: config.credentials.onePasswordAccount,
|
|
79
|
+
keepassxcDatabasePath: config.credentials.keepassxcDatabasePath,
|
|
80
|
+
keepassxcKeyFilePath: config.credentials.keepassxcKeyFilePath,
|
|
81
|
+
bitwardenFolder: config.credentials.bitwardenFolder,
|
|
82
|
+
bitwardenOrganizationId: config.credentials.bitwardenOrganizationId,
|
|
83
|
+
bitwardenCollectionId: config.credentials.bitwardenCollectionId
|
|
84
|
+
};
|
|
85
|
+
}
|
|
41
86
|
/**
|
|
42
87
|
* Build loopback listener options from config for the daemon. Returns undefined
|
|
43
88
|
* unless the loopback listener is explicitly enabled. If it's enabled but no
|
|
@@ -310,21 +355,10 @@ program
|
|
|
310
355
|
// Vault \u2014 backend + creds
|
|
311
356
|
try {
|
|
312
357
|
const config = loadConfig();
|
|
313
|
-
const store = await createCredentialStore(
|
|
314
|
-
backend: config.credentials.backend,
|
|
315
|
-
encryptionPassword: config.credentials.encryptionPassword,
|
|
316
|
-
vaultAddress: config.credentials.vaultAddress,
|
|
317
|
-
vaultToken: config.credentials.vaultToken,
|
|
318
|
-
onePasswordVault: config.credentials.onePasswordVault,
|
|
319
|
-
onePasswordAccount: config.credentials.onePasswordAccount,
|
|
320
|
-
keepassxcDatabasePath: config.credentials.keepassxcDatabasePath,
|
|
321
|
-
keepassxcKeyFilePath: config.credentials.keepassxcKeyFilePath,
|
|
322
|
-
bitwardenFolder: config.credentials.bitwardenFolder,
|
|
323
|
-
bitwardenOrganizationId: config.credentials.bitwardenOrganizationId,
|
|
324
|
-
bitwardenCollectionId: config.credentials.bitwardenCollectionId
|
|
325
|
-
});
|
|
358
|
+
const store = await createCredentialStore(storeOptionsFromConfig(config));
|
|
326
359
|
const creds = await store.list();
|
|
327
360
|
console.log(` \u2713 ${config.credentials.backend} backend (${creds.length} credential${creds.length !== 1 ? 's' : ''})`);
|
|
361
|
+
printOnePasswordModeHint(config);
|
|
328
362
|
}
|
|
329
363
|
catch (err) {
|
|
330
364
|
console.log(` \u2717 Backend not accessible: ${err.message}`);
|
|
@@ -528,19 +562,7 @@ async function runVaultSetup(opts) {
|
|
|
528
562
|
fs.mkdirSync(auditDir, { recursive: true, mode: 0o700 });
|
|
529
563
|
let store;
|
|
530
564
|
try {
|
|
531
|
-
store = await createCredentialStore(
|
|
532
|
-
backend: config.credentials.backend,
|
|
533
|
-
encryptionPassword: config.credentials.encryptionPassword || process.env['AQUAMAN_ENCRYPTION_PASSWORD'] || process.env['AQUAMAN_KEEPASS_PASSWORD'],
|
|
534
|
-
vaultAddress: config.credentials.vaultAddress || process.env['VAULT_ADDR'],
|
|
535
|
-
vaultToken: config.credentials.vaultToken || process.env['VAULT_TOKEN'],
|
|
536
|
-
onePasswordVault: config.credentials.onePasswordVault,
|
|
537
|
-
onePasswordAccount: config.credentials.onePasswordAccount,
|
|
538
|
-
keepassxcDatabasePath: config.credentials.keepassxcDatabasePath,
|
|
539
|
-
keepassxcKeyFilePath: config.credentials.keepassxcKeyFilePath,
|
|
540
|
-
bitwardenFolder: config.credentials.bitwardenFolder,
|
|
541
|
-
bitwardenOrganizationId: config.credentials.bitwardenOrganizationId,
|
|
542
|
-
bitwardenCollectionId: config.credentials.bitwardenCollectionId
|
|
543
|
-
});
|
|
565
|
+
store = await createCredentialStore(storeOptionsFromConfig(config));
|
|
544
566
|
}
|
|
545
567
|
catch (err) {
|
|
546
568
|
console.error(` Failed to initialize ${backend}: ${err instanceof Error ? err.message : err}`);
|
|
@@ -666,21 +688,7 @@ openclaw
|
|
|
666
688
|
// Initialize credential store
|
|
667
689
|
let credentialStore;
|
|
668
690
|
try {
|
|
669
|
-
credentialStore = await createCredentialStore(
|
|
670
|
-
backend: config.credentials.backend,
|
|
671
|
-
encryptionPassword: config.credentials.encryptionPassword,
|
|
672
|
-
vaultAddress: config.credentials.vaultAddress,
|
|
673
|
-
vaultToken: config.credentials.vaultToken,
|
|
674
|
-
vaultNamespace: config.credentials.vaultNamespace,
|
|
675
|
-
vaultMountPath: config.credentials.vaultMountPath,
|
|
676
|
-
onePasswordVault: config.credentials.onePasswordVault,
|
|
677
|
-
onePasswordAccount: config.credentials.onePasswordAccount,
|
|
678
|
-
keepassxcDatabasePath: config.credentials.keepassxcDatabasePath,
|
|
679
|
-
keepassxcKeyFilePath: config.credentials.keepassxcKeyFilePath,
|
|
680
|
-
bitwardenFolder: config.credentials.bitwardenFolder,
|
|
681
|
-
bitwardenOrganizationId: config.credentials.bitwardenOrganizationId,
|
|
682
|
-
bitwardenCollectionId: config.credentials.bitwardenCollectionId
|
|
683
|
-
});
|
|
691
|
+
credentialStore = wrapWithCache(await createCredentialStore(storeOptionsFromConfig(config)), resolveCacheTtl(config));
|
|
684
692
|
}
|
|
685
693
|
catch (err) {
|
|
686
694
|
console.error(`Credential backend "${config.credentials.backend}" failed to initialize: ${err instanceof Error ? err.message : err}`);
|
|
@@ -822,21 +830,7 @@ program
|
|
|
822
830
|
// Initialize credential store
|
|
823
831
|
let credentialStore;
|
|
824
832
|
try {
|
|
825
|
-
credentialStore = await createCredentialStore(
|
|
826
|
-
backend: config.credentials.backend,
|
|
827
|
-
encryptionPassword: config.credentials.encryptionPassword,
|
|
828
|
-
vaultAddress: config.credentials.vaultAddress,
|
|
829
|
-
vaultToken: config.credentials.vaultToken,
|
|
830
|
-
vaultNamespace: config.credentials.vaultNamespace,
|
|
831
|
-
vaultMountPath: config.credentials.vaultMountPath,
|
|
832
|
-
onePasswordVault: config.credentials.onePasswordVault,
|
|
833
|
-
onePasswordAccount: config.credentials.onePasswordAccount,
|
|
834
|
-
keepassxcDatabasePath: config.credentials.keepassxcDatabasePath,
|
|
835
|
-
keepassxcKeyFilePath: config.credentials.keepassxcKeyFilePath,
|
|
836
|
-
bitwardenFolder: config.credentials.bitwardenFolder,
|
|
837
|
-
bitwardenOrganizationId: config.credentials.bitwardenOrganizationId,
|
|
838
|
-
bitwardenCollectionId: config.credentials.bitwardenCollectionId
|
|
839
|
-
});
|
|
833
|
+
credentialStore = wrapWithCache(await createCredentialStore(storeOptionsFromConfig(config)), resolveCacheTtl(config));
|
|
840
834
|
}
|
|
841
835
|
catch (err) {
|
|
842
836
|
console.error(`Credential backend "${config.credentials.backend}" failed to initialize: ${err instanceof Error ? err.message : err}`);
|
|
@@ -896,21 +890,7 @@ openclaw
|
|
|
896
890
|
// Initialize credential store
|
|
897
891
|
let credentialStore;
|
|
898
892
|
try {
|
|
899
|
-
credentialStore = await createCredentialStore(
|
|
900
|
-
backend: config.credentials.backend,
|
|
901
|
-
encryptionPassword: config.credentials.encryptionPassword,
|
|
902
|
-
vaultAddress: config.credentials.vaultAddress,
|
|
903
|
-
vaultToken: config.credentials.vaultToken,
|
|
904
|
-
vaultNamespace: config.credentials.vaultNamespace,
|
|
905
|
-
vaultMountPath: config.credentials.vaultMountPath,
|
|
906
|
-
onePasswordVault: config.credentials.onePasswordVault,
|
|
907
|
-
onePasswordAccount: config.credentials.onePasswordAccount,
|
|
908
|
-
keepassxcDatabasePath: config.credentials.keepassxcDatabasePath,
|
|
909
|
-
keepassxcKeyFilePath: config.credentials.keepassxcKeyFilePath,
|
|
910
|
-
bitwardenFolder: config.credentials.bitwardenFolder,
|
|
911
|
-
bitwardenOrganizationId: config.credentials.bitwardenOrganizationId,
|
|
912
|
-
bitwardenCollectionId: config.credentials.bitwardenCollectionId
|
|
913
|
-
});
|
|
893
|
+
credentialStore = wrapWithCache(await createCredentialStore(storeOptionsFromConfig(config)), resolveCacheTtl(config));
|
|
914
894
|
}
|
|
915
895
|
catch (err) {
|
|
916
896
|
console.error(`Credential backend "${config.credentials.backend}" failed to initialize: ${err instanceof Error ? err.message : err}`);
|
|
@@ -1104,6 +1084,10 @@ hermes
|
|
|
1104
1084
|
}
|
|
1105
1085
|
catch { /* unreadable */ }
|
|
1106
1086
|
console.log(` ~/.hermes/.env: ${envWired ? aqua('wired') : 'not wired'} (${envPath})`);
|
|
1087
|
+
const shadowed = managedScopeShadowedKeys(integration.configureHermes());
|
|
1088
|
+
if (shadowed.length > 0) {
|
|
1089
|
+
console.log(` Managed scope: ⚠ ${HERMES_MANAGED_ENV_PATH} pins ${shadowed.join(', ')} — proxy bypassed for these keys`);
|
|
1090
|
+
}
|
|
1107
1091
|
const info = await detectHermes(config.hermes?.binaryPath || 'hermes');
|
|
1108
1092
|
console.log(` Hermes CLI: ${info.installed ? aqua(`v${info.version}`) : 'not found'}`);
|
|
1109
1093
|
// Live liveness probe of the loopback listener (no token needed for _health).
|
|
@@ -1162,6 +1146,16 @@ hermes
|
|
|
1162
1146
|
catch {
|
|
1163
1147
|
fail(`Could not read ${envPath}`);
|
|
1164
1148
|
}
|
|
1149
|
+
// 3b. Hermes >=0.17 managed scope: /etc/hermes/.env overrides ~/.hermes/.env
|
|
1150
|
+
{
|
|
1151
|
+
const shadowed = managedScopeShadowedKeys(integration.configureHermes());
|
|
1152
|
+
if (shadowed.length > 0) {
|
|
1153
|
+
fail(`Hermes managed scope (${HERMES_MANAGED_ENV_PATH}) pins ${shadowed.join(', ')} — it overrides ~/.hermes/.env, so the proxy is BYPASSED for these keys. Remove them from the managed file or point them at the proxy.`);
|
|
1154
|
+
}
|
|
1155
|
+
else {
|
|
1156
|
+
pass('No managed-scope override of aquaman env vars');
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1165
1159
|
// 4. Per-service vault credentials resolve
|
|
1166
1160
|
const wired = integration.wiredServices();
|
|
1167
1161
|
if (wired.length === 0) {
|
|
@@ -1169,21 +1163,7 @@ hermes
|
|
|
1169
1163
|
}
|
|
1170
1164
|
else {
|
|
1171
1165
|
try {
|
|
1172
|
-
const store = await createCredentialStore(
|
|
1173
|
-
backend: config.credentials.backend,
|
|
1174
|
-
encryptionPassword: config.credentials.encryptionPassword,
|
|
1175
|
-
vaultAddress: config.credentials.vaultAddress,
|
|
1176
|
-
vaultToken: config.credentials.vaultToken,
|
|
1177
|
-
vaultNamespace: config.credentials.vaultNamespace,
|
|
1178
|
-
vaultMountPath: config.credentials.vaultMountPath,
|
|
1179
|
-
onePasswordVault: config.credentials.onePasswordVault,
|
|
1180
|
-
onePasswordAccount: config.credentials.onePasswordAccount,
|
|
1181
|
-
keepassxcDatabasePath: config.credentials.keepassxcDatabasePath,
|
|
1182
|
-
keepassxcKeyFilePath: config.credentials.keepassxcKeyFilePath,
|
|
1183
|
-
bitwardenFolder: config.credentials.bitwardenFolder,
|
|
1184
|
-
bitwardenOrganizationId: config.credentials.bitwardenOrganizationId,
|
|
1185
|
-
bitwardenCollectionId: config.credentials.bitwardenCollectionId,
|
|
1186
|
-
});
|
|
1166
|
+
const store = await createCredentialStore(storeOptionsFromConfig(config));
|
|
1187
1167
|
for (const svc of wired) {
|
|
1188
1168
|
const val = await store.get(svc, 'api_key');
|
|
1189
1169
|
if (val)
|
|
@@ -1191,6 +1171,7 @@ hermes
|
|
|
1191
1171
|
else
|
|
1192
1172
|
fail(`No credential for ${svc}/api_key — run: aquaman credentials add ${svc} api_key`);
|
|
1193
1173
|
}
|
|
1174
|
+
printOnePasswordModeHint(config, ' ');
|
|
1194
1175
|
}
|
|
1195
1176
|
catch (err) {
|
|
1196
1177
|
fail(`Vault backend "${config.credentials.backend}" failed: ${err instanceof Error ? err.message : err}`);
|
|
@@ -1379,7 +1360,7 @@ openclaw
|
|
|
1379
1360
|
process.exit(1);
|
|
1380
1361
|
}
|
|
1381
1362
|
}
|
|
1382
|
-
catch
|
|
1363
|
+
catch {
|
|
1383
1364
|
console.error(` Cannot reach Vault at ${vaultAddr}.`);
|
|
1384
1365
|
process.exit(1);
|
|
1385
1366
|
}
|
|
@@ -1459,19 +1440,7 @@ openclaw
|
|
|
1459
1440
|
// 3. Prompt for API keys (or read from env in non-interactive mode)
|
|
1460
1441
|
let store;
|
|
1461
1442
|
try {
|
|
1462
|
-
store = await createCredentialStore(
|
|
1463
|
-
backend: config.credentials.backend,
|
|
1464
|
-
encryptionPassword: config.credentials.encryptionPassword || process.env['AQUAMAN_ENCRYPTION_PASSWORD'] || process.env['AQUAMAN_KEEPASS_PASSWORD'],
|
|
1465
|
-
vaultAddress: config.credentials.vaultAddress || process.env['VAULT_ADDR'],
|
|
1466
|
-
vaultToken: config.credentials.vaultToken || process.env['VAULT_TOKEN'],
|
|
1467
|
-
onePasswordVault: config.credentials.onePasswordVault,
|
|
1468
|
-
onePasswordAccount: config.credentials.onePasswordAccount,
|
|
1469
|
-
keepassxcDatabasePath: config.credentials.keepassxcDatabasePath,
|
|
1470
|
-
keepassxcKeyFilePath: config.credentials.keepassxcKeyFilePath,
|
|
1471
|
-
bitwardenFolder: config.credentials.bitwardenFolder,
|
|
1472
|
-
bitwardenOrganizationId: config.credentials.bitwardenOrganizationId,
|
|
1473
|
-
bitwardenCollectionId: config.credentials.bitwardenCollectionId
|
|
1474
|
-
});
|
|
1443
|
+
store = await createCredentialStore(storeOptionsFromConfig(config));
|
|
1475
1444
|
}
|
|
1476
1445
|
catch (err) {
|
|
1477
1446
|
console.error(` Failed to initialize ${backend} credential store: ${err instanceof Error ? err.message : err}`);
|
|
@@ -1657,22 +1626,54 @@ openclaw
|
|
|
1657
1626
|
if (!openclawConfig.plugins.allow.includes('aquaman-plugin')) {
|
|
1658
1627
|
openclawConfig.plugins.allow.push('aquaman-plugin');
|
|
1659
1628
|
}
|
|
1629
|
+
const configuredServices = storedServices.length > 0 ? storedServices : ['anthropic', 'openai'];
|
|
1660
1630
|
openclawConfig.plugins.entries['aquaman-plugin'] = {
|
|
1661
1631
|
enabled: true,
|
|
1662
1632
|
config: {
|
|
1663
1633
|
backend,
|
|
1664
|
-
services:
|
|
1634
|
+
services: configuredServices,
|
|
1665
1635
|
}
|
|
1666
1636
|
};
|
|
1637
|
+
// Detect the gateway version once \u2014 it gates the credential surface:
|
|
1638
|
+
// SecretRef wiring (canonical, >= 2026.6.5) vs the legacy
|
|
1639
|
+
// auth-profiles.json placeholder. AQUAMAN_OPENCLAW_VERSION overrides
|
|
1640
|
+
// detection (tests + operators pinning behavior explicitly).
|
|
1641
|
+
let ocVersion = process.env.AQUAMAN_OPENCLAW_VERSION || null;
|
|
1642
|
+
if (!ocVersion) {
|
|
1643
|
+
try {
|
|
1644
|
+
const { execSync } = await import('node:child_process');
|
|
1645
|
+
ocVersion = execSync('openclaw --version', { stdio: 'pipe', encoding: 'utf-8', timeout: 5000 }).trim();
|
|
1646
|
+
}
|
|
1647
|
+
catch { /* CLI not on PATH */ }
|
|
1648
|
+
}
|
|
1649
|
+
// b2. SecretRef provider wiring (v0.14.0+, OpenClaw >= 2026.6.5).
|
|
1650
|
+
// Config-level refs in openclaw.json are runtime-read on every
|
|
1651
|
+
// version and survive OpenClaw's plaintext scrubs \u2014 no SQLite
|
|
1652
|
+
// import step needed, unlike the legacy placeholder.
|
|
1653
|
+
const secretRefSupported = supportsSecretRefIntegrations(ocVersion);
|
|
1654
|
+
if (secretRefSupported) {
|
|
1655
|
+
const wiring = wireSecretRefProviders(openclawConfig, configuredServices);
|
|
1656
|
+
if (wiring.wiredProviders.length > 0) {
|
|
1657
|
+
console.log(` \u2713 SecretRef wiring for ${wiring.wiredProviders.join(', ')} (canonical credential surface)`);
|
|
1658
|
+
}
|
|
1659
|
+
const userKept = wiring.skippedProviders.filter(s => s === 'anthropic' || s === 'openai');
|
|
1660
|
+
if (userKept.length > 0) {
|
|
1661
|
+
console.log(` \u2192 Left existing user-set apiKey untouched for: ${userKept.join(', ')}`);
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1667
1664
|
fs.writeFileSync(openclawJsonPath, JSON.stringify(openclawConfig, null, 2), 'utf-8');
|
|
1668
1665
|
console.log(' \u2713 Plugin config written to ' + openclawJsonPath);
|
|
1669
|
-
// c. Generate auth-profiles.json (only if missing)
|
|
1666
|
+
// c. Generate auth-profiles.json (only if missing). With SecretRef
|
|
1667
|
+
// wiring active this is a redundant legacy surface \u2014 skip it so
|
|
1668
|
+
// `openclaw secrets audit` has no plaintext placeholder to flag.
|
|
1670
1669
|
const profilesPath = path.join(openclawStateDir, 'agents', 'main', 'agent', 'auth-profiles.json');
|
|
1671
|
-
if (
|
|
1670
|
+
if (secretRefSupported) {
|
|
1671
|
+
console.log(' \u2192 Legacy auth-profiles.json placeholder skipped (SecretRef wiring replaces it)');
|
|
1672
|
+
}
|
|
1673
|
+
else if (!fs.existsSync(profilesPath)) {
|
|
1672
1674
|
const profiles = {};
|
|
1673
1675
|
const order = {};
|
|
1674
|
-
const
|
|
1675
|
-
for (const svc of profileServices) {
|
|
1676
|
+
for (const svc of configuredServices) {
|
|
1676
1677
|
if (svc === 'anthropic' || svc === 'openai') {
|
|
1677
1678
|
profiles[`${svc}:default`] = {
|
|
1678
1679
|
type: 'api_key',
|
|
@@ -1688,20 +1689,13 @@ openclaw
|
|
|
1688
1689
|
console.log(' \u2713 Auth profiles generated at ' + profilesPath);
|
|
1689
1690
|
// OpenClaw >= 2026.6.5 reads provider auth profiles from each agent's
|
|
1690
1691
|
// openclaw-agent.sqlite, not auth-profiles.json (openclaw/openclaw#89102).
|
|
1691
|
-
//
|
|
1692
|
-
//
|
|
1692
|
+
// On those versions the SecretRef branch above applies instead; this
|
|
1693
|
+
// hint only fires when the version couldn't be detected (SecretRef
|
|
1694
|
+
// gate returned false) but the runtime turns out to be >= 2026.6.5.
|
|
1695
|
+
// We deliberately do NOT auto-run `openclaw doctor --fix` here:
|
|
1693
1696
|
// it is OpenClaw's broad config-healing command \u2014 verified to take
|
|
1694
1697
|
// ~19s (gateway probes) and to rewrite openclaw.json (it reset our
|
|
1695
|
-
// plugin entry to bundled defaults in testing).
|
|
1696
|
-
// doctor` detects the SQLite-only case and prints the import step so
|
|
1697
|
-
// the operator runs it intentionally. Holistic fix (SecretRef
|
|
1698
|
-
// provider manifest) is tracked for the next plugin touch.
|
|
1699
|
-
let ocVersion = null;
|
|
1700
|
-
try {
|
|
1701
|
-
const { execSync } = await import('node:child_process');
|
|
1702
|
-
ocVersion = execSync('openclaw --version', { stdio: 'pipe', encoding: 'utf-8', timeout: 5000 }).trim();
|
|
1703
|
-
}
|
|
1704
|
-
catch { /* CLI not on PATH */ }
|
|
1698
|
+
// plugin entry to bundled defaults in testing).
|
|
1705
1699
|
if (authProfilesAreSqliteOnly(ocVersion)) {
|
|
1706
1700
|
console.log(' \u2192 OpenClaw \u2265 2026.6.5 reads auth profiles from SQLite; import the placeholder once:');
|
|
1707
1701
|
console.log(' openclaw doctor --fix (backs up openclaw.json first; see aquaman openclaw doctor)');
|
|
@@ -1943,9 +1937,10 @@ openclaw
|
|
|
1943
1937
|
console.log(` \u2139 ${aqua('Policy')} not configured \u2014 agents can call any endpoint on proxied services`);
|
|
1944
1938
|
console.log(' \u2192 Run: aquaman setup (or add policy rules to ~/.aquaman/config.yaml)');
|
|
1945
1939
|
}
|
|
1946
|
-
// 6. OpenClaw detection
|
|
1940
|
+
// 6. OpenClaw detection. AQUAMAN_OPENCLAW_VERSION overrides version
|
|
1941
|
+
// detection (tests + operators pinning behavior explicitly).
|
|
1947
1942
|
let openclawDetected = false;
|
|
1948
|
-
let openclawVersion = null;
|
|
1943
|
+
let openclawVersion = process.env.AQUAMAN_OPENCLAW_VERSION || null;
|
|
1949
1944
|
let cliFound = false;
|
|
1950
1945
|
try {
|
|
1951
1946
|
const { execSync } = await import('node:child_process');
|
|
@@ -1956,7 +1951,8 @@ openclaw
|
|
|
1956
1951
|
if (cliFound) {
|
|
1957
1952
|
try {
|
|
1958
1953
|
const { execSync } = await import('node:child_process');
|
|
1959
|
-
const ver =
|
|
1954
|
+
const ver = openclawVersion ??
|
|
1955
|
+
execSync('openclaw --version', { stdio: 'pipe', encoding: 'utf-8', timeout: 5000 }).trim();
|
|
1960
1956
|
openclawVersion = ver;
|
|
1961
1957
|
console.log(` \u2713 ${aqua('OpenClaw')} installed (${ver})`);
|
|
1962
1958
|
}
|
|
@@ -2040,15 +2036,55 @@ openclaw
|
|
|
2040
2036
|
// openclaw.json invalid — already reported above
|
|
2041
2037
|
}
|
|
2042
2038
|
}
|
|
2043
|
-
//
|
|
2044
|
-
//
|
|
2045
|
-
//
|
|
2046
|
-
|
|
2047
|
-
|
|
2039
|
+
// 8.5. SecretRef provider wiring (v0.14.0+, OpenClaw >= 2026.6.5) — the
|
|
2040
|
+
// canonical credential surface that replaces the placeholder
|
|
2041
|
+
// auth-profiles dance.
|
|
2042
|
+
let secretRefFullyWired = false;
|
|
2043
|
+
if (supportsSecretRefIntegrations(openclawVersion) && fs.existsSync(openclawJsonPath)) {
|
|
2044
|
+
try {
|
|
2045
|
+
const openclawConfig = JSON.parse(fs.readFileSync(openclawJsonPath, 'utf-8'));
|
|
2046
|
+
const pluginServices = openclawConfig.plugins?.entries?.['aquaman-plugin']?.config?.services ?? ['anthropic', 'openai'];
|
|
2047
|
+
const status = secretRefWiringStatus(openclawConfig, pluginServices);
|
|
2048
|
+
if (status.providerConfigured && status.missingProviders.length === 0) {
|
|
2049
|
+
secretRefFullyWired = true;
|
|
2050
|
+
console.log(` ✓ ${aqua('SecretRef')} wiring active (${status.wiredProviders.join(', ') || 'no providers'})`);
|
|
2051
|
+
}
|
|
2052
|
+
else if (status.providerConfigured) {
|
|
2053
|
+
// Half-migrated: the provider block exists but some providers still
|
|
2054
|
+
// lack refs — a real inconsistency, fail the check.
|
|
2055
|
+
console.log(` ✗ ${aqua('SecretRef')} wiring incomplete (providers not wired: ${status.missingProviders.join(', ')})`);
|
|
2056
|
+
console.log(' → Run: aquaman openclaw setup (completes the SecretRef wiring in openclaw.json)');
|
|
2057
|
+
issues++;
|
|
2058
|
+
}
|
|
2059
|
+
else {
|
|
2060
|
+
// Legacy placeholder flow still functional — upgrade suggestion,
|
|
2061
|
+
// not a failure. (OpenClaw's configure-flow scrubs treat the
|
|
2062
|
+
// plaintext placeholder as residue; SecretRef wiring is immune.)
|
|
2063
|
+
console.log(` → ${aqua('SecretRef')} wiring available on this OpenClaw — run: aquaman openclaw setup`);
|
|
2064
|
+
console.log(' (replaces the legacy plaintext placeholder, which OpenClaw now scrubs by default)');
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
catch {
|
|
2068
|
+
// openclaw.json invalid — already reported above
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
// 9. Auth profiles. With SecretRef wiring active this surface is
|
|
2072
|
+
// legacy/inert. Otherwise: OpenClaw >= 2026.6.5 reads provider auth
|
|
2073
|
+
// profiles from each agent's openclaw-agent.sqlite and no longer reads
|
|
2074
|
+
// auth-profiles.json at runtime (openclaw/openclaw#89102) — the
|
|
2075
|
+
// placeholder the plugin writes to the JSON file must be imported
|
|
2076
|
+
// into SQLite once via `openclaw doctor --fix`.
|
|
2048
2077
|
const agentDir = path.join(openclawStateDir, 'agents', 'main', 'agent');
|
|
2049
2078
|
const profilesPath = path.join(agentDir, 'auth-profiles.json');
|
|
2050
2079
|
const sqlitePath = path.join(agentDir, 'openclaw-agent.sqlite');
|
|
2051
|
-
if (
|
|
2080
|
+
if (secretRefFullyWired) {
|
|
2081
|
+
console.log(` ✓ ${aqua('Auth profiles')} not needed (SecretRef wiring replaces the placeholder flow)`);
|
|
2082
|
+
if (fs.existsSync(profilesPath)) {
|
|
2083
|
+
console.log(' → Optional: the legacy auth-profiles.json placeholder is inert and may be deleted');
|
|
2084
|
+
console.log(' (`openclaw secrets audit` flags its plaintext placeholder as PLAINTEXT_FOUND)');
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
else if (authProfilesAreSqliteOnly(openclawVersion)) {
|
|
2052
2088
|
if (fs.existsSync(profilesPath)) {
|
|
2053
2089
|
console.log(` \u2717 ${aqua('Auth profiles')} JSON present but ${openclawVersion} reads from SQLite`);
|
|
2054
2090
|
console.log(' \u2192 Import the placeholder once: openclaw doctor --fix (backs up openclaw.json first)');
|