@vellumai/vellum-gateway 0.4.17 → 0.4.19
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
|
@@ -412,7 +412,7 @@ See [`benchmarking/gateway/README.md`](../benchmarking/gateway/README.md) for lo
|
|
|
412
412
|
| "No route configured" replies | Add a routing entry or set `GATEWAY_UNMAPPED_POLICY=default` with a default assistant |
|
|
413
413
|
| Runtime errors | Is `ASSISTANT_RUNTIME_BASE_URL` reachable? Check runtime logs. |
|
|
414
414
|
| No reply from assistant | Is the assistant runtime processing messages? Check for `RUNTIME_HTTP_PORT` env var. |
|
|
415
|
-
| 403 on channel inbound | The runtime rejected the request because JWT authentication failed. Ensure the gateway and runtime share the same signing key (
|
|
415
|
+
| 403 on channel inbound | The runtime rejected the request because JWT authentication failed. Ensure the gateway and runtime share the same signing key (`~/.vellum/protected/actor-token-signing-key`). |
|
|
416
416
|
|
|
417
417
|
### Guardian-Specific Troubleshooting
|
|
418
418
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { describe, test, expect, beforeEach, afterEach, mock } from "bun:test";
|
|
2
2
|
import { mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
|
-
import { randomBytes } from "node:crypto";
|
|
4
|
+
import { hostname, tmpdir, userInfo } from "node:os";
|
|
5
|
+
import { createCipheriv, pbkdf2Sync, randomBytes } from "node:crypto";
|
|
6
6
|
|
|
7
7
|
// ---------------------------------------------------------------------------
|
|
8
8
|
// Mock logger — capture log calls to verify no secrets leak
|
|
@@ -54,6 +54,69 @@ function writeMetadata(credentials: { service: string; field: string }[]): void
|
|
|
54
54
|
writeFileSync(join(dir, "metadata.json"), JSON.stringify({ credentials }));
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
const ALGORITHM = "aes-256-gcm";
|
|
58
|
+
const AUTH_TAG_LENGTH = 16;
|
|
59
|
+
const KEY_LENGTH = 32;
|
|
60
|
+
const PBKDF2_ITERATIONS = 100_000;
|
|
61
|
+
|
|
62
|
+
function getMachineEntropy(): string {
|
|
63
|
+
const parts: string[] = [];
|
|
64
|
+
try {
|
|
65
|
+
parts.push(hostname());
|
|
66
|
+
} catch {
|
|
67
|
+
parts.push("unknown-host");
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
parts.push(userInfo().username);
|
|
71
|
+
} catch {
|
|
72
|
+
parts.push("unknown-user");
|
|
73
|
+
}
|
|
74
|
+
// Must match assistant/src/util/platform.ts#getPlatformName.
|
|
75
|
+
parts.push(process.platform);
|
|
76
|
+
parts.push(process.arch);
|
|
77
|
+
try {
|
|
78
|
+
parts.push(userInfo().homedir);
|
|
79
|
+
} catch {
|
|
80
|
+
parts.push("/tmp");
|
|
81
|
+
}
|
|
82
|
+
return parts.join(":");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function writeEncryptedStore(entries: Record<string, string>): void {
|
|
86
|
+
const storePath = join(testDir, ".vellum", "protected", "keys.enc");
|
|
87
|
+
mkdirSync(join(testDir, ".vellum", "protected"), { recursive: true });
|
|
88
|
+
|
|
89
|
+
const salt = randomBytes(16);
|
|
90
|
+
const key = pbkdf2Sync(
|
|
91
|
+
getMachineEntropy(),
|
|
92
|
+
salt,
|
|
93
|
+
PBKDF2_ITERATIONS,
|
|
94
|
+
KEY_LENGTH,
|
|
95
|
+
"sha512",
|
|
96
|
+
);
|
|
97
|
+
const encryptedEntries: Record<string, { iv: string; tag: string; data: string }> = {};
|
|
98
|
+
for (const [account, value] of Object.entries(entries)) {
|
|
99
|
+
const iv = randomBytes(12);
|
|
100
|
+
const cipher = createCipheriv(ALGORITHM, key, iv, {
|
|
101
|
+
authTagLength: AUTH_TAG_LENGTH,
|
|
102
|
+
});
|
|
103
|
+
const encrypted = Buffer.concat([cipher.update(value, "utf-8"), cipher.final()]);
|
|
104
|
+
const tag = cipher.getAuthTag();
|
|
105
|
+
encryptedEntries[account] = {
|
|
106
|
+
iv: iv.toString("hex"),
|
|
107
|
+
tag: tag.toString("hex"),
|
|
108
|
+
data: encrypted.toString("hex"),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const store = {
|
|
113
|
+
version: 1,
|
|
114
|
+
salt: salt.toString("hex"),
|
|
115
|
+
entries: encryptedEntries,
|
|
116
|
+
};
|
|
117
|
+
writeFileSync(storePath, JSON.stringify(store));
|
|
118
|
+
}
|
|
119
|
+
|
|
57
120
|
const originalPlatform = process.platform;
|
|
58
121
|
|
|
59
122
|
beforeEach(() => {
|
|
@@ -161,10 +224,17 @@ describe("readTelegramCredentials: keychain on macOS", () => {
|
|
|
161
224
|
{ service: "telegram", field: "webhook_secret" },
|
|
162
225
|
]);
|
|
163
226
|
|
|
164
|
-
// Keychain throws (credential not found) — fall through to encrypted store
|
|
165
|
-
|
|
227
|
+
// Keychain throws (credential not found) — fall through to encrypted store.
|
|
228
|
+
writeEncryptedStore({
|
|
229
|
+
"credential:telegram:bot_token": "enc-bot-token",
|
|
230
|
+
"credential:telegram:webhook_secret": "enc-webhook-secret",
|
|
231
|
+
});
|
|
232
|
+
|
|
166
233
|
const result = readTelegramCredentials();
|
|
167
|
-
expect(result).
|
|
234
|
+
expect(result).toEqual({
|
|
235
|
+
botToken: "enc-bot-token",
|
|
236
|
+
webhookSecret: "enc-webhook-secret",
|
|
237
|
+
});
|
|
168
238
|
});
|
|
169
239
|
});
|
|
170
240
|
|
|
@@ -53,19 +53,8 @@ function getMachineEntropy(): string {
|
|
|
53
53
|
} catch {
|
|
54
54
|
parts.push("unknown-user");
|
|
55
55
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
parts.push("macOS");
|
|
59
|
-
break;
|
|
60
|
-
case "win32":
|
|
61
|
-
parts.push("Windows");
|
|
62
|
-
break;
|
|
63
|
-
case "linux":
|
|
64
|
-
parts.push("Linux");
|
|
65
|
-
break;
|
|
66
|
-
default:
|
|
67
|
-
parts.push(process.platform);
|
|
68
|
-
}
|
|
56
|
+
// Must mirror assistant/src/util/platform.ts#getPlatformName (raw platform).
|
|
57
|
+
parts.push(process.platform);
|
|
69
58
|
parts.push(process.arch);
|
|
70
59
|
try {
|
|
71
60
|
parts.push(userInfo().homedir);
|
package/src/auth/scopes.ts
CHANGED
|
@@ -42,6 +42,9 @@ const PROFILE_SCOPES: Record<ScopeProfile, ReadonlySet<Scope>> = {
|
|
|
42
42
|
ipc_v1: new Set<Scope>([
|
|
43
43
|
'ipc.all',
|
|
44
44
|
]),
|
|
45
|
+
ui_page_v1: new Set<Scope>([
|
|
46
|
+
'settings.read',
|
|
47
|
+
]),
|
|
45
48
|
};
|
|
46
49
|
|
|
47
50
|
// ---------------------------------------------------------------------------
|
package/src/auth/types.ts
CHANGED
|
@@ -13,7 +13,8 @@ export type ScopeProfile =
|
|
|
13
13
|
| 'actor_client_v1'
|
|
14
14
|
| 'gateway_ingress_v1'
|
|
15
15
|
| 'gateway_service_v1'
|
|
16
|
-
| 'ipc_v1'
|
|
16
|
+
| 'ipc_v1'
|
|
17
|
+
| 'ui_page_v1';
|
|
17
18
|
|
|
18
19
|
// ---------------------------------------------------------------------------
|
|
19
20
|
// Individual scope strings
|
package/src/credential-reader.ts
CHANGED
|
@@ -39,16 +39,10 @@ interface StoreFile {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
function getPlatformName(): string {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return "Windows";
|
|
47
|
-
case "linux":
|
|
48
|
-
return "Linux";
|
|
49
|
-
default:
|
|
50
|
-
return process.platform;
|
|
51
|
-
}
|
|
42
|
+
// Must match assistant/src/util/platform.ts#getPlatformName exactly.
|
|
43
|
+
// Using user-friendly labels like "macOS" here changes PBKDF2 entropy and
|
|
44
|
+
// makes gateway unable to decrypt credentials written by the daemon.
|
|
45
|
+
return process.platform;
|
|
52
46
|
}
|
|
53
47
|
|
|
54
48
|
function getMachineEntropy(): string {
|