@openclaw/googlechat 2026.5.2-beta.2 → 2026.5.3-beta.1

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.
Files changed (75) hide show
  1. package/dist/api-C67jv0ow.js +627 -0
  2. package/dist/api.js +3 -0
  3. package/dist/channel-DE2hroMn.js +682 -0
  4. package/dist/channel-config-api.js +5 -0
  5. package/dist/channel-plugin-api.js +2 -0
  6. package/dist/channel.runtime-CoSLc-4R.js +920 -0
  7. package/dist/contract-api.js +3 -0
  8. package/dist/doctor-contract-CG1sLToP.js +151 -0
  9. package/dist/doctor-contract-api.js +2 -0
  10. package/dist/index.js +22 -0
  11. package/dist/runtime-api-wkIdfwqY.js +30 -0
  12. package/dist/runtime-api.js +2 -0
  13. package/dist/secret-contract-DtQ_IO7J.js +99 -0
  14. package/dist/secret-contract-api.js +2 -0
  15. package/dist/setup-entry.js +15 -0
  16. package/dist/setup-plugin-api.js +75 -0
  17. package/dist/setup-surface-CofP-Gg9.js +309 -0
  18. package/dist/test-api.js +3 -0
  19. package/package.json +14 -6
  20. package/api.ts +0 -3
  21. package/channel-config-api.ts +0 -1
  22. package/channel-plugin-api.ts +0 -1
  23. package/contract-api.ts +0 -5
  24. package/doctor-contract-api.ts +0 -1
  25. package/index.ts +0 -20
  26. package/runtime-api.ts +0 -60
  27. package/secret-contract-api.ts +0 -5
  28. package/setup-entry.ts +0 -13
  29. package/setup-plugin-api.ts +0 -3
  30. package/src/accounts.ts +0 -169
  31. package/src/actions.test.ts +0 -265
  32. package/src/actions.ts +0 -227
  33. package/src/api.ts +0 -322
  34. package/src/approval-auth.test.ts +0 -24
  35. package/src/approval-auth.ts +0 -32
  36. package/src/auth.ts +0 -209
  37. package/src/channel-config.test.ts +0 -39
  38. package/src/channel.adapters.ts +0 -293
  39. package/src/channel.deps.runtime.ts +0 -28
  40. package/src/channel.runtime.ts +0 -17
  41. package/src/channel.setup.ts +0 -98
  42. package/src/channel.test.ts +0 -695
  43. package/src/channel.ts +0 -256
  44. package/src/config-schema.test.ts +0 -16
  45. package/src/config-schema.ts +0 -3
  46. package/src/doctor-contract.test.ts +0 -75
  47. package/src/doctor-contract.ts +0 -182
  48. package/src/doctor.ts +0 -57
  49. package/src/gateway.ts +0 -63
  50. package/src/google-auth.runtime.test.ts +0 -462
  51. package/src/google-auth.runtime.ts +0 -539
  52. package/src/group-policy.ts +0 -17
  53. package/src/monitor-access.test.ts +0 -443
  54. package/src/monitor-access.ts +0 -405
  55. package/src/monitor-reply-delivery.ts +0 -156
  56. package/src/monitor-routing.ts +0 -55
  57. package/src/monitor-types.ts +0 -33
  58. package/src/monitor-webhook.test.ts +0 -446
  59. package/src/monitor-webhook.ts +0 -285
  60. package/src/monitor.reply-delivery.test.ts +0 -139
  61. package/src/monitor.ts +0 -428
  62. package/src/monitor.webhook-routing.test.ts +0 -252
  63. package/src/runtime.ts +0 -9
  64. package/src/secret-contract.test.ts +0 -60
  65. package/src/secret-contract.ts +0 -161
  66. package/src/sender-allow.ts +0 -46
  67. package/src/setup-core.ts +0 -40
  68. package/src/setup-surface.ts +0 -236
  69. package/src/setup.test.ts +0 -560
  70. package/src/targets.test.ts +0 -421
  71. package/src/targets.ts +0 -66
  72. package/src/types.config.ts +0 -3
  73. package/src/types.ts +0 -73
  74. package/test-api.ts +0 -2
  75. package/tsconfig.json +0 -16
@@ -1,462 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import os from "node:os";
3
- import path from "node:path";
4
- import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
5
-
6
- const mocks = vi.hoisted(() => ({
7
- buildHostnameAllowlistPolicyFromSuffixAllowlist: vi.fn((hosts: string[]) => ({
8
- hostnameAllowlist: hosts,
9
- })),
10
- fetchWithSsrFGuard: vi.fn(),
11
- gaxiosCtor: vi.fn(function MockGaxios(this: { defaults: Record<string, unknown> }, defaults) {
12
- this.defaults = defaults as Record<string, unknown>;
13
- }),
14
- }));
15
-
16
- vi.mock("openclaw/plugin-sdk/ssrf-runtime", () => ({
17
- buildHostnameAllowlistPolicyFromSuffixAllowlist:
18
- mocks.buildHostnameAllowlistPolicyFromSuffixAllowlist,
19
- fetchWithSsrFGuard: mocks.fetchWithSsrFGuard,
20
- }));
21
-
22
- vi.mock("gaxios", () => ({
23
- Gaxios: mocks.gaxiosCtor,
24
- }));
25
-
26
- let __testing: typeof import("./google-auth.runtime.js").__testing;
27
- let createGoogleAuthFetch: typeof import("./google-auth.runtime.js").createGoogleAuthFetch;
28
- let getGoogleAuthTransport: typeof import("./google-auth.runtime.js").getGoogleAuthTransport;
29
- let resolveValidatedGoogleChatCredentials: typeof import("./google-auth.runtime.js").resolveValidatedGoogleChatCredentials;
30
-
31
- beforeAll(async () => {
32
- ({
33
- __testing,
34
- createGoogleAuthFetch,
35
- getGoogleAuthTransport,
36
- resolveValidatedGoogleChatCredentials,
37
- } = await import("./google-auth.runtime.js"));
38
- });
39
-
40
- beforeEach(() => {
41
- __testing.resetGoogleAuthRuntimeForTests();
42
- mocks.buildHostnameAllowlistPolicyFromSuffixAllowlist.mockClear();
43
- mocks.fetchWithSsrFGuard.mockReset();
44
- mocks.gaxiosCtor.mockClear();
45
- });
46
-
47
- afterEach(() => {
48
- vi.restoreAllMocks();
49
- vi.unstubAllGlobals();
50
- });
51
-
52
- describe("googlechat google auth runtime", () => {
53
- it("routes Google auth fetches through the SSRF guard and preserves explicit proxy mTLS", async () => {
54
- const release = vi.fn();
55
- const injectedFetch = vi.fn(globalThis.fetch);
56
- mocks.fetchWithSsrFGuard.mockResolvedValueOnce({
57
- response: new Response("ok", { status: 200 }),
58
- release,
59
- });
60
-
61
- const guardedFetch = createGoogleAuthFetch(injectedFetch);
62
- const response = await guardedFetch("https://oauth2.googleapis.com/token", {
63
- agent: { proxy: new URL("http://proxy.example:8080") },
64
- cert: "CLIENT_CERT",
65
- headers: { "content-type": "application/json" },
66
- key: "CLIENT_KEY",
67
- method: "POST",
68
- proxy: "http://proxy.example:8080",
69
- } as RequestInit);
70
-
71
- expect(mocks.fetchWithSsrFGuard).toHaveBeenCalledWith({
72
- auditContext: "googlechat.auth.google-auth",
73
- dispatcherPolicy: {
74
- allowPrivateProxy: true,
75
- mode: "explicit-proxy",
76
- proxyTls: {
77
- cert: "CLIENT_CERT",
78
- key: "CLIENT_KEY",
79
- },
80
- proxyUrl: "http://proxy.example:8080",
81
- },
82
- fetchImpl: injectedFetch,
83
- init: {
84
- headers: { "content-type": "application/json" },
85
- method: "POST",
86
- },
87
- policy: {
88
- hostnameAllowlist: ["accounts.google.com", "googleapis.com"],
89
- },
90
- url: "https://oauth2.googleapis.com/token",
91
- });
92
- await expect(response.text()).resolves.toBe("ok");
93
- expect(release).toHaveBeenCalledOnce();
94
- });
95
-
96
- it("lets the guard resolve the ambient runtime fetch when no override is injected", async () => {
97
- const release = vi.fn();
98
- mocks.fetchWithSsrFGuard.mockResolvedValueOnce({
99
- response: new Response("ok", { status: 200 }),
100
- release,
101
- });
102
-
103
- const guardedFetch = createGoogleAuthFetch();
104
- await guardedFetch("https://oauth2.googleapis.com/token", {
105
- method: "POST",
106
- } as RequestInit);
107
-
108
- expect(mocks.fetchWithSsrFGuard.mock.calls[0]?.[0]).not.toHaveProperty("fetchImpl");
109
- expect(release).toHaveBeenCalledOnce();
110
- });
111
-
112
- it("keeps using the guard-selected runtime fetch even if global fetch changes later", async () => {
113
- const release = vi.fn();
114
- const originalFetch = globalThis.fetch;
115
- mocks.fetchWithSsrFGuard.mockResolvedValueOnce({
116
- response: new Response("ok", { status: 200 }),
117
- release,
118
- });
119
-
120
- const guardedFetch = createGoogleAuthFetch();
121
- (globalThis as Record<string, unknown>).fetch = vi.fn(async () => new Response("patched"));
122
-
123
- try {
124
- await guardedFetch("https://oauth2.googleapis.com/token", {
125
- method: "POST",
126
- } as RequestInit);
127
- } finally {
128
- (globalThis as Record<string, unknown>).fetch = originalFetch;
129
- }
130
-
131
- expect(mocks.fetchWithSsrFGuard.mock.calls[0]?.[0]).not.toHaveProperty("fetchImpl");
132
- expect(release).toHaveBeenCalledOnce();
133
- });
134
-
135
- it("bypasses explicit proxy when noProxy excludes the Google auth host", async () => {
136
- const release = vi.fn();
137
- mocks.fetchWithSsrFGuard.mockResolvedValueOnce({
138
- response: new Response("ok", { status: 200 }),
139
- release,
140
- });
141
-
142
- const guardedFetch = createGoogleAuthFetch();
143
- const response = await guardedFetch("https://oauth2.googleapis.com/token", {
144
- cert: "CLIENT_CERT",
145
- key: "CLIENT_KEY",
146
- method: "POST",
147
- noProxy: ["oauth2.googleapis.com"],
148
- proxy: "http://proxy.example:8080",
149
- } as RequestInit);
150
-
151
- expect(mocks.fetchWithSsrFGuard).toHaveBeenCalledWith({
152
- auditContext: "googlechat.auth.google-auth",
153
- dispatcherPolicy: {
154
- connect: {
155
- cert: "CLIENT_CERT",
156
- key: "CLIENT_KEY",
157
- },
158
- mode: "direct",
159
- },
160
- init: {
161
- method: "POST",
162
- },
163
- policy: {
164
- hostnameAllowlist: ["accounts.google.com", "googleapis.com"],
165
- },
166
- url: "https://oauth2.googleapis.com/token",
167
- });
168
- await expect(response.text()).resolves.toBe("ok");
169
- expect(release).toHaveBeenCalledOnce();
170
- });
171
-
172
- it("preserves env-proxy transport when HTTPS proxy is configured", async () => {
173
- const release = vi.fn();
174
- mocks.fetchWithSsrFGuard.mockResolvedValueOnce({
175
- response: new Response("ok", { status: 200 }),
176
- release,
177
- });
178
- vi.stubEnv("HTTPS_PROXY", "http://env-proxy.example:8080");
179
- vi.stubEnv("https_proxy", "http://lower-proxy.example:8080");
180
-
181
- const guardedFetch = createGoogleAuthFetch();
182
- const response = await guardedFetch("https://oauth2.googleapis.com/token", {
183
- cert: "CLIENT_CERT",
184
- key: "CLIENT_KEY",
185
- method: "POST",
186
- } as RequestInit);
187
-
188
- expect(mocks.fetchWithSsrFGuard).toHaveBeenCalledWith({
189
- auditContext: "googlechat.auth.google-auth",
190
- dispatcherPolicy: {
191
- mode: "env-proxy",
192
- proxyTls: {
193
- cert: "CLIENT_CERT",
194
- key: "CLIENT_KEY",
195
- },
196
- },
197
- init: {
198
- method: "POST",
199
- },
200
- policy: {
201
- hostnameAllowlist: ["accounts.google.com", "googleapis.com"],
202
- },
203
- url: "https://oauth2.googleapis.com/token",
204
- });
205
- await expect(response.text()).resolves.toBe("ok");
206
- expect(release).toHaveBeenCalledOnce();
207
- });
208
-
209
- it("matches gaxios proxy env precedence for Google auth requests", () => {
210
- vi.stubEnv("HTTP_PROXY", "http://upper-http-proxy.example:8080");
211
- vi.stubEnv("http_proxy", "http://lower-http-proxy.example:8080");
212
- vi.stubEnv("HTTPS_PROXY", "http://upper-https-proxy.example:8080");
213
- vi.stubEnv("https_proxy", "http://lower-https-proxy.example:8080");
214
-
215
- expect(__testing.resolveGoogleAuthEnvProxyUrl("https")).toBe(
216
- "http://upper-https-proxy.example:8080",
217
- );
218
- expect(__testing.resolveGoogleAuthEnvProxyUrl("http")).toBe(
219
- "http://upper-http-proxy.example:8080",
220
- );
221
- });
222
-
223
- it("releases guarded auth fetch resources even when callers do not consume the body", async () => {
224
- const release = vi.fn();
225
- mocks.fetchWithSsrFGuard.mockResolvedValueOnce({
226
- response: new Response("ok", { status: 200 }),
227
- release,
228
- });
229
-
230
- const guardedFetch = createGoogleAuthFetch();
231
- const response = await guardedFetch("https://oauth2.googleapis.com/token", {
232
- method: "POST",
233
- } as RequestInit);
234
-
235
- expect(release).toHaveBeenCalledOnce();
236
- await expect(response.text()).resolves.toBe("ok");
237
- });
238
-
239
- it("rejects oversized guarded auth responses before buffering them into memory", async () => {
240
- const release = vi.fn();
241
- let chunkIndex = 0;
242
- const chunks = [new Uint8Array(700 * 1024), new Uint8Array(400 * 1024)];
243
- const body = new ReadableStream<Uint8Array>({
244
- pull(controller) {
245
- if (chunkIndex < chunks.length) {
246
- controller.enqueue(chunks[chunkIndex++]);
247
- return;
248
- }
249
- controller.close();
250
- },
251
- });
252
- mocks.fetchWithSsrFGuard.mockResolvedValueOnce({
253
- response: new Response(body, { status: 200 }),
254
- release,
255
- });
256
-
257
- const guardedFetch = createGoogleAuthFetch();
258
-
259
- await expect(
260
- guardedFetch("https://oauth2.googleapis.com/token", {
261
- method: "POST",
262
- } as RequestInit),
263
- ).rejects.toThrow("Google auth response exceeds 1048576 bytes.");
264
- expect(release).toHaveBeenCalledOnce();
265
- });
266
-
267
- it("rejects non-stream guarded auth responses instead of buffering them unbounded", async () => {
268
- const release = vi.fn();
269
- const arrayBuffer = vi.fn(async () => new ArrayBuffer(16));
270
- mocks.fetchWithSsrFGuard.mockResolvedValueOnce({
271
- response: {
272
- arrayBuffer,
273
- body: null,
274
- headers: new Headers(),
275
- status: 200,
276
- statusText: "OK",
277
- } as unknown as Response,
278
- release,
279
- });
280
-
281
- const guardedFetch = createGoogleAuthFetch();
282
-
283
- await expect(
284
- guardedFetch("https://oauth2.googleapis.com/token", {
285
- method: "POST",
286
- } as RequestInit),
287
- ).rejects.toThrow(
288
- "Google auth response body stream unavailable; refusing to buffer unbounded response.",
289
- );
290
- expect(arrayBuffer).not.toHaveBeenCalled();
291
- expect(release).toHaveBeenCalledOnce();
292
- });
293
-
294
- it("rejects oversized auth responses from content-length before reading the body", async () => {
295
- const release = vi.fn();
296
- const arrayBuffer = vi.fn(async () => new ArrayBuffer(16));
297
- mocks.fetchWithSsrFGuard.mockResolvedValueOnce({
298
- response: {
299
- arrayBuffer,
300
- body: null,
301
- headers: new Headers({
302
- "content-length": String(2 * 1024 * 1024),
303
- }),
304
- status: 200,
305
- statusText: "OK",
306
- } as unknown as Response,
307
- release,
308
- });
309
-
310
- const guardedFetch = createGoogleAuthFetch();
311
-
312
- await expect(
313
- guardedFetch("https://oauth2.googleapis.com/token", {
314
- method: "POST",
315
- } as RequestInit),
316
- ).rejects.toThrow("Google auth response exceeds 1048576 bytes.");
317
- expect(arrayBuffer).not.toHaveBeenCalled();
318
- expect(release).toHaveBeenCalledOnce();
319
- });
320
-
321
- it("builds a scoped Gaxios transport without mutating global window", async () => {
322
- const originalWindowDescriptor = Object.getOwnPropertyDescriptor(globalThis, "window");
323
- Reflect.deleteProperty(globalThis as object, "window");
324
- try {
325
- const transport = await getGoogleAuthTransport();
326
-
327
- expect(mocks.gaxiosCtor).toHaveBeenCalledOnce();
328
- expect(transport).toMatchObject({
329
- defaults: {
330
- fetchImplementation: expect.any(Function),
331
- },
332
- });
333
- expect("window" in globalThis).toBe(false);
334
- } finally {
335
- if (originalWindowDescriptor) {
336
- Object.defineProperty(globalThis, "window", originalWindowDescriptor);
337
- }
338
- }
339
- });
340
-
341
- it("rejects service-account credentials that override Google auth endpoints", async () => {
342
- await expect(
343
- resolveValidatedGoogleChatCredentials({
344
- accountId: "default",
345
- config: {},
346
- credentialSource: "inline",
347
- credentials: {
348
- client_email: "bot@example.iam.gserviceaccount.com",
349
- private_key: "key",
350
- token_uri: "https://evil.example/token",
351
- type: "service_account",
352
- },
353
- enabled: true,
354
- }),
355
- ).rejects.toThrow(/token_uri/);
356
- });
357
-
358
- it("reads and validates service-account files before passing them to google-auth", async () => {
359
- const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "googlechat-auth-"));
360
- try {
361
- const credentialsPath = path.join(tempDir, "service-account.json");
362
- await fs.writeFile(
363
- credentialsPath,
364
- JSON.stringify({
365
- auth_provider_x509_cert_url: "https://www.googleapis.com/oauth2/v1/certs",
366
- auth_uri: "https://accounts.google.com/o/oauth2/auth",
367
- client_email: "bot@example.iam.gserviceaccount.com",
368
- private_key: "key",
369
- token_uri: "https://oauth2.googleapis.com/token",
370
- type: "service_account",
371
- universe_domain: "googleapis.com",
372
- }),
373
- "utf8",
374
- );
375
-
376
- await expect(
377
- resolveValidatedGoogleChatCredentials({
378
- accountId: "default",
379
- config: {},
380
- credentialSource: "file",
381
- credentialsFile: credentialsPath,
382
- enabled: true,
383
- }),
384
- ).resolves.toMatchObject({
385
- client_email: "bot@example.iam.gserviceaccount.com",
386
- token_uri: "https://oauth2.googleapis.com/token",
387
- type: "service_account",
388
- });
389
- } finally {
390
- await fs.rm(tempDir, { force: true, recursive: true });
391
- }
392
- });
393
-
394
- it("accepts symlinked service-account files used by secret mounts", async () => {
395
- const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "googlechat-auth-link-"));
396
- try {
397
- const credentialsPath = path.join(tempDir, "service-account.json");
398
- const symlinkPath = path.join(tempDir, "service-account-link.json");
399
- await fs.writeFile(
400
- credentialsPath,
401
- JSON.stringify({
402
- auth_provider_x509_cert_url: "https://www.googleapis.com/oauth2/v1/certs",
403
- auth_uri: "https://accounts.google.com/o/oauth2/auth",
404
- client_email: "bot@example.iam.gserviceaccount.com",
405
- private_key: "key",
406
- token_uri: "https://oauth2.googleapis.com/token",
407
- type: "service_account",
408
- universe_domain: "googleapis.com",
409
- }),
410
- "utf8",
411
- );
412
- try {
413
- await fs.symlink(credentialsPath, symlinkPath);
414
- } catch (error) {
415
- if ((error as NodeJS.ErrnoException).code === "EPERM") {
416
- return;
417
- }
418
- throw error;
419
- }
420
-
421
- await expect(
422
- resolveValidatedGoogleChatCredentials({
423
- accountId: "default",
424
- config: {},
425
- credentialSource: "file",
426
- credentialsFile: symlinkPath,
427
- enabled: true,
428
- }),
429
- ).resolves.toMatchObject({
430
- client_email: "bot@example.iam.gserviceaccount.com",
431
- token_uri: "https://oauth2.googleapis.com/token",
432
- type: "service_account",
433
- });
434
- } finally {
435
- await fs.rm(tempDir, { force: true, recursive: true });
436
- }
437
- });
438
-
439
- it("does not disclose raw credential paths or OS errors when file reads fail", async () => {
440
- const missingPath = path.join(os.tmpdir(), "googlechat-auth-missing", "service-account.json");
441
-
442
- await expect(
443
- resolveValidatedGoogleChatCredentials({
444
- accountId: "default",
445
- config: {},
446
- credentialSource: "file",
447
- credentialsFile: missingPath,
448
- enabled: true,
449
- }),
450
- ).rejects.toThrow("Failed to load Google Chat service account file.");
451
-
452
- await expect(
453
- resolveValidatedGoogleChatCredentials({
454
- accountId: "default",
455
- config: {},
456
- credentialSource: "file",
457
- credentialsFile: missingPath,
458
- enabled: true,
459
- }),
460
- ).rejects.not.toThrow(/ENOENT|service-account\.json|googlechat-auth-missing/);
461
- });
462
- });