memos-mcp 1.0.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.
@@ -0,0 +1,322 @@
1
+ /**
2
+ * Tests for Certificate Manager
3
+ */
4
+
5
+ import { describe, it, beforeEach, afterEach } from "node:test";
6
+ import assert from "node:assert";
7
+ import { existsSync, mkdirSync, writeFileSync, unlinkSync, readFileSync } from "node:fs";
8
+ import path from "node:path";
9
+ import os from "node:os";
10
+ import {
11
+ getAllIntermediateCerts,
12
+ getIntermediateCerts,
13
+ refreshAllCerts,
14
+ clearCache,
15
+ getCacheStatus,
16
+ } from "./cert-manager.ts";
17
+
18
+ // Test cache directory (same as in cert-manager.ts)
19
+ const CERT_CACHE_DIR = path.join(os.homedir(), ".cache", "memos-mcp", "certs");
20
+ const CACHE_FILE = path.join(CERT_CACHE_DIR, "cert-cache.json");
21
+
22
+ describe("CertManager", () => {
23
+ // Store original cache if exists
24
+ let originalCache: string | null = null;
25
+
26
+ beforeEach(() => {
27
+ // Backup original cache if it exists (sync for beforeEach)
28
+ if (existsSync(CACHE_FILE)) {
29
+ originalCache = readFileSync(CACHE_FILE, "utf-8");
30
+ }
31
+ });
32
+
33
+ afterEach(async () => {
34
+ // Restore original cache
35
+ if (originalCache) {
36
+ writeFileSync(CACHE_FILE, originalCache);
37
+ } else if (existsSync(CACHE_FILE)) {
38
+ // If there was no original cache, remove the test one
39
+ unlinkSync(CACHE_FILE);
40
+ }
41
+ originalCache = null;
42
+ });
43
+
44
+ describe("clearCache", () => {
45
+ it("should remove the cache file", async () => {
46
+ // Ensure cache exists first
47
+ if (!existsSync(CERT_CACHE_DIR)) {
48
+ mkdirSync(CERT_CACHE_DIR, { recursive: true });
49
+ }
50
+ writeFileSync(CACHE_FILE, JSON.stringify({ version: 1, certs: {} }));
51
+
52
+ await clearCache();
53
+
54
+ assert.strictEqual(existsSync(CACHE_FILE), false);
55
+ });
56
+
57
+ it("should not throw if cache file does not exist", async () => {
58
+ // Ensure cache doesn't exist
59
+ if (existsSync(CACHE_FILE)) {
60
+ unlinkSync(CACHE_FILE);
61
+ }
62
+
63
+ await assert.doesNotReject(async () => await clearCache());
64
+ });
65
+ });
66
+
67
+ describe("getCacheStatus", () => {
68
+ it("should return empty object when cache is empty", async () => {
69
+ await clearCache();
70
+
71
+ const status = await getCacheStatus();
72
+
73
+ assert.deepStrictEqual(status, {});
74
+ });
75
+
76
+ it("should return status for cached certificates", async () => {
77
+ // Clear in-memory cache first so it reloads from file
78
+ await clearCache();
79
+
80
+ // Create a mock cache
81
+ const mockCache = {
82
+ version: 1,
83
+ certs: {
84
+ E8: {
85
+ pem: "-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----",
86
+ fetchedAt: Date.now(),
87
+ expiresAt: Date.now() + 365 * 24 * 60 * 60 * 1000, // 1 year from now
88
+ },
89
+ },
90
+ };
91
+
92
+ if (!existsSync(CERT_CACHE_DIR)) {
93
+ mkdirSync(CERT_CACHE_DIR, { recursive: true });
94
+ }
95
+ writeFileSync(CACHE_FILE, JSON.stringify(mockCache));
96
+
97
+ const status = await getCacheStatus();
98
+
99
+ assert.ok(status.E8);
100
+ assert.ok(status.E8.fetchedAt instanceof Date);
101
+ assert.ok(status.E8.expiresAt instanceof Date);
102
+ assert.strictEqual(typeof status.E8.needsRefresh, "boolean");
103
+ });
104
+
105
+ it("should indicate needsRefresh for old cache entries", async () => {
106
+ // Clear in-memory cache first so it reloads from file
107
+ await clearCache();
108
+
109
+ // Create an old cache entry (40 days ago)
110
+ const fortyDaysAgo = Date.now() - 40 * 24 * 60 * 60 * 1000;
111
+ const mockCache = {
112
+ version: 1,
113
+ certs: {
114
+ E8: {
115
+ pem: "-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----",
116
+ fetchedAt: fortyDaysAgo,
117
+ expiresAt: Date.now() + 365 * 24 * 60 * 60 * 1000,
118
+ },
119
+ },
120
+ };
121
+
122
+ if (!existsSync(CERT_CACHE_DIR)) {
123
+ mkdirSync(CERT_CACHE_DIR, { recursive: true });
124
+ }
125
+ writeFileSync(CACHE_FILE, JSON.stringify(mockCache));
126
+
127
+ const status = await getCacheStatus();
128
+
129
+ assert.strictEqual(status.E8.needsRefresh, true);
130
+ });
131
+
132
+ it("should indicate needsRefresh for soon-to-expire certificates", async () => {
133
+ // Clear in-memory cache first so it reloads from file
134
+ await clearCache();
135
+
136
+ // Create a cache entry that expires in 20 days
137
+ const twentyDaysFromNow = Date.now() + 20 * 24 * 60 * 60 * 1000;
138
+ const mockCache = {
139
+ version: 1,
140
+ certs: {
141
+ E8: {
142
+ pem: "-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----",
143
+ fetchedAt: Date.now(),
144
+ expiresAt: twentyDaysFromNow,
145
+ },
146
+ },
147
+ };
148
+
149
+ if (!existsSync(CERT_CACHE_DIR)) {
150
+ mkdirSync(CERT_CACHE_DIR, { recursive: true });
151
+ }
152
+ writeFileSync(CACHE_FILE, JSON.stringify(mockCache));
153
+
154
+ const status = await getCacheStatus();
155
+
156
+ assert.strictEqual(status.E8.needsRefresh, true);
157
+ });
158
+ });
159
+
160
+ describe("getIntermediateCerts", () => {
161
+ it("should return empty array for unknown certificate names", async () => {
162
+ const result = await getIntermediateCerts(["UNKNOWN_CERT"]);
163
+
164
+ assert.deepStrictEqual(result, []);
165
+ });
166
+
167
+ it("should return cached certificate if available and fresh", async () => {
168
+ // Clear in-memory cache first so it reloads from file
169
+ await clearCache();
170
+
171
+ // Create a fresh cache
172
+ const mockCache = {
173
+ version: 1,
174
+ certs: {
175
+ E8: {
176
+ pem: "-----BEGIN CERTIFICATE-----\nMOCK_E8_CERT\n-----END CERTIFICATE-----",
177
+ fetchedAt: Date.now(),
178
+ expiresAt: Date.now() + 365 * 24 * 60 * 60 * 1000,
179
+ },
180
+ },
181
+ };
182
+
183
+ if (!existsSync(CERT_CACHE_DIR)) {
184
+ mkdirSync(CERT_CACHE_DIR, { recursive: true });
185
+ }
186
+ writeFileSync(CACHE_FILE, JSON.stringify(mockCache));
187
+
188
+ const result = await getIntermediateCerts(["E8"]);
189
+
190
+ assert.ok(result.length === 1);
191
+ assert.ok(result[0].includes("MOCK_E8_CERT"));
192
+ });
193
+
194
+ it("should return multiple certificates when requested", async () => {
195
+ // Clear in-memory cache first so it reloads from file
196
+ await clearCache();
197
+
198
+ // Create cache with multiple certs
199
+ const mockCache = {
200
+ version: 1,
201
+ certs: {
202
+ E8: {
203
+ pem: "-----BEGIN CERTIFICATE-----\nMOCK_E8\n-----END CERTIFICATE-----",
204
+ fetchedAt: Date.now(),
205
+ expiresAt: Date.now() + 365 * 24 * 60 * 60 * 1000,
206
+ },
207
+ R3: {
208
+ pem: "-----BEGIN CERTIFICATE-----\nMOCK_R3\n-----END CERTIFICATE-----",
209
+ fetchedAt: Date.now(),
210
+ expiresAt: Date.now() + 365 * 24 * 60 * 60 * 1000,
211
+ },
212
+ },
213
+ };
214
+
215
+ if (!existsSync(CERT_CACHE_DIR)) {
216
+ mkdirSync(CERT_CACHE_DIR, { recursive: true });
217
+ }
218
+ writeFileSync(CACHE_FILE, JSON.stringify(mockCache));
219
+
220
+ const result = await getIntermediateCerts(["E8", "R3"]);
221
+
222
+ assert.strictEqual(result.length, 2);
223
+ assert.ok(result.some((c) => c.includes("MOCK_E8")));
224
+ assert.ok(result.some((c) => c.includes("MOCK_R3")));
225
+ });
226
+ });
227
+
228
+ describe("getAllIntermediateCerts (integration)", () => {
229
+ it("should fetch and cache certificates from Let's Encrypt", async () => {
230
+ // Clear cache first
231
+ await clearCache();
232
+
233
+ // This is an integration test - it actually fetches from the network
234
+ const certs = await getAllIntermediateCerts();
235
+
236
+ // Should have fetched at least some certificates
237
+ assert.ok(certs.length > 0, "Should have fetched certificates");
238
+ assert.ok(
239
+ certs.some((c) => c.includes("-----BEGIN CERTIFICATE-----")),
240
+ "Should contain PEM certificates",
241
+ );
242
+
243
+ // Check cache was populated
244
+ const status = await getCacheStatus();
245
+ const cachedCerts = Object.keys(status);
246
+ assert.ok(cachedCerts.length > 0, "Should have cached certificates");
247
+ });
248
+ });
249
+
250
+ describe("refreshAllCerts (integration)", () => {
251
+ it("should refresh all certificates", async () => {
252
+ // Clear cache first
253
+ await clearCache();
254
+
255
+ // Refresh all certs
256
+ await refreshAllCerts();
257
+
258
+ // Check cache was populated
259
+ const status = await getCacheStatus();
260
+ const cachedCerts = Object.keys(status);
261
+
262
+ // Should have cached multiple certificates
263
+ assert.ok(cachedCerts.length >= 5, `Expected at least 5 certs, got ${cachedCerts.length}`);
264
+
265
+ // All should have been recently fetched
266
+ const now = Date.now();
267
+ for (const [name, info] of Object.entries(status)) {
268
+ const fetchedAge = now - info.fetchedAt.getTime();
269
+ // Should have been fetched within the last minute
270
+ assert.ok(fetchedAge < 60000, `${name} should have been fetched recently, age: ${fetchedAge}ms`);
271
+ }
272
+ });
273
+ });
274
+
275
+ describe("cache persistence", () => {
276
+ it("should persist cache across calls", async () => {
277
+ // Clear in-memory cache first so it reloads from file
278
+ await clearCache();
279
+
280
+ // Create initial cache
281
+ const mockCache = {
282
+ version: 1,
283
+ certs: {
284
+ TEST: {
285
+ pem: "-----BEGIN CERTIFICATE-----\nTEST_PERSIST\n-----END CERTIFICATE-----",
286
+ fetchedAt: Date.now(),
287
+ expiresAt: Date.now() + 365 * 24 * 60 * 60 * 1000,
288
+ },
289
+ },
290
+ };
291
+
292
+ if (!existsSync(CERT_CACHE_DIR)) {
293
+ mkdirSync(CERT_CACHE_DIR, { recursive: true });
294
+ }
295
+ writeFileSync(CACHE_FILE, JSON.stringify(mockCache));
296
+
297
+ // Read status - this loads the cache
298
+ const status1 = await getCacheStatus();
299
+ assert.ok(status1.TEST);
300
+
301
+ // Read again - should still have the same data
302
+ const status2 = await getCacheStatus();
303
+ assert.ok(status2.TEST);
304
+ assert.strictEqual(status1.TEST.fetchedAt.getTime(), status2.TEST.fetchedAt.getTime());
305
+ });
306
+
307
+ it("should handle corrupted cache gracefully", async () => {
308
+ // Clear in-memory cache first so it reloads from file
309
+ await clearCache();
310
+
311
+ // Write corrupted cache
312
+ if (!existsSync(CERT_CACHE_DIR)) {
313
+ mkdirSync(CERT_CACHE_DIR, { recursive: true });
314
+ }
315
+ writeFileSync(CACHE_FILE, "not valid json {{{");
316
+
317
+ // Should not throw, should return empty
318
+ const status = await getCacheStatus();
319
+ assert.deepStrictEqual(status, {});
320
+ });
321
+ });
322
+ });
@@ -0,0 +1,346 @@
1
+ /**
2
+ * Certificate Manager
3
+ * Automatically fetches, caches, and refreshes intermediate CA certificates
4
+ * for servers with incomplete certificate chains.
5
+ *
6
+ * Uses native Node.js APIs - no external dependencies like curl or openssl CLI.
7
+ */
8
+
9
+ import fs from "node:fs/promises";
10
+ import path from "node:path";
11
+ import os from "node:os";
12
+ import crypto from "node:crypto";
13
+ import { Agent } from "undici";
14
+ import tls from "node:tls";
15
+
16
+ // Official Let's Encrypt certificate URLs (HTTPS - secure)
17
+ // Source: https://letsencrypt.org/certificates/
18
+ const INTERMEDIATE_CERT_URLS: Record<string, string> = {
19
+ // Let's Encrypt ECDSA intermediates (2024)
20
+ E5: "https://letsencrypt.org/certs/2024/e5.pem",
21
+ E6: "https://letsencrypt.org/certs/2024/e6.pem",
22
+ E7: "https://letsencrypt.org/certs/2024/e7.pem",
23
+ E8: "https://letsencrypt.org/certs/2024/e8.pem",
24
+ E9: "https://letsencrypt.org/certs/2024/e9.pem",
25
+ // Let's Encrypt RSA intermediates (2024)
26
+ R10: "https://letsencrypt.org/certs/2024/r10.pem",
27
+ R11: "https://letsencrypt.org/certs/2024/r11.pem",
28
+ // Legacy intermediates
29
+ R3: "https://letsencrypt.org/certs/2024/r3.pem",
30
+ R4: "https://letsencrypt.org/certs/2024/r4.pem",
31
+ };
32
+
33
+ // Cache directory for certificates
34
+ const CERT_CACHE_DIR = path.join(os.homedir(), ".cache", "memos-mcp", "certs");
35
+
36
+ // Refresh interval: 30 days in milliseconds
37
+ const REFRESH_INTERVAL_MS = 30 * 24 * 60 * 60 * 1000;
38
+
39
+ interface CertCacheEntry {
40
+ pem: string;
41
+ fetchedAt: number;
42
+ expiresAt: number;
43
+ }
44
+
45
+ interface CertCache {
46
+ version: number;
47
+ certs: Record<string, CertCacheEntry>;
48
+ }
49
+
50
+ // In-memory cache
51
+ let memoryCache: CertCache | null = null;
52
+
53
+ // Cached https agent
54
+ let cachedAgent: Agent | null = null;
55
+
56
+ /**
57
+ * Ensure cache directory exists
58
+ */
59
+ async function ensureCacheDir(): Promise<void> {
60
+ try {
61
+ await fs.mkdir(CERT_CACHE_DIR, { recursive: true });
62
+ } catch {
63
+ // Directory may already exist
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Get path to cache file
69
+ */
70
+ function getCacheFilePath(): string {
71
+ return path.join(CERT_CACHE_DIR, "cert-cache.json");
72
+ }
73
+
74
+ /**
75
+ * Load certificate cache from disk
76
+ */
77
+ async function loadCache(): Promise<CertCache> {
78
+ if (memoryCache) {
79
+ return memoryCache;
80
+ }
81
+
82
+ try {
83
+ const cacheFile = getCacheFilePath();
84
+ const data = await fs.readFile(cacheFile, "utf-8");
85
+ memoryCache = JSON.parse(data) as CertCache;
86
+ return memoryCache;
87
+ } catch {
88
+ // Cache corrupted or unreadable, start fresh
89
+ }
90
+ memoryCache = { version: 1, certs: {} };
91
+ return memoryCache;
92
+ }
93
+
94
+ /**
95
+ * Save certificate cache to disk
96
+ */
97
+ async function saveCache(cache: CertCache): Promise<void> {
98
+ memoryCache = cache;
99
+ try {
100
+ await ensureCacheDir();
101
+ const cacheFile = getCacheFilePath();
102
+ await fs.writeFile(cacheFile, JSON.stringify(cache, null, 2));
103
+ } catch {
104
+ // Ignore cache write errors - not critical
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Convert DER buffer to PEM string
110
+ */
111
+ function derToPem(der: Buffer): string {
112
+ const base64 = der.toString("base64");
113
+ const lines: string[] = [];
114
+ for (let i = 0; i < base64.length; i += 64) {
115
+ lines.push(base64.slice(i, i + 64));
116
+ }
117
+ return `-----BEGIN CERTIFICATE-----\n${lines.join("\n")}\n-----END CERTIFICATE-----`;
118
+ }
119
+
120
+ /**
121
+ * Parse certificate expiration date from PEM using Node.js crypto
122
+ */
123
+ function getCertExpirationDate(pem: string): Date | null {
124
+ try {
125
+ const cert = new crypto.X509Certificate(pem);
126
+ return new Date(cert.validTo);
127
+ } catch {
128
+ // Ignore parsing errors
129
+ }
130
+ return null;
131
+ }
132
+
133
+ /**
134
+ * Download certificate from URL (async) - returns DER buffer
135
+ */
136
+ async function downloadCertAsync(url: string): Promise<Buffer | null> {
137
+ try {
138
+ const response = await fetch(url, {
139
+ signal: AbortSignal.timeout(10000),
140
+ });
141
+
142
+ if (!response.ok) {
143
+ return null;
144
+ }
145
+
146
+ const arrayBuffer = await response.arrayBuffer();
147
+ return Buffer.from(arrayBuffer);
148
+ } catch {
149
+ return null;
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Download certificate and return as PEM (async)
155
+ * Handles both PEM (from HTTPS URLs) and DER (legacy AIA URLs) formats
156
+ */
157
+ async function downloadCert(url: string): Promise<string | null> {
158
+ const data = await downloadCertAsync(url);
159
+ if (!data || data.length === 0) {
160
+ return null;
161
+ }
162
+
163
+ try {
164
+ // Check if it's already PEM format (starts with "-----BEGIN")
165
+ const dataStr = data.toString("utf8");
166
+ if (dataStr.startsWith("-----BEGIN")) {
167
+ // Verify it's a valid certificate by parsing it
168
+ new crypto.X509Certificate(dataStr);
169
+ return dataStr.trim();
170
+ }
171
+
172
+ // Otherwise assume DER format and convert to PEM
173
+ const pem = derToPem(data);
174
+ // Verify it's a valid certificate by parsing it
175
+ new crypto.X509Certificate(pem);
176
+ return pem;
177
+ } catch {
178
+ return null;
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Check if a cached certificate needs refresh
184
+ */
185
+ function needsRefresh(entry: CertCacheEntry): boolean {
186
+ const now = Date.now();
187
+
188
+ // Refresh if:
189
+ // 1. Cache is older than REFRESH_INTERVAL_MS
190
+ // 2. Certificate expires within 30 days
191
+ const cacheAge = now - entry.fetchedAt;
192
+ const timeUntilExpiry = entry.expiresAt - now;
193
+ const thirtyDays = 30 * 24 * 60 * 60 * 1000;
194
+
195
+ return cacheAge > REFRESH_INTERVAL_MS || timeUntilExpiry < thirtyDays;
196
+ }
197
+
198
+ /**
199
+ * Get a certificate by name, fetching/refreshing as needed (async)
200
+ */
201
+ async function getCertificate(name: string): Promise<string | null> {
202
+ const url = INTERMEDIATE_CERT_URLS[name];
203
+ if (!url) {
204
+ return null;
205
+ }
206
+
207
+ const cache = await loadCache();
208
+ const cached = cache.certs[name];
209
+
210
+ // Return cached cert if valid and fresh
211
+ if (cached && !needsRefresh(cached)) {
212
+ return cached.pem;
213
+ }
214
+
215
+ // Try to fetch fresh certificate
216
+ const pem = await downloadCert(url);
217
+ if (pem) {
218
+ const expirationDate = getCertExpirationDate(pem);
219
+ cache.certs[name] = {
220
+ pem,
221
+ fetchedAt: Date.now(),
222
+ expiresAt: expirationDate?.getTime() ?? Date.now() + 365 * 24 * 60 * 60 * 1000,
223
+ };
224
+ await saveCache(cache);
225
+ return pem;
226
+ }
227
+
228
+ // Fall back to cached cert if fetch failed
229
+ if (cached) {
230
+ return cached.pem;
231
+ }
232
+
233
+ return null;
234
+ }
235
+
236
+ /**
237
+ * Get all intermediate certificates (fetches/refreshes as needed)
238
+ * Returns array of PEM strings
239
+ */
240
+ export async function getAllIntermediateCerts(): Promise<string[]> {
241
+ const certs: string[] = [];
242
+
243
+ const results = await Promise.all(Object.keys(INTERMEDIATE_CERT_URLS).map((name) => getCertificate(name)));
244
+
245
+ for (const cert of results) {
246
+ if (cert) {
247
+ certs.push(cert);
248
+ }
249
+ }
250
+
251
+ return certs;
252
+ }
253
+
254
+ /**
255
+ * Get specific intermediate certificates by name
256
+ */
257
+ export async function getIntermediateCerts(names: string[]): Promise<string[]> {
258
+ const results = await Promise.all(names.map((name) => getCertificate(name)));
259
+ return results.filter((cert): cert is string => cert !== null);
260
+ }
261
+
262
+ /**
263
+ * Force refresh all certificates
264
+ */
265
+ export async function refreshAllCerts(): Promise<void> {
266
+ // Clear memory cache to force fresh fetch
267
+ memoryCache = null;
268
+ cachedAgent = null;
269
+
270
+ const cache: CertCache = { version: 1, certs: {} };
271
+
272
+ const entries = Object.entries(INTERMEDIATE_CERT_URLS);
273
+ const results = await Promise.all(
274
+ entries.map(async ([name, url]) => {
275
+ const pem = await downloadCert(url);
276
+ return { name, pem };
277
+ }),
278
+ );
279
+
280
+ for (const { name, pem } of results) {
281
+ if (pem) {
282
+ const expirationDate = getCertExpirationDate(pem);
283
+ cache.certs[name] = {
284
+ pem,
285
+ fetchedAt: Date.now(),
286
+ expiresAt: expirationDate?.getTime() ?? Date.now() + 365 * 24 * 60 * 60 * 1000,
287
+ };
288
+ }
289
+ }
290
+
291
+ await saveCache(cache);
292
+ }
293
+
294
+ /**
295
+ * Clear the certificate cache
296
+ */
297
+ export async function clearCache(): Promise<void> {
298
+ memoryCache = null;
299
+ cachedAgent = null;
300
+ try {
301
+ const cacheFile = getCacheFilePath();
302
+ await fs.unlink(cacheFile);
303
+ } catch {
304
+ // Ignore errors - file may not exist
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Get cache status for debugging
310
+ */
311
+ export async function getCacheStatus(): Promise<
312
+ Record<string, { fetchedAt: Date; expiresAt: Date; needsRefresh: boolean }>
313
+ > {
314
+ const cache = await loadCache();
315
+ const status: Record<string, { fetchedAt: Date; expiresAt: Date; needsRefresh: boolean }> = {};
316
+
317
+ for (const [name, entry] of Object.entries(cache.certs)) {
318
+ status[name] = {
319
+ fetchedAt: new Date(entry.fetchedAt),
320
+ expiresAt: new Date(entry.expiresAt),
321
+ needsRefresh: needsRefresh(entry),
322
+ };
323
+ }
324
+
325
+ return status;
326
+ }
327
+
328
+ /**
329
+ * Get an HTTPS agent configured with intermediate certificates
330
+ * Creates agent lazily and caches it
331
+ */
332
+ export async function getHttpsAgent(): Promise<Agent> {
333
+ if (cachedAgent) {
334
+ return cachedAgent;
335
+ }
336
+
337
+ const intermediateCerts = await getAllIntermediateCerts();
338
+
339
+ cachedAgent = new Agent({
340
+ connect: {
341
+ ca: [...tls.rootCertificates, ...intermediateCerts],
342
+ },
343
+ });
344
+
345
+ return cachedAgent;
346
+ }