@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 (`RUNTIME_BEARER_TOKEN` / `~/.vellum/http-token`). |
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/vellum-gateway",
3
- "version": "0.4.17",
3
+ "version": "0.4.19",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./twilio/verify": "./src/twilio/verify.ts",
@@ -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
- // Encrypted store also has nothing, so result should be null
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).toBeNull();
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
- switch (process.platform) {
57
- case "darwin":
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);
@@ -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
@@ -39,16 +39,10 @@ interface StoreFile {
39
39
  }
40
40
 
41
41
  function getPlatformName(): string {
42
- switch (process.platform) {
43
- case "darwin":
44
- return "macOS";
45
- case "win32":
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 {