@devshub198211/devguard 2.0.1 → 2.0.3

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/dist/index.cjs DELETED
@@ -1,2655 +0,0 @@
1
- 'use strict';
2
-
3
- var fs = require('fs');
4
- var crypto6 = require('crypto');
5
- var path = require('path');
6
- var os = require('os');
7
- var https = require('https');
8
- var http = require('http');
9
-
10
- function _interopNamespace(e) {
11
- if (e && e.__esModule) return e;
12
- var n = Object.create(null);
13
- if (e) {
14
- Object.keys(e).forEach(function (k) {
15
- if (k !== 'default') {
16
- var d = Object.getOwnPropertyDescriptor(e, k);
17
- Object.defineProperty(n, k, d.get ? d : {
18
- enumerable: true,
19
- get: function () { return e[k]; }
20
- });
21
- }
22
- });
23
- }
24
- n.default = e;
25
- return Object.freeze(n);
26
- }
27
-
28
- var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
29
- var crypto6__namespace = /*#__PURE__*/_interopNamespace(crypto6);
30
- var path__namespace = /*#__PURE__*/_interopNamespace(path);
31
- var os__namespace = /*#__PURE__*/_interopNamespace(os);
32
- var https__namespace = /*#__PURE__*/_interopNamespace(https);
33
- var http__namespace = /*#__PURE__*/_interopNamespace(http);
34
-
35
- // src/security/lockfile-guardian.ts
36
- var LOCKFILES = ["package-lock.json", "yarn.lock", "pnpm-lock.yaml", "bun.lockb"];
37
- var MAX_LOCKFILE_SIZE = 100 * 1024 * 1024;
38
- function safeResolve(root, ...segments) {
39
- const resolvedRoot = path__namespace.resolve(root);
40
- const resolved = path__namespace.resolve(resolvedRoot, ...segments);
41
- if (!resolved.startsWith(resolvedRoot + path__namespace.sep) && resolved !== resolvedRoot) {
42
- throw new Error(`Path traversal detected: ${segments.join("/")} escapes root ${resolvedRoot}`);
43
- }
44
- return resolved;
45
- }
46
- function hashFile(filePath) {
47
- const stat = fs__namespace.statSync(filePath);
48
- if (stat.size > MAX_LOCKFILE_SIZE) {
49
- throw new Error(`File too large to hash (${stat.size} bytes, max ${MAX_LOCKFILE_SIZE}): ${filePath}`);
50
- }
51
- const buf = fs__namespace.readFileSync(filePath);
52
- return "sha512-" + crypto6__namespace.createHash("sha512").update(buf).digest("base64");
53
- }
54
- function verifyLockfile(root = process.cwd(), snapshotPath) {
55
- const resolvedRoot = path__namespace.resolve(root);
56
- const snap = snapshotPath ? path__namespace.resolve(snapshotPath) : path__namespace.join(resolvedRoot, ".lockguard-snapshot.json");
57
- let snapshot = null;
58
- if (fs__namespace.existsSync(snap)) {
59
- try {
60
- const raw = JSON.parse(fs__namespace.readFileSync(snap, "utf-8"));
61
- if (raw && typeof raw === "object" && raw.version === 2 && typeof raw.entries === "object" && raw.entries !== null) {
62
- snapshot = raw;
63
- }
64
- } catch {
65
- }
66
- }
67
- const tampered = [];
68
- const missing = [];
69
- const added = [];
70
- const entries = [];
71
- const known = new Set(Object.keys(snapshot?.entries ?? {}));
72
- for (const lf of LOCKFILES) {
73
- const full = safeResolve(resolvedRoot, lf);
74
- if (!fs__namespace.existsSync(full)) {
75
- if (known.has(lf)) missing.push(lf);
76
- continue;
77
- }
78
- try {
79
- const stat = fs__namespace.statSync(full);
80
- if (!stat.isFile()) continue;
81
- const hash = hashFile(full);
82
- entries.push({ file: lf, hash, size: stat.size, mtime: stat.mtimeMs });
83
- if (snapshot) {
84
- const prev = snapshot.entries[lf];
85
- if (!prev) {
86
- added.push(lf);
87
- } else if (prev.hash !== hash) {
88
- tampered.push(lf);
89
- }
90
- }
91
- } catch {
92
- }
93
- }
94
- return {
95
- valid: tampered.length === 0 && missing.length === 0,
96
- tampered,
97
- missing,
98
- added,
99
- entries,
100
- scannedAt: (/* @__PURE__ */ new Date()).toISOString()
101
- };
102
- }
103
- function createSnapshot(root = process.cwd(), snapshotPath) {
104
- const resolvedRoot = path__namespace.resolve(root);
105
- const snap = snapshotPath ? path__namespace.resolve(snapshotPath) : path__namespace.join(resolvedRoot, ".lockguard-snapshot.json");
106
- const entries = {};
107
- for (const lf of LOCKFILES) {
108
- const full = safeResolve(resolvedRoot, lf);
109
- if (!fs__namespace.existsSync(full)) continue;
110
- try {
111
- const stat = fs__namespace.statSync(full);
112
- if (!stat.isFile()) continue;
113
- entries[lf] = { hash: hashFile(full), size: stat.size };
114
- } catch {
115
- }
116
- }
117
- const snapshot = {
118
- version: 2,
119
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
120
- host: os__namespace.hostname(),
121
- entries
122
- };
123
- const tmpPath = snap + ".tmp." + crypto6__namespace.randomBytes(4).toString("hex");
124
- try {
125
- fs__namespace.writeFileSync(tmpPath, JSON.stringify(snapshot, null, 2));
126
- fs__namespace.renameSync(tmpPath, snap);
127
- } catch (e) {
128
- try {
129
- fs__namespace.unlinkSync(tmpPath);
130
- } catch {
131
- }
132
- throw e;
133
- }
134
- return snapshot;
135
- }
136
- function extractTransitiveDeps(root = process.cwd()) {
137
- const resolvedRoot = path__namespace.resolve(root);
138
- const pkgLock = path__namespace.join(resolvedRoot, "package-lock.json");
139
- if (!fs__namespace.existsSync(pkgLock)) return {};
140
- try {
141
- const lock = JSON.parse(fs__namespace.readFileSync(pkgLock, "utf-8"));
142
- const deps = {};
143
- const packages = lock.packages;
144
- if (typeof packages !== "object" || packages === null) return {};
145
- for (const [k, v] of Object.entries(packages)) {
146
- if (k && typeof v === "object" && v !== null && typeof v.version === "string") {
147
- const name = k.replace(/^node_modules\//, "");
148
- if (name === "__proto__" || name === "constructor" || name === "prototype") continue;
149
- deps[name] = v.version;
150
- }
151
- }
152
- return deps;
153
- } catch {
154
- return {};
155
- }
156
- }
157
- var MAX_PKG_JSON_SIZE = 2 * 1024 * 1024;
158
- var MAX_SCRIPT_LENGTH = 1e4;
159
- var MAX_SCAN_DEPTH = 5;
160
- var RULES = [
161
- { id: "R001", label: "Curl-to-shell pipe", severity: "critical", test: (s) => /curl\s+.+\|.*(ba)?sh/i.test(s) },
162
- { id: "R002", label: "Wget-to-shell pipe", severity: "critical", test: (s) => /wget\s+.+\|.*(ba)?sh/i.test(s) },
163
- { id: "R003", label: "Netcat reverse shell", severity: "critical", test: (s) => /\bnc(at)?\b.+-e\b|\bncat\b.+\bsh\b/i.test(s) },
164
- { id: "R004", label: "System credential access", severity: "critical", test: (s) => /\/etc\/(passwd|shadow|sudoers)/i.test(s) },
165
- { id: "R005", label: "SSH key exfiltration", severity: "critical", test: (s) => /\.ssh\/(id_rsa|authorized_keys|config)/i.test(s) },
166
- { id: "R006", label: "Crypto miner binary", severity: "critical", test: (s) => /\b(xmrig|minerd|cpuminer|ethminer|claymore)\b/i.test(s) },
167
- { id: "R007", label: "Stratum mining pool URL", severity: "critical", test: (s) => /stratum\+tcp:\/\//i.test(s) },
168
- { id: "R008", label: "eval() usage", severity: "high", test: (s) => /\beval\s*\(/.test(s) },
169
- { id: "R009", label: "Base64 decode + exec", severity: "high", test: (s) => /base64.*(decode|--decode|-d).*exec|exec.*base64.*(decode|--decode|-d)/i.test(s) },
170
- { id: "R010", label: "Dynamic require from URL", severity: "high", test: (s) => /require\s*\(\s*['"]https?:/i.test(s) },
171
- { id: "R011", label: "child_process exec in hook", severity: "high", test: (s) => /child_process.*\.(exec|spawn|execSync|spawnSync)/i.test(s) },
172
- { id: "R012", label: "Function constructor exec", severity: "high", test: (s) => /new\s+Function\s*\(/.test(s) },
173
- { id: "R013", label: "setTimeout with string arg", severity: "high", test: (s) => /setTimeout\s*\(\s*['"`]/.test(s) },
174
- { id: "R014", label: "Unauthorized npm publish", severity: "high", test: (s) => /npm\s+(publish|adduser|login)/.test(s) },
175
- { id: "R015", label: "HOME/USERPROFILE exfiltration", severity: "medium", test: (s) => /process\.env\.(HOME|USERPROFILE|APPDATA)/i.test(s) },
176
- { id: "R016", label: "PATH env manipulation", severity: "medium", test: (s) => /process\.env\.PATH\s*=/.test(s) },
177
- { id: "R017", label: "Outbound HTTP in hook", severity: "medium", test: (s) => /https?\.get|axios\.|fetch\s*\(|request\s*\(/i.test(s) },
178
- { id: "R018", label: "Filesystem write in hook", severity: "medium", test: (s) => /fs\.(writeFile|writeFileSync|appendFile|unlink|rmdir|rm\b)/i.test(s) },
179
- { id: "R019", label: "Registry credential file read", severity: "medium", test: (s) => /\.npmrc|\.yarnrc|\.gradle|\.m2\/settings/i.test(s) },
180
- { id: "R020", label: "Hex string obfuscation", severity: "high", test: (s) => /(\\x[0-9a-f]{2}){6,}/i.test(s) },
181
- { id: "R021", label: "Unicode escape obfuscation", severity: "high", test: (s) => /(\\u[0-9a-f]{4}){4,}/i.test(s) },
182
- { id: "R022", label: "String.fromCharCode array", severity: "high", test: (s) => /String\.fromCharCode\s*\(.{20,}\)/i.test(s) },
183
- { id: "R023", label: "Excessive base64 payload", severity: "high", test: (_s, d) => d !== void 0 && d.length > 200 }
184
- ];
185
- var HOOK_NAMES = ["preinstall", "install", "postinstall", "preuninstall", "postuninstall", "prepare", "prepublish", "prepack", "postpack"];
186
- function tryDecodeBase64(script) {
187
- const match = script.match(/[A-Za-z0-9+/]{60,}={0,2}/);
188
- if (!match) return void 0;
189
- try {
190
- return Buffer.from(match[0], "base64").toString("utf-8");
191
- } catch {
192
- return void 0;
193
- }
194
- }
195
- function safeTruncate(script) {
196
- return script.length > MAX_SCRIPT_LENGTH ? script.slice(0, MAX_SCRIPT_LENGTH) : script;
197
- }
198
- function scanPackage(pkgJsonPath) {
199
- if (!fs__namespace.existsSync(pkgJsonPath)) return [];
200
- try {
201
- const stat = fs__namespace.statSync(pkgJsonPath);
202
- if (!stat.isFile()) return [];
203
- if (stat.size > MAX_PKG_JSON_SIZE) return [];
204
- } catch {
205
- return [];
206
- }
207
- let pkg;
208
- try {
209
- pkg = JSON.parse(fs__namespace.readFileSync(pkgJsonPath, "utf-8"));
210
- } catch {
211
- return [];
212
- }
213
- if (typeof pkg !== "object" || pkg === null) return [];
214
- const scripts = pkg.scripts ?? {};
215
- if (typeof scripts !== "object" || scripts === null) return [];
216
- const findings = [];
217
- const pkgName = typeof pkg.name === "string" ? pkg.name : path__namespace.dirname(pkgJsonPath);
218
- const version = typeof pkg.version === "string" ? pkg.version : "unknown";
219
- for (const hook of HOOK_NAMES) {
220
- const script = scripts[hook];
221
- if (!script || typeof script !== "string") continue;
222
- const truncated = safeTruncate(script);
223
- const decoded = tryDecodeBase64(truncated);
224
- for (const rule of RULES) {
225
- if (rule.test(truncated, decoded) || decoded && rule.test(decoded, void 0)) {
226
- findings.push({
227
- package: pkgName,
228
- version,
229
- script: hook,
230
- ruleId: rule.id,
231
- pattern: rule.label,
232
- severity: rule.severity,
233
- raw: script.slice(0, 300),
234
- deobfuscated: decoded?.slice(0, 300)
235
- });
236
- }
237
- }
238
- }
239
- return findings;
240
- }
241
- function scanNodeModules(root = process.cwd()) {
242
- const resolvedRoot = path__namespace.resolve(root);
243
- const nmPath = path__namespace.join(resolvedRoot, "node_modules");
244
- if (!fs__namespace.existsSync(nmPath)) return [];
245
- if (!nmPath.startsWith(resolvedRoot + path__namespace.sep) && nmPath !== resolvedRoot) return [];
246
- const all = [];
247
- function scanDir(dir, depth) {
248
- if (depth > MAX_SCAN_DEPTH) return;
249
- let entries;
250
- try {
251
- entries = fs__namespace.readdirSync(dir, { withFileTypes: true });
252
- } catch {
253
- return;
254
- }
255
- for (const e of entries) {
256
- if (e.isSymbolicLink()) continue;
257
- if (!e.isDirectory()) continue;
258
- const full = path__namespace.join(dir, e.name);
259
- const resolvedFull = path__namespace.resolve(full);
260
- if (!resolvedFull.startsWith(nmPath)) continue;
261
- if (e.name.startsWith("@")) {
262
- scanDir(full, depth + 1);
263
- continue;
264
- }
265
- const pkgJson = path__namespace.join(full, "package.json");
266
- if (fs__namespace.existsSync(pkgJson)) all.push(...scanPackage(pkgJson));
267
- }
268
- }
269
- scanDir(nmPath, 0);
270
- return all;
271
- }
272
- function scanProject(root = process.cwd()) {
273
- const resolvedRoot = path__namespace.resolve(root);
274
- return [...scanPackage(path__namespace.join(resolvedRoot, "package.json")), ...scanNodeModules(resolvedRoot)];
275
- }
276
- var MAX_RESPONSE_SIZE = 1 * 1024 * 1024;
277
- var ALLOWED_HOSTS = /* @__PURE__ */ new Set(["api.github.com", "registry.npmjs.org"]);
278
- function validateHost(url) {
279
- const parsed = new URL(url);
280
- if (parsed.protocol !== "https:") {
281
- throw new Error("Only HTTPS URLs are allowed");
282
- }
283
- if (!ALLOWED_HOSTS.has(parsed.hostname)) {
284
- throw new Error(`Host not allowed: ${parsed.hostname}`);
285
- }
286
- return parsed;
287
- }
288
- function httpsGet(url, headers) {
289
- return new Promise((resolve7, reject) => {
290
- const u = validateHost(url);
291
- const req = https__namespace.request({ hostname: u.hostname, path: u.pathname + u.search, headers, method: "GET" }, (res) => {
292
- let body = "";
293
- let size = 0;
294
- res.on("data", (d) => {
295
- size += typeof d === "string" ? d.length : d.length;
296
- if (size > MAX_RESPONSE_SIZE) {
297
- req.destroy();
298
- reject(new Error("Response too large"));
299
- return;
300
- }
301
- body += d;
302
- });
303
- res.on("end", () => resolve7({ status: res.statusCode ?? 0, body }));
304
- });
305
- req.on("error", (e) => reject(new Error(`Network error: ${e.message}`)));
306
- req.setTimeout(8e3, () => {
307
- req.destroy();
308
- reject(new Error("Request timeout"));
309
- });
310
- req.end();
311
- });
312
- }
313
- function httpsPost(url, headers, payload) {
314
- return new Promise((resolve7, reject) => {
315
- const u = validateHost(url);
316
- const body = Buffer.from(payload);
317
- if (body.length > MAX_RESPONSE_SIZE) {
318
- reject(new Error("Payload too large"));
319
- return;
320
- }
321
- const req = https__namespace.request({
322
- hostname: u.hostname,
323
- path: u.pathname + u.search,
324
- method: "POST",
325
- headers: { ...headers, "Content-Length": String(body.length) }
326
- }, (res) => {
327
- let b = "";
328
- let size = 0;
329
- res.on("data", (d) => {
330
- size += typeof d === "string" ? d.length : d.length;
331
- if (size > MAX_RESPONSE_SIZE) {
332
- req.destroy();
333
- reject(new Error("Response too large"));
334
- return;
335
- }
336
- b += d;
337
- });
338
- res.on("end", () => resolve7({ status: res.statusCode ?? 0, body: b }));
339
- });
340
- req.on("error", (e) => reject(new Error(`Network error: ${e.message}`)));
341
- req.setTimeout(1e4, () => {
342
- req.destroy();
343
- reject(new Error("Request timeout"));
344
- });
345
- req.write(body);
346
- req.end();
347
- });
348
- }
349
- async function inspectGitHubToken(token, name = "GITHUB_TOKEN") {
350
- if (!token || typeof token !== "string" || token.length < 4) {
351
- return { name, provider: "github", status: "invalid", ageDays: null, maxAgeDays: 90, message: "Token is empty or too short" };
352
- }
353
- try {
354
- const res = await httpsGet("https://api.github.com/user", {
355
- "Authorization": `Bearer ${token}`,
356
- "User-Agent": "devguard/2.0",
357
- "Accept": "application/vnd.github+json",
358
- "X-GitHub-Api-Version": "2022-11-28"
359
- });
360
- if (res.status === 401) return { name, provider: "github", status: "invalid", ageDays: null, maxAgeDays: 90, message: "Token is invalid or revoked" };
361
- if (res.status !== 200) return { name, provider: "github", status: "unknown", ageDays: null, maxAgeDays: 90, message: `API returned ${res.status}` };
362
- return { name, provider: "github", status: "ok", ageDays: null, maxAgeDays: 90, message: "Token valid \u2014 use fine-grained PATs with expiry for age tracking" };
363
- } catch (e) {
364
- const msg = e instanceof Error ? e.message : "Unknown error";
365
- return { name, provider: "github", status: "unknown", ageDays: null, maxAgeDays: 90, message: `Check failed: ${msg}` };
366
- }
367
- }
368
- async function inspectNpmToken(token, name = "NPM_TOKEN") {
369
- if (!token || typeof token !== "string" || token.length < 4) {
370
- return { name, provider: "npm", status: "invalid", ageDays: null, maxAgeDays: 90, message: "Token is empty or too short" };
371
- }
372
- try {
373
- const res = await httpsGet("https://registry.npmjs.org/-/npm/v1/tokens", { "Authorization": `Bearer ${token}`, "Accept": "application/json" });
374
- if (res.status === 401) return { name, provider: "npm", status: "invalid", ageDays: null, maxAgeDays: 90, message: "Token invalid or revoked" };
375
- if (res.status === 200) {
376
- let data;
377
- try {
378
- data = JSON.parse(res.body);
379
- } catch {
380
- return { name, provider: "npm", status: "unknown", ageDays: null, maxAgeDays: 90, message: "Invalid JSON response from registry" };
381
- }
382
- const tokens = Array.isArray(data.objects) ? data.objects : [];
383
- const prefix = token.slice(0, 6);
384
- const found = tokens.find((t) => t.key?.startsWith(prefix) || t.token?.startsWith(prefix));
385
- const extra = [found?.readonly ? "read-only" : "", found?.cidr_whitelist?.length ? "CIDR-restricted" : ""].filter(Boolean).join(", ");
386
- return { name, provider: "npm", status: "ok", ageDays: null, maxAgeDays: 90, message: `Valid npm token${extra ? " (" + extra + ")" : ""}` };
387
- }
388
- return { name, provider: "npm", status: "unknown", ageDays: null, maxAgeDays: 90, message: `Registry returned ${res.status}` };
389
- } catch (e) {
390
- const msg = e instanceof Error ? e.message : "Unknown error";
391
- return { name, provider: "npm", status: "unknown", ageDays: null, maxAgeDays: 90, message: `Check failed: ${msg}` };
392
- }
393
- }
394
- async function createNpmToken(existingToken, opts) {
395
- if (!existingToken || typeof existingToken !== "string") {
396
- return { success: false, error: "Token is required" };
397
- }
398
- try {
399
- const payload = JSON.stringify({ password: existingToken, readonly: opts?.readonly ?? false, cidr_whitelist: opts?.cidr ?? [] });
400
- const res = await httpsPost("https://registry.npmjs.org/-/npm/v1/tokens", { "Authorization": `Bearer ${existingToken}`, "Content-Type": "application/json" }, payload);
401
- if (res.status === 200 || res.status === 201) {
402
- let parsed;
403
- try {
404
- parsed = JSON.parse(res.body);
405
- } catch {
406
- return { success: false, error: "Invalid JSON in npm response" };
407
- }
408
- if (typeof parsed.token !== "string") return { success: false, error: "No token in response" };
409
- return { success: true, newToken: parsed.token };
410
- }
411
- return { success: false, error: `npm registry returned ${res.status}` };
412
- } catch (e) {
413
- const msg = e instanceof Error ? e.message : "Unknown error";
414
- return { success: false, error: msg };
415
- }
416
- }
417
- function checkTokenAge(configs) {
418
- return configs.map((cfg) => {
419
- const maxAgeDays = cfg.maxAgeDays ?? 90;
420
- if (!cfg.createdAt) return { name: cfg.name, provider: cfg.provider, status: "unknown", ageDays: null, maxAgeDays, message: "Creation date unknown \u2014 set createdAt to enable age tracking" };
421
- if (typeof cfg.createdAt !== "number" || cfg.createdAt < 0) return { name: cfg.name, provider: cfg.provider, status: "unknown", ageDays: null, maxAgeDays, message: "Invalid createdAt timestamp" };
422
- const ageDays = Math.floor((Date.now() - cfg.createdAt) / 864e5);
423
- if (ageDays < 0) return { name: cfg.name, provider: cfg.provider, status: "unknown", ageDays: null, maxAgeDays, message: "Token creation date is in the future" };
424
- const status = ageDays >= maxAgeDays ? "stale" : ageDays >= maxAgeDays * 0.8 ? "expiring_soon" : "ok";
425
- return { name: cfg.name, provider: cfg.provider, status, ageDays, maxAgeDays, message: `${ageDays}/${maxAgeDays} days old` };
426
- });
427
- }
428
- function maskToken(token) {
429
- if (!token || typeof token !== "string") return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
430
- if (token.length <= 8) return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
431
- return token.slice(0, 4) + "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" + token.slice(-4);
432
- }
433
- function loadTokensFromEnv(names, maxAgeDays = 90) {
434
- if (!Array.isArray(names)) return [];
435
- return names.filter((n) => typeof n === "string" && n.length > 0 && process.env[n]).map((n) => {
436
- const value = process.env[n];
437
- const provider = /github|gh_/i.test(n) ? "github" : /npm/i.test(n) ? "npm" : "generic";
438
- return { name: n, value, provider, maxAgeDays };
439
- });
440
- }
441
- var RANGE_CHARS = /^[\^~*]|^latest$|^next$|^>=|^>/;
442
- var DEP_FIELDS = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
443
- var MAX_PKG_JSON_SIZE2 = 5 * 1024 * 1024;
444
- var MAX_RESPONSE_SIZE2 = 2 * 1024 * 1024;
445
- var MAX_REDIRECTS = 3;
446
- function enforceExactPins(pkgJsonPath) {
447
- const resolved = path__namespace.resolve(pkgJsonPath);
448
- if (!fs__namespace.existsSync(resolved)) return [];
449
- try {
450
- const stat = fs__namespace.statSync(resolved);
451
- if (!stat.isFile() || stat.size > MAX_PKG_JSON_SIZE2) return [];
452
- } catch {
453
- return [];
454
- }
455
- let pkg;
456
- try {
457
- pkg = JSON.parse(fs__namespace.readFileSync(resolved, "utf-8"));
458
- } catch {
459
- return [];
460
- }
461
- if (typeof pkg !== "object" || pkg === null) return [];
462
- const violations = [];
463
- for (const field of DEP_FIELDS) {
464
- const deps = pkg[field];
465
- if (!deps || typeof deps !== "object") continue;
466
- for (const [name, ver] of Object.entries(deps)) {
467
- if (typeof ver !== "string") continue;
468
- if (RANGE_CHARS.test(ver.trim())) {
469
- const suggestion = resolveExactVersion(path__namespace.dirname(resolved), name) ?? ver.replace(/^[\^~]/, "");
470
- violations.push({ name, specifier: ver, field, suggestion });
471
- }
472
- }
473
- }
474
- return violations;
475
- }
476
- function resolveExactVersion(root, pkgName) {
477
- const lockPath = path__namespace.join(root, "package-lock.json");
478
- if (!fs__namespace.existsSync(lockPath)) return null;
479
- try {
480
- const stat = fs__namespace.statSync(lockPath);
481
- if (stat.size > MAX_PKG_JSON_SIZE2 * 10) return null;
482
- const lock = JSON.parse(fs__namespace.readFileSync(lockPath, "utf-8"));
483
- const packages = lock.packages;
484
- if (typeof packages !== "object" || packages === null) return null;
485
- const entry = packages["node_modules/" + pkgName];
486
- return typeof entry?.version === "string" ? entry.version : null;
487
- } catch {
488
- return null;
489
- }
490
- }
491
- function extractLockfileSRI(root = process.cwd()) {
492
- const resolvedRoot = path__namespace.resolve(root);
493
- const lockPath = path__namespace.join(resolvedRoot, "package-lock.json");
494
- if (!fs__namespace.existsSync(lockPath)) return {};
495
- try {
496
- const lock = JSON.parse(fs__namespace.readFileSync(lockPath, "utf-8"));
497
- const packages = lock.packages;
498
- if (typeof packages !== "object" || packages === null) return {};
499
- const result = {};
500
- for (const [key, val] of Object.entries(packages)) {
501
- if (key && typeof val === "object" && val !== null && typeof val.version === "string" && typeof val.integrity === "string") {
502
- const name = key.replace(/^node_modules\//, "");
503
- if (name === "__proto__" || name === "constructor" || name === "prototype") continue;
504
- result[name] = { version: val.version, integrity: val.integrity };
505
- }
506
- }
507
- return result;
508
- } catch {
509
- return {};
510
- }
511
- }
512
- function httpsGet2(url, redirectCount = 0) {
513
- return new Promise((resolve7, reject) => {
514
- if (redirectCount > MAX_REDIRECTS) {
515
- reject(new Error("Too many redirects"));
516
- return;
517
- }
518
- let parsed;
519
- try {
520
- parsed = new URL(url);
521
- } catch {
522
- reject(new Error("Invalid URL"));
523
- return;
524
- }
525
- if (parsed.protocol !== "https:") {
526
- reject(new Error("Only HTTPS URLs are allowed"));
527
- return;
528
- }
529
- if (parsed.hostname !== "registry.npmjs.org") {
530
- reject(new Error(`Host not allowed: ${parsed.hostname}`));
531
- return;
532
- }
533
- const req = https__namespace.get(url, { headers: { "Accept": "application/json", "User-Agent": "devguard/2.0" } }, (res) => {
534
- if ((res.statusCode === 301 || res.statusCode === 302) && res.headers.location) {
535
- try {
536
- const redirectUrl = new URL(res.headers.location);
537
- if (redirectUrl.protocol !== "https:" || redirectUrl.hostname !== "registry.npmjs.org") {
538
- reject(new Error("Redirect to disallowed host"));
539
- return;
540
- }
541
- } catch {
542
- reject(new Error("Invalid redirect URL"));
543
- return;
544
- }
545
- httpsGet2(res.headers.location, redirectCount + 1).then(resolve7).catch(reject);
546
- return;
547
- }
548
- let body = "";
549
- let size = 0;
550
- res.on("data", (d) => {
551
- size += typeof d === "string" ? d.length : d.length;
552
- if (size > MAX_RESPONSE_SIZE2) {
553
- req.destroy();
554
- reject(new Error("Response too large"));
555
- return;
556
- }
557
- body += d;
558
- });
559
- res.on("end", () => resolve7(body));
560
- });
561
- req.on("error", (e) => reject(new Error(`Network error: ${e.message}`)));
562
- req.setTimeout(8e3, () => {
563
- req.destroy();
564
- reject(new Error("Request timeout"));
565
- });
566
- });
567
- }
568
- async function fetchRegistrySRI(name, version) {
569
- if (!name || !version || typeof name !== "string" || typeof version !== "string") return null;
570
- try {
571
- const encoded = encodeURIComponent(name).replace(/%40/g, "@").replace(/%2F/gi, "/");
572
- const encodedVersion = encodeURIComponent(version);
573
- const body = await httpsGet2(`https://registry.npmjs.org/${encoded}/${encodedVersion}`);
574
- const parsed = JSON.parse(body);
575
- const integrity = parsed?.dist?.integrity;
576
- return typeof integrity === "string" ? integrity : null;
577
- } catch {
578
- return null;
579
- }
580
- }
581
- async function verifySRI(root = process.cwd(), sampleSize = 20) {
582
- const lockEntries = extractLockfileSRI(root);
583
- const names = Object.keys(lockEntries).sort();
584
- const toCheck = sampleSize < names.length ? names.slice(0, sampleSize) : names;
585
- return Promise.all(toCheck.map(async (name) => {
586
- const { version, integrity } = lockEntries[name];
587
- const registryHash = await fetchRegistrySRI(name, version);
588
- return { name, version, lockfileHash: integrity, registryHash, match: registryHash === null ? null : registryHash === integrity };
589
- }));
590
- }
591
- function autoPin(pkgJsonPath, dryRun = false) {
592
- const resolved = path__namespace.resolve(pkgJsonPath);
593
- let rawContent;
594
- try {
595
- rawContent = fs__namespace.readFileSync(resolved, "utf-8");
596
- } catch {
597
- return { fixed: 0 };
598
- }
599
- let pkg;
600
- try {
601
- pkg = JSON.parse(rawContent);
602
- } catch {
603
- return { fixed: 0 };
604
- }
605
- if (typeof pkg !== "object" || pkg === null) return { fixed: 0 };
606
- const root = path__namespace.dirname(resolved);
607
- let fixed = 0;
608
- for (const field of DEP_FIELDS) {
609
- const deps = pkg[field];
610
- if (!deps || typeof deps !== "object") continue;
611
- for (const [name, ver] of Object.entries(deps)) {
612
- if (typeof ver !== "string") continue;
613
- if (RANGE_CHARS.test(ver.trim())) {
614
- const exact = resolveExactVersion(root, name);
615
- if (exact) {
616
- deps[name] = exact;
617
- fixed++;
618
- }
619
- }
620
- }
621
- }
622
- const content = JSON.stringify(pkg, null, 2) + "\n";
623
- if (!dryRun && fixed > 0) {
624
- const tmpPath = resolved + ".tmp." + crypto6__namespace.randomBytes(4).toString("hex");
625
- try {
626
- fs__namespace.writeFileSync(tmpPath, content);
627
- fs__namespace.renameSync(tmpPath, resolved);
628
- } catch (e) {
629
- try {
630
- fs__namespace.unlinkSync(tmpPath);
631
- } catch {
632
- }
633
- throw e;
634
- }
635
- }
636
- return { fixed, content };
637
- }
638
-
639
- // src/dx/api-contract.ts
640
- var Schema = class {
641
- constructor() {
642
- /** @internal */
643
- this._isOptional = false;
644
- }
645
- safeParse(val) {
646
- try {
647
- let result = this._parse(val === void 0 ? this._default : val, "");
648
- if (this._transform) result = this._transform(result);
649
- return { success: true, data: result, errors: [] };
650
- } catch (e) {
651
- return { success: false, errors: parseErrors(e) };
652
- }
653
- }
654
- parse(val) {
655
- const r = this.safeParse(val);
656
- if (!r.success) throw new Error(r.errors.map((e) => (e.path ? `${e.path}: ` : "") + e.message).join("; "));
657
- return r.data;
658
- }
659
- optional() {
660
- return new OptionalSchema(this);
661
- }
662
- nullable() {
663
- return new NullableSchema(this);
664
- }
665
- default(val) {
666
- const c2 = Object.create(this);
667
- c2._default = val;
668
- return c2;
669
- }
670
- describe(desc) {
671
- const c2 = Object.create(this);
672
- c2._description = desc;
673
- return c2;
674
- }
675
- transform(fn) {
676
- const c2 = Object.create(this);
677
- c2._transform = fn;
678
- return c2;
679
- }
680
- toJSONSchema() {
681
- return {};
682
- }
683
- };
684
- function parseErrors(e) {
685
- if (e instanceof ValidationException) return e.errors;
686
- return [{ path: "", message: e?.message ?? "Unknown error" }];
687
- }
688
- var ValidationException = class extends Error {
689
- constructor(errors) {
690
- super(errors.map((e) => e.message).join("; "));
691
- this.errors = errors;
692
- }
693
- };
694
- function fail(path7, message) {
695
- throw new ValidationException([{ path: path7, message }]);
696
- }
697
- var OptionalSchema = class extends Schema {
698
- constructor(inner) {
699
- super();
700
- this.inner = inner;
701
- }
702
- _parse(val, path7) {
703
- if (val === void 0 || val === null) return void 0;
704
- return this.inner._parse(val, path7);
705
- }
706
- };
707
- var NullableSchema = class extends Schema {
708
- constructor(inner) {
709
- super();
710
- this.inner = inner;
711
- }
712
- _parse(val, path7) {
713
- if (val === null || val === void 0) return null;
714
- return this.inner._parse(val, path7);
715
- }
716
- };
717
- var StringSchema = class extends Schema {
718
- constructor() {
719
- super(...arguments);
720
- this._email = false;
721
- this._url = false;
722
- this._trim = false;
723
- }
724
- _parse(val, path7) {
725
- if (typeof val !== "string") fail(path7, `Expected string, got ${typeof val}`);
726
- let s = val;
727
- if (this._trim) s = s.trim();
728
- if (this._min !== void 0 && s.length < this._min) fail(path7, `Too short (min ${this._min})`);
729
- if (this._max !== void 0 && s.length > this._max) fail(path7, `Too long (max ${this._max})`);
730
- if (this._enum && !this._enum.includes(s)) fail(path7, `Must be one of: ${this._enum.join(", ")}`);
731
- if (this._regex && !this._regex.test(s)) fail(path7, `Does not match ${this._regex}`);
732
- if (this._email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s)) fail(path7, "Invalid email address");
733
- if (this._url) {
734
- try {
735
- new URL(s);
736
- } catch {
737
- fail(path7, "Invalid URL");
738
- }
739
- }
740
- return s;
741
- }
742
- min(n) {
743
- const c2 = Object.create(this);
744
- c2._min = n;
745
- return c2;
746
- }
747
- max(n) {
748
- const c2 = Object.create(this);
749
- c2._max = n;
750
- return c2;
751
- }
752
- length(n) {
753
- return this.min(n).max(n);
754
- }
755
- enum(vals) {
756
- const c2 = Object.create(this);
757
- c2._enum = vals;
758
- return c2;
759
- }
760
- regex(r) {
761
- const c2 = Object.create(this);
762
- c2._regex = r;
763
- return c2;
764
- }
765
- email() {
766
- const c2 = Object.create(this);
767
- c2._email = true;
768
- return c2;
769
- }
770
- url() {
771
- const c2 = Object.create(this);
772
- c2._url = true;
773
- return c2;
774
- }
775
- trim() {
776
- const c2 = Object.create(this);
777
- c2._trim = true;
778
- return c2;
779
- }
780
- toJSONSchema() {
781
- return { type: "string", ...this._min ? { minLength: this._min } : {}, ...this._max ? { maxLength: this._max } : {}, ...this._enum ? { enum: this._enum } : {}, ...this._email ? { format: "email" } : {}, ...this._url ? { format: "uri" } : {} };
782
- }
783
- };
784
- var NumberSchema = class extends Schema {
785
- constructor() {
786
- super(...arguments);
787
- this._int = false;
788
- this._positive = false;
789
- }
790
- _parse(val, path7) {
791
- const n = typeof val === "string" ? parseFloat(val) : val;
792
- if (typeof n !== "number" || isNaN(n)) fail(path7, `Expected number, got ${typeof val}`);
793
- if (this._int && !Number.isInteger(n)) fail(path7, "Expected integer");
794
- if (this._positive && n <= 0) fail(path7, "Must be positive");
795
- if (this._min !== void 0 && n < this._min) fail(path7, `Too small (min ${this._min})`);
796
- if (this._max !== void 0 && n > this._max) fail(path7, `Too large (max ${this._max})`);
797
- return n;
798
- }
799
- min(n) {
800
- const c2 = Object.create(this);
801
- c2._min = n;
802
- return c2;
803
- }
804
- max(n) {
805
- const c2 = Object.create(this);
806
- c2._max = n;
807
- return c2;
808
- }
809
- int() {
810
- const c2 = Object.create(this);
811
- c2._int = true;
812
- return c2;
813
- }
814
- positive() {
815
- const c2 = Object.create(this);
816
- c2._positive = true;
817
- return c2;
818
- }
819
- toJSONSchema() {
820
- return { type: this._int ? "integer" : "number", ...this._min !== void 0 ? { minimum: this._min } : {}, ...this._max !== void 0 ? { maximum: this._max } : {} };
821
- }
822
- };
823
- var BooleanSchema = class extends Schema {
824
- _parse(val, path7) {
825
- if (val === "true" || val === 1 || val === "1") return true;
826
- if (val === "false" || val === 0 || val === "0") return false;
827
- if (typeof val !== "boolean") fail(path7, `Expected boolean, got ${typeof val}`);
828
- return val;
829
- }
830
- toJSONSchema() {
831
- return { type: "boolean" };
832
- }
833
- };
834
- var ObjectSchema = class _ObjectSchema extends Schema {
835
- constructor(shape, isStrict = false) {
836
- super();
837
- this.shape = shape;
838
- this._isStrict = isStrict;
839
- }
840
- _parse(val, path7) {
841
- if (typeof val !== "object" || val === null || Array.isArray(val)) fail(path7, `Expected object`);
842
- const obj = val;
843
- const result = {};
844
- const errors = [];
845
- for (const [key, schema] of Object.entries(this.shape)) {
846
- try {
847
- result[key] = schema._parse(obj[key], path7 ? `${path7}.${key}` : key);
848
- } catch (e) {
849
- errors.push(...parseErrors(e));
850
- }
851
- }
852
- if (this._isStrict) {
853
- const knownKeys = new Set(Object.keys(this.shape));
854
- for (const k of Object.keys(obj)) if (!knownKeys.has(k)) errors.push({ path: path7 ? `${path7}.${k}` : k, message: "Unknown field" });
855
- }
856
- if (errors.length) throw new ValidationException(errors);
857
- return result;
858
- }
859
- extend(extra) {
860
- return new _ObjectSchema({ ...this.shape, ...extra });
861
- }
862
- pick(keys) {
863
- const s = {};
864
- keys.forEach((k) => s[k] = this.shape[k]);
865
- return new _ObjectSchema(s);
866
- }
867
- omit(keys) {
868
- const s = { ...this.shape };
869
- keys.forEach((k) => delete s[k]);
870
- return new _ObjectSchema(s);
871
- }
872
- strict() {
873
- return new _ObjectSchema(this.shape, true);
874
- }
875
- toJSONSchema() {
876
- const props = {};
877
- const required = [];
878
- for (const [k, v] of Object.entries(this.shape)) {
879
- props[k] = v.toJSONSchema?.() ?? {};
880
- if (!(v instanceof OptionalSchema) && !(v instanceof NullableSchema)) required.push(k);
881
- }
882
- return { type: "object", properties: props, ...required.length ? { required } : {} };
883
- }
884
- };
885
- var ArraySchema = class extends Schema {
886
- constructor(item) {
887
- super();
888
- this.item = item;
889
- }
890
- _parse(val, path7) {
891
- if (!Array.isArray(val)) fail(path7, `Expected array`);
892
- if (this._min !== void 0 && val.length < this._min) fail(path7, `Too few items (min ${this._min})`);
893
- if (this._max !== void 0 && val.length > this._max) fail(path7, `Too many items (max ${this._max})`);
894
- const errors = [];
895
- const result = [];
896
- val.forEach((item, i) => {
897
- try {
898
- result.push(this.item._parse(item, `${path7}[${i}]`));
899
- } catch (e) {
900
- errors.push(...parseErrors(e));
901
- }
902
- });
903
- if (errors.length) throw new ValidationException(errors);
904
- return result;
905
- }
906
- min(n) {
907
- const c2 = Object.create(this);
908
- c2._min = n;
909
- return c2;
910
- }
911
- max(n) {
912
- const c2 = Object.create(this);
913
- c2._max = n;
914
- return c2;
915
- }
916
- nonempty() {
917
- return this.min(1);
918
- }
919
- toJSONSchema() {
920
- return { type: "array", items: this.item.toJSONSchema?.() ?? {} };
921
- }
922
- };
923
- var RecordSchema = class extends Schema {
924
- constructor(valueSchema) {
925
- super();
926
- this.valueSchema = valueSchema;
927
- }
928
- _parse(val, path7) {
929
- if (typeof val !== "object" || val === null || Array.isArray(val)) fail(path7, "Expected object");
930
- const result = {};
931
- for (const [k, v] of Object.entries(val)) {
932
- if (k === "__proto__" || k === "constructor" || k === "prototype") continue;
933
- result[k] = this.valueSchema._parse(v, `${path7}.${k}`);
934
- }
935
- return result;
936
- }
937
- };
938
- var LiteralSchema = class extends Schema {
939
- constructor(literal) {
940
- super();
941
- this.literal = literal;
942
- }
943
- _parse(val, path7) {
944
- if (val !== this.literal) fail(path7, `Expected ${JSON.stringify(this.literal)}, got ${JSON.stringify(val)}`);
945
- return val;
946
- }
947
- };
948
- var UnionSchema = class extends Schema {
949
- constructor(options) {
950
- super();
951
- this.options = options;
952
- }
953
- _parse(val, path7) {
954
- for (const opt of this.options) {
955
- try {
956
- return opt._parse(val, path7);
957
- } catch {
958
- continue;
959
- }
960
- }
961
- fail(path7, `Value does not match any union variant`);
962
- }
963
- };
964
- var c = {
965
- string: () => new StringSchema(),
966
- number: () => new NumberSchema(),
967
- boolean: () => new BooleanSchema(),
968
- object: (shape) => new ObjectSchema(shape),
969
- array: (item) => new ArraySchema(item),
970
- record: (val) => new RecordSchema(val),
971
- literal: (v) => new LiteralSchema(v),
972
- union: (opts) => new UnionSchema(opts),
973
- any: () => new class extends Schema {
974
- _parse(v) {
975
- return v;
976
- }
977
- }()
978
- };
979
- function validateBody(schema) {
980
- return function(req, res, next) {
981
- const result = schema.safeParse(req.body);
982
- if (!result.success) return res.status(400).json({ error: "Validation failed", details: result.errors });
983
- req.body = result.data;
984
- next();
985
- };
986
- }
987
- function validateQuery(schema) {
988
- return function(req, res, next) {
989
- const result = schema.safeParse(req.query);
990
- if (!result.success) return res.status(400).json({ error: "Invalid query parameters", details: result.errors });
991
- req.query = result.data;
992
- next();
993
- };
994
- }
995
- async function typedFetch(url, schema, init) {
996
- if (typeof fetch === "undefined") throw new Error("[devguard] typedFetch requires Node.js >= 18 or a global fetch polyfill. See: https://nodejs.org/en/blog/announcements/v18-release-announce");
997
- const response = await fetch(url, init);
998
- if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
999
- const json = await response.json();
1000
- const data = schema.parse(json);
1001
- return { data, response };
1002
- }
1003
-
1004
- // src/ai/agent-schema.ts
1005
- function cleanLLMOutput(raw) {
1006
- let s = raw.trim();
1007
- s = s.replace(/^```(?:json|JSON)?\s*/m, "").replace(/```\s*$/m, "").trim();
1008
- const openBrace = s.indexOf("{");
1009
- const openBracket = s.indexOf("[");
1010
- let startIdx = -1;
1011
- let openChar = "", closeChar = "";
1012
- if (openBrace === -1 && openBracket === -1) return s;
1013
- if (openBrace === -1) {
1014
- startIdx = openBracket;
1015
- openChar = "[";
1016
- closeChar = "]";
1017
- } else if (openBracket === -1) {
1018
- startIdx = openBrace;
1019
- openChar = "{";
1020
- closeChar = "}";
1021
- } else if (openBrace < openBracket) {
1022
- startIdx = openBrace;
1023
- openChar = "{";
1024
- closeChar = "}";
1025
- } else {
1026
- startIdx = openBracket;
1027
- openChar = "[";
1028
- closeChar = "]";
1029
- }
1030
- let depth = 0, inString = false, escape = false, end = -1;
1031
- for (let i = startIdx; i < s.length; i++) {
1032
- const ch = s[i];
1033
- if (escape) {
1034
- escape = false;
1035
- continue;
1036
- }
1037
- if (ch === "\\" && inString) {
1038
- escape = true;
1039
- continue;
1040
- }
1041
- if (ch === '"') {
1042
- inString = !inString;
1043
- continue;
1044
- }
1045
- if (inString) continue;
1046
- if (ch === openChar) depth++;
1047
- else if (ch === closeChar) {
1048
- depth--;
1049
- if (depth === 0) {
1050
- end = i;
1051
- break;
1052
- }
1053
- }
1054
- }
1055
- return end !== -1 ? s.slice(startIdx, end + 1) : s;
1056
- }
1057
- function repairJSON(raw) {
1058
- let result = "";
1059
- let inString = false;
1060
- let escape = false;
1061
- for (let i = 0; i < raw.length; i++) {
1062
- const ch = raw[i];
1063
- if (escape) {
1064
- result += ch;
1065
- escape = false;
1066
- continue;
1067
- }
1068
- if (ch === "\\" && inString) {
1069
- result += ch;
1070
- escape = true;
1071
- continue;
1072
- }
1073
- if (ch === '"') {
1074
- inString = !inString;
1075
- result += ch;
1076
- continue;
1077
- }
1078
- if (inString) {
1079
- result += ch;
1080
- continue;
1081
- }
1082
- if (ch === ",") {
1083
- let j = i + 1;
1084
- while (j < raw.length && /\s/.test(raw[j])) j++;
1085
- if (j < raw.length && (raw[j] === "}" || raw[j] === "]")) {
1086
- continue;
1087
- }
1088
- }
1089
- result += ch;
1090
- }
1091
- return result;
1092
- }
1093
- function parseSchema(schema, raw) {
1094
- const cleaned = cleanLLMOutput(raw);
1095
- try {
1096
- const parsed = JSON.parse(cleaned);
1097
- const r = schema.safeParse(parsed);
1098
- if (r.success) return { success: true, data: r.data, raw: cleaned, errors: [], attempts: 1 };
1099
- return { success: false, raw: cleaned, errors: r.errors.map((e) => (e.path ? e.path + ": " : "") + e.message), attempts: 1 };
1100
- } catch {
1101
- const repaired = repairJSON(cleaned);
1102
- if (repaired !== cleaned) {
1103
- try {
1104
- const parsed = JSON.parse(repaired);
1105
- const r = schema.safeParse(parsed);
1106
- if (r.success) return { success: true, data: r.data, raw: repaired, errors: [], attempts: 2 };
1107
- return { success: false, raw: repaired, errors: r.errors.map((e) => (e.path ? e.path + ": " : "") + e.message), attempts: 2 };
1108
- } catch {
1109
- }
1110
- }
1111
- return { success: false, errors: ["Could not parse LLM output as JSON"], attempts: 2 };
1112
- }
1113
- }
1114
- async function parseWithRetry(schema, promptFn, maxRetries = 3) {
1115
- let context = "Respond with valid JSON only. No markdown fences, no explanation, just the JSON.";
1116
- let totalAttempts = 0;
1117
- for (let i = 0; i <= maxRetries; i++) {
1118
- const raw = await promptFn(context);
1119
- totalAttempts++;
1120
- const result = parseSchema(schema, raw);
1121
- if (result.success) return { ...result, attempts: totalAttempts };
1122
- context = `Your response failed validation: ${result.errors.join("; ")}. Fix these and respond with valid JSON only.`;
1123
- }
1124
- return { success: false, errors: [`Max retries (${maxRetries}) exceeded`], attempts: totalAttempts };
1125
- }
1126
-
1127
- // src/ai/mcp-server-kit.ts
1128
- var ERR = { PARSE: -32700, INVALID: -32600, NOT_FOUND: -32601, PARAMS: -32602, INTERNAL: -32603 };
1129
- var MAX_MESSAGE_SIZE = 10 * 1024 * 1024;
1130
- var MAX_NAME_LENGTH = 256;
1131
- var HANDLER_TIMEOUT_MS = 3e4;
1132
- function validateToolInput(args, schema) {
1133
- if (typeof args !== "object" || args === null || Array.isArray(args)) {
1134
- return "Arguments must be an object";
1135
- }
1136
- if (schema.required) {
1137
- for (const req of schema.required) {
1138
- if (!(req in args) || args[req] === void 0 || args[req] === null) {
1139
- return `Missing required argument: ${req}`;
1140
- }
1141
- }
1142
- }
1143
- for (const [key, val] of Object.entries(args)) {
1144
- if (key === "__proto__" || key === "constructor" || key === "prototype") {
1145
- return `Invalid argument name: ${key}`;
1146
- }
1147
- const propSchema = schema.properties[key];
1148
- if (propSchema) {
1149
- const expectedType = propSchema.type;
1150
- if (expectedType === "string" && typeof val !== "string") return `Argument '${key}' must be a string`;
1151
- if (expectedType === "number" && typeof val !== "number") return `Argument '${key}' must be a number`;
1152
- if (expectedType === "boolean" && typeof val !== "boolean") return `Argument '${key}' must be a boolean`;
1153
- if (expectedType === "integer" && (typeof val !== "number" || !Number.isInteger(val))) return `Argument '${key}' must be an integer`;
1154
- if (propSchema.enum && !propSchema.enum.includes(val)) return `Argument '${key}' must be one of: ${propSchema.enum.join(", ")}`;
1155
- }
1156
- }
1157
- return null;
1158
- }
1159
- var MCPServerBuilder = class {
1160
- constructor(name, version = "1.0.0", description = "") {
1161
- this.name = name;
1162
- this.version = version;
1163
- this.description = description;
1164
- this.tools = /* @__PURE__ */ new Map();
1165
- this.resources = /* @__PURE__ */ new Map();
1166
- this.prompts = /* @__PURE__ */ new Map();
1167
- this.capabilities = {};
1168
- this.initialized = false;
1169
- if (!name || typeof name !== "string") throw new Error("Server name is required");
1170
- }
1171
- addTool(tool) {
1172
- if (!tool.name || typeof tool.name !== "string" || tool.name.length > MAX_NAME_LENGTH) throw new Error("Tool must have a valid name");
1173
- if (typeof tool.handler !== "function") throw new Error("Tool must have a handler function");
1174
- if (!tool.inputSchema || tool.inputSchema.type !== "object") throw new Error("Tool must have a valid inputSchema with type: 'object'");
1175
- this.tools.set(tool.name, tool);
1176
- this.capabilities.tools = {};
1177
- return this;
1178
- }
1179
- addResource(resource) {
1180
- if (!resource.uri || typeof resource.uri !== "string") throw new Error("Resource must have a URI");
1181
- if (typeof resource.fetch !== "function") throw new Error("Resource must have a fetch function");
1182
- this.resources.set(resource.uri, resource);
1183
- this.capabilities.resources = {};
1184
- return this;
1185
- }
1186
- addPrompt(prompt) {
1187
- if (!prompt.name || typeof prompt.name !== "string") throw new Error("Prompt must have a name");
1188
- if (typeof prompt.handler !== "function") throw new Error("Prompt must have a handler function");
1189
- this.prompts.set(prompt.name, prompt);
1190
- this.capabilities.prompts = {};
1191
- return this;
1192
- }
1193
- respond(id, result) {
1194
- return JSON.stringify({ jsonrpc: "2.0", id, result }) + "\n";
1195
- }
1196
- error(id, code, message, data) {
1197
- return JSON.stringify({ jsonrpc: "2.0", id, error: { code, message, data } }) + "\n";
1198
- }
1199
- /** Run a handler with a timeout */
1200
- async runWithTimeout(fn, timeoutMs) {
1201
- return new Promise((resolve7, reject) => {
1202
- const timer = setTimeout(() => reject(new Error("Handler execution timeout")), timeoutMs);
1203
- fn().then(
1204
- (result) => {
1205
- clearTimeout(timer);
1206
- resolve7(result);
1207
- },
1208
- (err) => {
1209
- clearTimeout(timer);
1210
- reject(err);
1211
- }
1212
- );
1213
- });
1214
- }
1215
- async dispatch(req) {
1216
- const { id, method, params } = req;
1217
- if (req.jsonrpc !== "2.0") {
1218
- if (id !== null && id !== void 0) return this.error(id, ERR.INVALID, "Invalid JSON-RPC version \u2014 must be '2.0'");
1219
- return null;
1220
- }
1221
- if (typeof method !== "string" || method.length === 0) {
1222
- if (id !== null && id !== void 0) return this.error(id, ERR.INVALID, "Method must be a non-empty string");
1223
- return null;
1224
- }
1225
- if (method === "initialize") {
1226
- this.initialized = true;
1227
- return this.respond(id, {
1228
- protocolVersion: "2024-11-05",
1229
- capabilities: this.capabilities,
1230
- serverInfo: { name: this.name, version: this.version },
1231
- instructions: this.description || void 0
1232
- });
1233
- }
1234
- if (method === "initialized") return null;
1235
- if (method === "ping") return this.respond(id, {});
1236
- if (method === "tools/list") {
1237
- return this.respond(id, {
1238
- tools: [...this.tools.values()].map((t) => ({
1239
- name: t.name,
1240
- description: t.description,
1241
- inputSchema: t.inputSchema
1242
- }))
1243
- });
1244
- }
1245
- if (method === "tools/call") {
1246
- const toolName = params?.name;
1247
- if (typeof toolName !== "string") return this.error(id, ERR.PARAMS, "Tool name is required");
1248
- const tool = this.tools.get(toolName);
1249
- if (!tool) return this.error(id, ERR.NOT_FOUND, `Tool not found: ${toolName}`);
1250
- const args = params?.arguments ?? {};
1251
- const validationError = validateToolInput(args, tool.inputSchema);
1252
- if (validationError) return this.error(id, ERR.PARAMS, validationError);
1253
- try {
1254
- const result = await this.runWithTimeout(
1255
- () => tool.handler(args, { progressToken: params?._meta?.progressToken }),
1256
- HANDLER_TIMEOUT_MS
1257
- );
1258
- const content = typeof result === "string" ? [{ type: "text", text: result }] : [{ type: "text", text: JSON.stringify(result) }];
1259
- return this.respond(id, { content, isError: false });
1260
- } catch (e) {
1261
- const msg = e?.message ?? String(e);
1262
- return this.respond(id, { content: [{ type: "text", text: msg.slice(0, 4096) }], isError: true });
1263
- }
1264
- }
1265
- if (method === "resources/list") {
1266
- return this.respond(id, {
1267
- resources: [...this.resources.values()].map((r) => ({
1268
- uri: r.uri,
1269
- name: r.name,
1270
- description: r.description,
1271
- mimeType: r.mimeType
1272
- }))
1273
- });
1274
- }
1275
- if (method === "resources/read") {
1276
- const uri = params?.uri;
1277
- if (typeof uri !== "string") return this.error(id, ERR.PARAMS, "Resource URI is required");
1278
- const resource = this.resources.get(uri);
1279
- if (!resource) return this.error(id, ERR.NOT_FOUND, `Resource not found: ${uri}`);
1280
- try {
1281
- const raw = await this.runWithTimeout(() => resource.fetch(), HANDLER_TIMEOUT_MS);
1282
- const content = typeof raw === "string" ? { uri: resource.uri, mimeType: resource.mimeType ?? "text/plain", text: raw } : { uri: resource.uri, mimeType: raw.mimeType ?? resource.mimeType ?? "text/plain", ...raw.text ? { text: raw.text } : { blob: raw.blob } };
1283
- return this.respond(id, { contents: [content] });
1284
- } catch (e) {
1285
- return this.error(id, ERR.INTERNAL, `Resource fetch failed: ${(e?.message ?? "Unknown error").slice(0, 1024)}`);
1286
- }
1287
- }
1288
- if (method === "prompts/list") {
1289
- return this.respond(id, {
1290
- prompts: [...this.prompts.values()].map((p) => ({ name: p.name, description: p.description, arguments: p.arguments }))
1291
- });
1292
- }
1293
- if (method === "prompts/get") {
1294
- const promptName = params?.name;
1295
- if (typeof promptName !== "string") return this.error(id, ERR.PARAMS, "Prompt name is required");
1296
- const prompt = this.prompts.get(promptName);
1297
- if (!prompt) return this.error(id, ERR.NOT_FOUND, `Prompt not found: ${promptName}`);
1298
- try {
1299
- const messages = await this.runWithTimeout(
1300
- () => prompt.handler(params?.arguments ?? {}),
1301
- HANDLER_TIMEOUT_MS
1302
- );
1303
- return this.respond(id, { description: prompt.description, messages });
1304
- } catch (e) {
1305
- return this.error(id, ERR.INTERNAL, `Prompt handler failed: ${(e?.message ?? "Unknown error").slice(0, 1024)}`);
1306
- }
1307
- }
1308
- if (id !== null && id !== void 0) return this.error(id, ERR.NOT_FOUND, `Method not found: ${method}`);
1309
- return null;
1310
- }
1311
- startStdio() {
1312
- process.stdin.setEncoding("utf-8");
1313
- let buf = "";
1314
- let processing = Promise.resolve();
1315
- process.stdin.on("data", (chunk) => {
1316
- buf += chunk;
1317
- if (buf.length > MAX_MESSAGE_SIZE) {
1318
- process.stdout.write(this.error(null, ERR.PARSE, "Message too large") + "\n");
1319
- buf = "";
1320
- return;
1321
- }
1322
- const lines = buf.split("\n");
1323
- buf = lines.pop() ?? "";
1324
- for (const line of lines) {
1325
- const trimmed = line.trim();
1326
- if (!trimmed) continue;
1327
- processing = processing.then(async () => {
1328
- let req;
1329
- try {
1330
- req = JSON.parse(trimmed);
1331
- } catch {
1332
- process.stdout.write(this.error(null, ERR.PARSE, "Parse error") + "\n");
1333
- return;
1334
- }
1335
- if (typeof req !== "object" || req === null || Array.isArray(req)) {
1336
- process.stdout.write(this.error(null, ERR.INVALID, "Request must be a JSON object") + "\n");
1337
- return;
1338
- }
1339
- try {
1340
- const response = await this.dispatch(req);
1341
- if (response) process.stdout.write(response);
1342
- } catch (e) {
1343
- const reqId = req?.id;
1344
- if (reqId !== null && reqId !== void 0) {
1345
- process.stdout.write(this.error(reqId, ERR.INTERNAL, (e?.message ?? "Internal error").slice(0, 1024)) + "\n");
1346
- }
1347
- }
1348
- });
1349
- }
1350
- });
1351
- process.stdin.on("end", () => process.exit(0));
1352
- process.stderr.write(`[devguard/mcp] "${this.name}" v${this.version} started
1353
- `);
1354
- }
1355
- };
1356
- var FileSystemAdapter = class {
1357
- constructor(dir = ".devguard-memory") {
1358
- this.keyIndex = /* @__PURE__ */ new Map();
1359
- this.baseDir = path__namespace.resolve(dir);
1360
- if (!fs__namespace.existsSync(this.baseDir)) {
1361
- fs__namespace.mkdirSync(this.baseDir, { recursive: true });
1362
- }
1363
- }
1364
- getFilePath(agentId) {
1365
- const safeId = crypto6__namespace.createHash("sha256").update(agentId).digest("hex");
1366
- return path__namespace.join(this.baseDir, `${safeId}.json`);
1367
- }
1368
- async save(agentId, entry) {
1369
- const file = this.getFilePath(agentId);
1370
- let history = [];
1371
- if (fs__namespace.existsSync(file)) {
1372
- try {
1373
- const raw = fs__namespace.readFileSync(file, "utf-8");
1374
- history = JSON.parse(raw);
1375
- if (!Array.isArray(history)) history = [];
1376
- } catch {
1377
- history = [];
1378
- }
1379
- }
1380
- history.push(entry);
1381
- const tmp = `${file}.tmp.${crypto6__namespace.randomBytes(4).toString("hex")}`;
1382
- fs__namespace.writeFileSync(tmp, JSON.stringify(history, null, 2));
1383
- fs__namespace.renameSync(tmp, file);
1384
- }
1385
- async getHistory(agentId, limit = 50) {
1386
- const file = this.getFilePath(agentId);
1387
- if (!fs__namespace.existsSync(file)) return [];
1388
- try {
1389
- const raw = fs__namespace.readFileSync(file, "utf-8");
1390
- const history = JSON.parse(raw);
1391
- if (!Array.isArray(history)) return [];
1392
- return history.slice(-limit);
1393
- } catch {
1394
- return [];
1395
- }
1396
- }
1397
- async clear(agentId) {
1398
- const file = this.getFilePath(agentId);
1399
- if (fs__namespace.existsSync(file)) fs__namespace.unlinkSync(file);
1400
- }
1401
- };
1402
- var RedisAdapter = class {
1403
- constructor(redisClient) {
1404
- this.client = redisClient;
1405
- }
1406
- async save(agentId, entry) {
1407
- const key = `devguard:mem:${agentId}`;
1408
- await this.client.rPush(key, JSON.stringify(entry));
1409
- await this.client.lTrim(key, -100, -1);
1410
- }
1411
- async getHistory(agentId, limit = 50) {
1412
- const key = `devguard:mem:${agentId}`;
1413
- const raw = await this.client.lRange(key, -limit, -1);
1414
- return raw.map((r) => {
1415
- try {
1416
- return JSON.parse(r);
1417
- } catch {
1418
- return null;
1419
- }
1420
- }).filter(Boolean);
1421
- }
1422
- async clear(agentId) {
1423
- await this.client.del(`devguard:mem:${agentId}`);
1424
- }
1425
- };
1426
- var AgentMemory = class {
1427
- constructor(adapter) {
1428
- this.adapter = adapter ?? new FileSystemAdapter();
1429
- }
1430
- async track(agentId, role, content, metadata) {
1431
- const entry = {
1432
- id: crypto6__namespace.randomUUID(),
1433
- role,
1434
- content,
1435
- metadata,
1436
- timestamp: Date.now()
1437
- };
1438
- await this.adapter.save(agentId, entry);
1439
- return entry;
1440
- }
1441
- async getContext(agentId, limit = 10) {
1442
- const history = await this.adapter.getHistory(agentId, limit);
1443
- return history.map((h) => `${h.role.toUpperCase()}: ${h.content}`).join("\n");
1444
- }
1445
- async clear(agentId) {
1446
- await this.adapter.clear(agentId);
1447
- }
1448
- };
1449
- var MAX_RECORDS = 5e3;
1450
- var LLMBudget = class {
1451
- constructor(config) {
1452
- this.records = [];
1453
- this.totalCost = 0;
1454
- this.config = config;
1455
- }
1456
- track(usage) {
1457
- const record = {
1458
- ...usage,
1459
- id: crypto6__namespace.randomUUID(),
1460
- timestamp: Date.now()
1461
- };
1462
- this.records.push(record);
1463
- this.totalCost += record.costUSD;
1464
- if (this.records.length > MAX_RECORDS) {
1465
- this.records.shift();
1466
- }
1467
- if (this.totalCost >= this.config.monthlyLimitUSD) {
1468
- console.warn(`[devguard] LLM Budget Exceeded: $${this.totalCost.toFixed(4)} / $${this.config.monthlyLimitUSD}`);
1469
- } else if (this.config.warnAtUSD && this.totalCost >= this.config.warnAtUSD) {
1470
- console.warn(`[devguard] LLM Budget Warning: $${this.totalCost.toFixed(4)} / $${this.config.monthlyLimitUSD}`);
1471
- }
1472
- return record;
1473
- }
1474
- getSummary() {
1475
- return {
1476
- totalCost: this.totalCost,
1477
- recordCount: this.records.length,
1478
- limit: this.config.monthlyLimitUSD,
1479
- remaining: Math.max(0, this.config.monthlyLimitUSD - this.totalCost),
1480
- isExceeded: this.totalCost >= this.config.monthlyLimitUSD
1481
- };
1482
- }
1483
- getHistory(limit = 100) {
1484
- return this.records.slice(-limit);
1485
- }
1486
- reset() {
1487
- this.records = [];
1488
- this.totalCost = 0;
1489
- }
1490
- };
1491
- var MIN_SECRET_LENGTH = 16;
1492
- var CLOCK_SKEW_TOLERANCE = 30;
1493
- function b64urlDecode(s) {
1494
- const base64 = s.replace(/-/g, "+").replace(/_/g, "/");
1495
- const padLen = (4 - base64.length % 4) % 4;
1496
- const padded = base64 + "=".repeat(padLen);
1497
- return Buffer.from(padded, "base64");
1498
- }
1499
- function b64urlEncode(buf) {
1500
- return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
1501
- }
1502
- function decodeJWT(token) {
1503
- if (typeof token !== "string") return null;
1504
- const parts = token.split(".");
1505
- if (parts.length !== 3) return null;
1506
- if (!parts[0] || !parts[1] || !parts[2]) return null;
1507
- try {
1508
- const header = JSON.parse(b64urlDecode(parts[0]).toString());
1509
- const payload = JSON.parse(b64urlDecode(parts[1]).toString());
1510
- if (typeof header !== "object" || header === null) return null;
1511
- if (typeof payload !== "object" || payload === null) return null;
1512
- return { header, payload, sig: parts[2], signingInput: `${parts[0]}.${parts[1]}` };
1513
- } catch {
1514
- return null;
1515
- }
1516
- }
1517
- function signHMAC(payload, secret, algorithm = "HS256") {
1518
- if (!secret || typeof secret !== "string") throw new Error("JWT secret is required");
1519
- if (secret.length < MIN_SECRET_LENGTH) throw new Error(`JWT secret must be at least ${MIN_SECRET_LENGTH} characters (got ${secret.length})`);
1520
- if (typeof payload !== "object" || payload === null) throw new Error("Payload must be a non-null object");
1521
- const h = b64urlEncode(Buffer.from(JSON.stringify({ alg: algorithm, typ: "JWT" })));
1522
- const p = b64urlEncode(Buffer.from(JSON.stringify(payload)));
1523
- const input = `${h}.${p}`;
1524
- const sig = b64urlEncode(crypto6__namespace.createHmac(algorithm === "HS512" ? "sha512" : "sha256", secret).update(input).digest());
1525
- return `${input}.${sig}`;
1526
- }
1527
- function verifyHMAC(token, secret, expectedAlg) {
1528
- if (!secret || typeof secret !== "string") return { valid: false, error: "Secret is required" };
1529
- const decoded = decodeJWT(token);
1530
- if (!decoded) return { valid: false, error: "Malformed JWT" };
1531
- const alg = decoded.header.alg;
1532
- if (!["HS256", "HS512"].includes(alg)) return { valid: false, error: `Unsupported algorithm: ${alg}` };
1533
- if (expectedAlg && alg !== expectedAlg) return { valid: false, error: `Algorithm mismatch: expected ${expectedAlg}, got ${alg}` };
1534
- const expected = b64urlEncode(crypto6__namespace.createHmac(alg === "HS512" ? "sha512" : "sha256", secret).update(decoded.signingInput).digest());
1535
- const sigBuf = Buffer.from(decoded.sig);
1536
- const expBuf = Buffer.from(expected);
1537
- if (sigBuf.length !== expBuf.length || !crypto6__namespace.timingSafeEqual(sigBuf, expBuf)) return { valid: false, error: "Invalid signature" };
1538
- const now = Math.floor(Date.now() / 1e3);
1539
- if (decoded.payload.exp && now > decoded.payload.exp + CLOCK_SKEW_TOLERANCE) return { valid: false, error: "Token expired" };
1540
- if (decoded.payload.nbf && now < decoded.payload.nbf - CLOCK_SKEW_TOLERANCE) return { valid: false, error: "Token not yet valid" };
1541
- return { valid: true, payload: decoded.payload };
1542
- }
1543
- var JWKS_TTL_MS = 5 * 60 * 1e3;
1544
- var JWKS_MAX_ENTRIES = 50;
1545
- var BoundedJWKSCache = class {
1546
- constructor() {
1547
- this.cache = /* @__PURE__ */ new Map();
1548
- this.pruneTimer = null;
1549
- this.pruneTimer = setInterval(() => this.prune(), 6e4);
1550
- if (this.pruneTimer.unref) this.pruneTimer.unref();
1551
- }
1552
- get(uri) {
1553
- const entry = this.cache.get(uri);
1554
- if (!entry) return null;
1555
- if (Date.now() - entry.fetchedAt > JWKS_TTL_MS) {
1556
- this.cache.delete(uri);
1557
- return null;
1558
- }
1559
- return entry.keys;
1560
- }
1561
- set(uri, keys) {
1562
- if (this.cache.size >= JWKS_MAX_ENTRIES) {
1563
- let oldestKey = null;
1564
- let oldestTime = Infinity;
1565
- for (const [k, v] of this.cache) {
1566
- if (v.fetchedAt < oldestTime) {
1567
- oldestTime = v.fetchedAt;
1568
- oldestKey = k;
1569
- }
1570
- }
1571
- if (oldestKey) this.cache.delete(oldestKey);
1572
- }
1573
- this.cache.set(uri, { keys, fetchedAt: Date.now() });
1574
- }
1575
- prune() {
1576
- const now = Date.now();
1577
- let pruned = 0;
1578
- for (const [uri, entry] of this.cache) {
1579
- if (now - entry.fetchedAt > JWKS_TTL_MS) {
1580
- this.cache.delete(uri);
1581
- pruned++;
1582
- }
1583
- }
1584
- return pruned;
1585
- }
1586
- size() {
1587
- return this.cache.size;
1588
- }
1589
- destroy() {
1590
- if (this.pruneTimer) {
1591
- clearInterval(this.pruneTimer);
1592
- this.pruneTimer = null;
1593
- }
1594
- this.cache.clear();
1595
- }
1596
- };
1597
- var jwksCache = new BoundedJWKSCache();
1598
- var MAX_JWKS_SIZE = 512 * 1024;
1599
- function httpsGet3(url) {
1600
- return new Promise((resolve7, reject) => {
1601
- let parsed;
1602
- try {
1603
- parsed = new URL(url);
1604
- } catch {
1605
- reject(new Error("Invalid JWKS URL"));
1606
- return;
1607
- }
1608
- if (parsed.protocol !== "https:") {
1609
- reject(new Error("JWKS URL must use HTTPS"));
1610
- return;
1611
- }
1612
- const req = https__namespace.get(url, { headers: { "Accept": "application/json" } }, (res) => {
1613
- let body = "";
1614
- let size = 0;
1615
- res.on("data", (d) => {
1616
- size += typeof d === "string" ? d.length : d.length;
1617
- if (size > MAX_JWKS_SIZE) {
1618
- req.destroy();
1619
- reject(new Error("JWKS response too large"));
1620
- return;
1621
- }
1622
- body += d;
1623
- });
1624
- res.on("end", () => resolve7(body));
1625
- });
1626
- req.on("error", (e) => reject(new Error(`JWKS fetch failed: ${e.message}`)));
1627
- req.setTimeout(5e3, () => {
1628
- req.destroy();
1629
- reject(new Error("JWKS fetch timeout"));
1630
- });
1631
- });
1632
- }
1633
- async function fetchJWKS(jwksUri) {
1634
- const cached = jwksCache.get(jwksUri);
1635
- if (cached) return cached;
1636
- const body = await httpsGet3(jwksUri);
1637
- let parsed;
1638
- try {
1639
- parsed = JSON.parse(body);
1640
- } catch {
1641
- throw new Error("Invalid JWKS JSON response");
1642
- }
1643
- if (!parsed || !Array.isArray(parsed.keys)) throw new Error("JWKS response missing 'keys' array");
1644
- const keys = parsed.keys;
1645
- jwksCache.set(jwksUri, keys);
1646
- return keys;
1647
- }
1648
- var jwksCacheUtils = {
1649
- prune: () => jwksCache.prune(),
1650
- size: () => jwksCache.size(),
1651
- destroy: () => jwksCache.destroy()
1652
- };
1653
- function jwkToPublicKey(jwk) {
1654
- if (jwk.kty !== "RSA" || !jwk.n || !jwk.e) throw new Error("Unsupported JWK type \u2014 only RSA is supported");
1655
- return crypto6__namespace.createPublicKey({ key: { kty: "RSA", n: jwk.n, e: jwk.e }, format: "jwk" });
1656
- }
1657
- async function verifyRS256(token, jwksUri) {
1658
- const decoded = decodeJWT(token);
1659
- if (!decoded) return { valid: false, error: "Malformed JWT" };
1660
- if (decoded.header.alg !== "RS256") return { valid: false, error: "Expected RS256" };
1661
- let keys;
1662
- try {
1663
- keys = await fetchJWKS(jwksUri);
1664
- } catch (e) {
1665
- return { valid: false, error: `JWKS fetch failed: ${e instanceof Error ? e.message : "Unknown error"}` };
1666
- }
1667
- const matching = decoded.header.kid ? keys.filter((k) => k.kid === decoded.header.kid) : keys;
1668
- if (!matching.length) return { valid: false, error: "No matching JWK found" };
1669
- for (const jwk of matching) {
1670
- try {
1671
- const pubKey = jwkToPublicKey(jwk);
1672
- const isValid = crypto6__namespace.verify("sha256", Buffer.from(decoded.signingInput), { key: pubKey, padding: crypto6__namespace.constants.RSA_PKCS1_PADDING }, b64urlDecode(decoded.sig));
1673
- if (isValid) {
1674
- const now = Math.floor(Date.now() / 1e3);
1675
- if (decoded.payload.exp && now > decoded.payload.exp + CLOCK_SKEW_TOLERANCE) return { valid: false, error: "Token expired" };
1676
- if (decoded.payload.nbf && now < decoded.payload.nbf - CLOCK_SKEW_TOLERANCE) return { valid: false, error: "Token not yet valid" };
1677
- return { valid: true, payload: decoded.payload };
1678
- }
1679
- } catch {
1680
- continue;
1681
- }
1682
- }
1683
- return { valid: false, error: "Signature verification failed" };
1684
- }
1685
- var MAX_REVOCATION_SIZE = 1e5;
1686
- var RevocationList = class {
1687
- constructor() {
1688
- this.revoked = /* @__PURE__ */ new Map();
1689
- }
1690
- revoke(jti) {
1691
- if (!jti || typeof jti !== "string") return;
1692
- if (this.revoked.size >= MAX_REVOCATION_SIZE && !this.revoked.has(jti)) {
1693
- let oldestKey = null;
1694
- let oldestTime = Infinity;
1695
- for (const [k, ts] of this.revoked) {
1696
- if (ts < oldestTime) {
1697
- oldestTime = ts;
1698
- oldestKey = k;
1699
- }
1700
- }
1701
- if (oldestKey) this.revoked.delete(oldestKey);
1702
- }
1703
- this.revoked.set(jti, Date.now());
1704
- }
1705
- isRevoked(jti) {
1706
- return jti ? this.revoked.has(jti) : false;
1707
- }
1708
- revokedCount() {
1709
- return this.revoked.size;
1710
- }
1711
- prune(maxAgeMs = 30 * 24 * 60 * 60 * 1e3) {
1712
- const cutoff = Date.now() - maxAgeMs;
1713
- let pruned = 0;
1714
- for (const [jti, ts] of this.revoked) if (ts < cutoff) {
1715
- this.revoked.delete(jti);
1716
- pruned++;
1717
- }
1718
- return pruned;
1719
- }
1720
- exportJSON() {
1721
- return JSON.stringify([...this.revoked.entries()]);
1722
- }
1723
- importJSON(json) {
1724
- let data;
1725
- try {
1726
- data = JSON.parse(json);
1727
- } catch {
1728
- return;
1729
- }
1730
- if (!Array.isArray(data)) return;
1731
- for (const entry of data) {
1732
- if (Array.isArray(entry) && entry.length === 2 && typeof entry[0] === "string" && typeof entry[1] === "number") {
1733
- if (this.revoked.size >= MAX_REVOCATION_SIZE) break;
1734
- this.revoked.set(entry[0], entry[1]);
1735
- }
1736
- }
1737
- }
1738
- };
1739
- function detectAnomalies(payload, ctx) {
1740
- const warnings = [];
1741
- let score = 0;
1742
- if (!payload.iat) {
1743
- warnings.push("Missing iat");
1744
- score += 20;
1745
- }
1746
- if (!payload.jti) {
1747
- warnings.push("Missing jti \u2014 revocation not possible");
1748
- score += 15;
1749
- }
1750
- if (!payload.iss) {
1751
- warnings.push("Missing iss");
1752
- score += 10;
1753
- }
1754
- if (!payload.sub) {
1755
- warnings.push("Missing sub");
1756
- score += 10;
1757
- }
1758
- if (ctx?.expectedIss && payload.iss !== ctx.expectedIss) {
1759
- warnings.push(`Issuer mismatch: ${payload.iss}`);
1760
- score += 40;
1761
- }
1762
- if (ctx?.expectedAud) {
1763
- const aud = Array.isArray(payload.aud) ? payload.aud : [payload.aud];
1764
- if (!aud.includes(ctx.expectedAud)) {
1765
- warnings.push("Audience mismatch");
1766
- score += 40;
1767
- }
1768
- }
1769
- if (payload.exp && payload.iat && payload.exp - payload.iat > 86400 * 30) {
1770
- warnings.push(`Token lifetime > 30 days`);
1771
- score += 25;
1772
- }
1773
- if (payload.nbf && payload.iat && payload.nbf > payload.iat + 86400) {
1774
- warnings.push("nbf is more than 1 day after iat");
1775
- score += 10;
1776
- }
1777
- if (!payload.exp) {
1778
- warnings.push("Missing exp \u2014 token never expires");
1779
- score += 20;
1780
- }
1781
- return { score: Math.min(100, score), warnings, level: score >= 50 ? "dangerous" : score >= 20 ? "suspicious" : "safe" };
1782
- }
1783
- var JWTVerifier = class {
1784
- constructor(opts) {
1785
- this.opts = opts;
1786
- this.revocation = new RevocationList();
1787
- }
1788
- async verify(token) {
1789
- if (!token || typeof token !== "string") return { valid: false, error: "Token is required" };
1790
- let result;
1791
- if (this.opts.secret) result = verifyHMAC(token, this.opts.secret, this.opts.expectedAlg);
1792
- else if (this.opts.jwksUri) result = await verifyRS256(token, this.opts.jwksUri);
1793
- else return { valid: false, error: "No secret or jwksUri configured" };
1794
- if (!result.valid || !result.payload) return result;
1795
- if (this.revocation.isRevoked(result.payload.jti)) return { valid: false, error: "Token has been revoked" };
1796
- return { ...result, anomalies: detectAnomalies(result.payload, { expectedIss: this.opts.expectedIss, expectedAud: this.opts.expectedAud }) };
1797
- }
1798
- revoke(jti) {
1799
- this.revocation.revoke(jti);
1800
- }
1801
- getRevocationList() {
1802
- return this.revocation;
1803
- }
1804
- };
1805
-
1806
- // src/auth/bot-fence.ts
1807
- var BOT_UA = /bot|crawl|spider|scraper|python-?requests|axios\/|node-?fetch|undici|got\/|superagent|aiohttp|httpx|libwww|java\/|curl\/|wget\//i;
1808
- var HEADLESS_UA = /headlesschrome|playwright|puppeteer|selenium|phantomjs|zombie|slimer|cypress/i;
1809
- var AI_UA = /GPTBot|ChatGPT|Claude-Web|anthropic-ai|cohere-ai|meta-externalagent|Bytespider|CCBot/i;
1810
- var KNOWN_GOOD_UA = /Mozilla\/5\.[01].+(Chrome\/[1-9]\d{2}|Firefox\/[1-9]\d{2}|Safari\/[0-9]+)/;
1811
- var SIGNALS = [
1812
- { signal: "Bot user-agent", weight: 65, test: (fp) => BOT_UA.test(fp.userAgent ?? "") },
1813
- { signal: "Headless browser UA", weight: 80, test: (fp) => HEADLESS_UA.test(fp.userAgent ?? "") },
1814
- { signal: "Known AI crawler", weight: 70, test: (fp) => AI_UA.test(fp.userAgent ?? "") },
1815
- { signal: "Missing user-agent", weight: 45, test: (fp) => !fp.userAgent?.trim() },
1816
- { signal: "No Accept-Language", weight: 20, test: (fp) => !fp.acceptLanguage && !fp.headers?.["accept-language"] },
1817
- { signal: "No Accept-Encoding", weight: 15, test: (fp) => !fp.acceptEncoding && !fp.headers?.["accept-encoding"] },
1818
- { signal: "Missing Referer on form", weight: 10, test: (fp) => !fp.referer && !fp.headers?.["referer"] },
1819
- { signal: "Suspicious timing <80ms", weight: 30, test: (fp) => fp.timingMs !== void 0 && fp.timingMs < 80 },
1820
- { signal: "Zero mouse events", weight: 25, test: (fp) => fp.mouseEvents !== void 0 && fp.mouseEvents === 0 },
1821
- { signal: "No keyboard entropy", weight: 20, test: (fp) => fp.keystrokeIntervals !== void 0 && fp.keystrokeIntervals.length === 0 },
1822
- { signal: "Non-browser UA format", weight: 20, test: (fp) => !!fp.userAgent && !KNOWN_GOOD_UA.test(fp.userAgent) && !BOT_UA.test(fp.userAgent) },
1823
- { signal: "Duplicate headers", weight: 15, test: (fp) => fp.headers ? Object.values(fp.headers).some((v) => Array.isArray(v) && v.length > 1) : false }
1824
- ];
1825
- function scoreRequest(fp) {
1826
- const results = SIGNALS.map((s) => ({ signal: s.signal, weight: s.weight, matched: s.test(fp) }));
1827
- const raw = results.filter((r) => r.matched).reduce((acc, r) => acc + r.weight, 0);
1828
- const score = Math.min(100, raw);
1829
- const verdict = score >= 70 ? "bot" : score >= 45 ? "suspicious" : score >= 20 ? "likely_human" : "human";
1830
- const recommendation = score >= 70 ? "block" : score >= 45 ? "challenge" : "allow";
1831
- return { score, verdict, signals: results, recommendation };
1832
- }
1833
- function normalizeIP(ip) {
1834
- if (!ip || typeof ip !== "string") return "";
1835
- const trimmed = ip.trim();
1836
- const v4mapped = trimmed.match(/^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/i);
1837
- if (v4mapped) return v4mapped[1];
1838
- return trimmed;
1839
- }
1840
- var MAX_TRACKED_IPS = 1e5;
1841
- var PRUNE_INTERVAL = 100;
1842
- var IPRateLimiter = class {
1843
- constructor(maxRequests = 60, windowMs = 6e4, blockDurationMs = 5 * 6e4) {
1844
- this.maxRequests = maxRequests;
1845
- this.windowMs = windowMs;
1846
- this.blockDurationMs = blockDurationMs;
1847
- this.windows = /* @__PURE__ */ new Map();
1848
- this.blocked = /* @__PURE__ */ new Map();
1849
- // ip → blockedUntil timestamp
1850
- this.checkCount = 0;
1851
- }
1852
- check(ip) {
1853
- const normalizedIp = normalizeIP(ip);
1854
- if (!normalizedIp) return { allowed: false, remaining: 0, retryAfterMs: this.blockDurationMs };
1855
- this.checkCount++;
1856
- if (this.checkCount % PRUNE_INTERVAL === 0) this.prune();
1857
- const blockedUntil = this.blocked.get(normalizedIp);
1858
- if (blockedUntil !== void 0) {
1859
- if (Date.now() < blockedUntil) {
1860
- return { allowed: false, remaining: 0, retryAfterMs: blockedUntil - Date.now() };
1861
- }
1862
- this.blocked.delete(normalizedIp);
1863
- }
1864
- const now = Date.now();
1865
- let w = this.windows.get(normalizedIp);
1866
- if (!w || now - w.windowStart >= this.windowMs) {
1867
- w = { count: 0, windowStart: now };
1868
- this.windows.set(normalizedIp, w);
1869
- }
1870
- w.count++;
1871
- if (w.count > this.maxRequests) {
1872
- this.blocked.set(normalizedIp, Date.now() + this.blockDurationMs);
1873
- return { allowed: false, remaining: 0, retryAfterMs: this.blockDurationMs };
1874
- }
1875
- return { allowed: true, remaining: this.maxRequests - w.count };
1876
- }
1877
- /** Prune stale windows and expired blocks to prevent memory growth */
1878
- prune() {
1879
- const now = Date.now();
1880
- const windowCutoff = now - this.windowMs * 2;
1881
- for (const [ip, w] of this.windows) if (w.windowStart < windowCutoff) this.windows.delete(ip);
1882
- for (const [ip, until] of this.blocked) if (now >= until) this.blocked.delete(ip);
1883
- if (this.windows.size > MAX_TRACKED_IPS) {
1884
- const sorted = [...this.windows.entries()].sort((a, b) => a[1].windowStart - b[1].windowStart);
1885
- const toRemove = sorted.slice(0, this.windows.size - MAX_TRACKED_IPS);
1886
- for (const [ip] of toRemove) this.windows.delete(ip);
1887
- }
1888
- }
1889
- };
1890
- function createMiddleware(opts = {}) {
1891
- const { blockThreshold = 70, challengeThreshold = 50, whitelist = [], blacklist = [] } = opts;
1892
- const wSet = new Set(whitelist.map(normalizeIP));
1893
- const bSet = new Set(blacklist.map(normalizeIP));
1894
- return function botFenceMiddleware(req, res, next) {
1895
- const rawIp = req.ip ?? req.connection?.remoteAddress ?? "";
1896
- const ip = normalizeIP(rawIp);
1897
- if (wSet.has(ip)) return next();
1898
- if (bSet.has(ip)) return res.status(403).json({ error: "Access denied" });
1899
- if (opts.rateLimiter) {
1900
- const rl = opts.rateLimiter.check(ip);
1901
- if (!rl.allowed) {
1902
- res.setHeader("Retry-After", Math.ceil((rl.retryAfterMs ?? 6e4) / 1e3));
1903
- return res.status(429).json({ error: "Too many requests" });
1904
- }
1905
- }
1906
- const fp = {
1907
- ip,
1908
- userAgent: req.headers["user-agent"],
1909
- headers: req.headers,
1910
- acceptLanguage: req.headers["accept-language"],
1911
- acceptEncoding: req.headers["accept-encoding"],
1912
- referer: req.headers["referer"],
1913
- origin: req.headers["origin"]
1914
- };
1915
- const botScore = scoreRequest(fp);
1916
- req.botScore = botScore;
1917
- if (botScore.score >= blockThreshold) {
1918
- opts.onBlock?.(fp, botScore);
1919
- return res.status(403).json({ error: "Automated request detected", verdict: botScore.verdict });
1920
- }
1921
- if (botScore.score >= challengeThreshold) {
1922
- res.setHeader("X-Bot-Challenge", "true");
1923
- }
1924
- next();
1925
- };
1926
- }
1927
- var MAX_CBOR_DEPTH = 20;
1928
- var MAX_CBOR_SIZE = 1 * 1024 * 1024;
1929
- function cborDecode(buf) {
1930
- if (buf.length > MAX_CBOR_SIZE) throw new Error("CBOR input too large");
1931
- let offset = 0;
1932
- function read(depth) {
1933
- if (depth > MAX_CBOR_DEPTH) throw new Error("CBOR nesting too deep");
1934
- if (offset >= buf.length) throw new Error("CBOR: unexpected end of input");
1935
- const b = buf[offset++];
1936
- const mt = b >> 5 & 7;
1937
- let ai = b & 31;
1938
- let val = ai;
1939
- if (ai === 24) {
1940
- if (offset >= buf.length) throw new Error("CBOR: unexpected end of input");
1941
- val = buf[offset++];
1942
- } else if (ai === 25) {
1943
- if (offset + 2 > buf.length) throw new Error("CBOR: unexpected end of input");
1944
- val = buf.readUInt16BE(offset);
1945
- offset += 2;
1946
- } else if (ai === 26) {
1947
- if (offset + 4 > buf.length) throw new Error("CBOR: unexpected end of input");
1948
- val = buf.readUInt32BE(offset);
1949
- offset += 4;
1950
- } else if (ai >= 28) {
1951
- if (ai === 31 && (mt === 2 || mt === 3)) throw new Error("CBOR: indefinite length not supported");
1952
- if (ai >= 28 && ai <= 30) throw new Error("CBOR: reserved additional info");
1953
- }
1954
- if (mt === 0) return val;
1955
- if (mt === 1) return -1 - val;
1956
- if (mt === 2) {
1957
- if (offset + val > buf.length) throw new Error("CBOR: byte string exceeds buffer");
1958
- const s = buf.slice(offset, offset + val);
1959
- offset += val;
1960
- return s;
1961
- }
1962
- if (mt === 3) {
1963
- if (offset + val > buf.length) throw new Error("CBOR: text string exceeds buffer");
1964
- const s = buf.slice(offset, offset + val).toString("utf8");
1965
- offset += val;
1966
- return s;
1967
- }
1968
- if (mt === 4) {
1969
- if (val > 1e4) throw new Error("CBOR: array too large");
1970
- const arr = [];
1971
- for (let i = 0; i < val; i++) arr.push(read(depth + 1));
1972
- return arr;
1973
- }
1974
- if (mt === 5) {
1975
- if (val > 1e4) throw new Error("CBOR: map too large");
1976
- const obj = {};
1977
- for (let i = 0; i < val; i++) {
1978
- const k = read(depth + 1);
1979
- const v = read(depth + 1);
1980
- if (k === "__proto__" || k === "constructor" || k === "prototype") continue;
1981
- obj[k] = v;
1982
- }
1983
- return obj;
1984
- }
1985
- if (mt === 7) {
1986
- if (ai === 20) return false;
1987
- if (ai === 21) return true;
1988
- if (ai === 22) return null;
1989
- if (ai === 23) return void 0;
1990
- }
1991
- return null;
1992
- }
1993
- return read(0);
1994
- }
1995
- var MAX_CHALLENGES = 1e4;
1996
- var ChallengeStore = class {
1997
- constructor() {
1998
- this.challenges = /* @__PURE__ */ new Map();
1999
- }
2000
- create(ttlMs = 3e5) {
2001
- if (this.challenges.size >= MAX_CHALLENGES) {
2002
- this._prune();
2003
- if (this.challenges.size >= MAX_CHALLENGES) {
2004
- throw new Error("Challenge store capacity exceeded \u2014 too many pending challenges");
2005
- }
2006
- }
2007
- const challenge = crypto6__namespace.randomBytes(32).toString("base64url");
2008
- this.challenges.set(challenge, { challenge, expiresAt: Date.now() + ttlMs });
2009
- return challenge;
2010
- }
2011
- consume(challenge) {
2012
- if (!challenge || typeof challenge !== "string") return false;
2013
- const entry = this.challenges.get(challenge);
2014
- if (!entry) return false;
2015
- this.challenges.delete(challenge);
2016
- return Date.now() <= entry.expiresAt;
2017
- }
2018
- _prune() {
2019
- const now = Date.now();
2020
- for (const [k, v] of this.challenges) if (v.expiresAt < now) this.challenges.delete(k);
2021
- }
2022
- /** Get the number of active challenges */
2023
- size() {
2024
- return this.challenges.size;
2025
- }
2026
- };
2027
- var MAX_CREDENTIALS_PER_USER = 50;
2028
- var CredentialStore = class {
2029
- constructor() {
2030
- this.creds = /* @__PURE__ */ new Map();
2031
- this.byUser = /* @__PURE__ */ new Map();
2032
- }
2033
- save(cred) {
2034
- const userCreds = this.byUser.get(cred.userId);
2035
- if (userCreds && !userCreds.has(cred.credentialId) && userCreds.size >= MAX_CREDENTIALS_PER_USER) {
2036
- throw new Error(`User ${cred.userId} has reached the maximum credential limit (${MAX_CREDENTIALS_PER_USER})`);
2037
- }
2038
- this.creds.set(cred.credentialId, cred);
2039
- if (!this.byUser.has(cred.userId)) this.byUser.set(cred.userId, /* @__PURE__ */ new Set());
2040
- this.byUser.get(cred.userId).add(cred.credentialId);
2041
- }
2042
- get(credentialId) {
2043
- return this.creds.get(credentialId);
2044
- }
2045
- getByUser(userId) {
2046
- const ids = this.byUser.get(userId) ?? /* @__PURE__ */ new Set();
2047
- return [...ids].map((id) => this.creds.get(id)).filter(Boolean);
2048
- }
2049
- update(credentialId, patch) {
2050
- const existing = this.creds.get(credentialId);
2051
- if (existing) {
2052
- const safeUpdate = {};
2053
- if (typeof patch.signCount === "number") safeUpdate.signCount = patch.signCount;
2054
- if (typeof patch.lastUsedAt === "number") safeUpdate.lastUsedAt = patch.lastUsedAt;
2055
- if (Array.isArray(patch.transports)) safeUpdate.transports = patch.transports;
2056
- this.creds.set(credentialId, { ...existing, ...safeUpdate });
2057
- }
2058
- }
2059
- delete(credentialId) {
2060
- const cred = this.creds.get(credentialId);
2061
- if (cred) {
2062
- this.byUser.get(cred.userId)?.delete(credentialId);
2063
- this.creds.delete(credentialId);
2064
- }
2065
- }
2066
- exportJSON() {
2067
- return JSON.stringify([...this.creds.values()], null, 2);
2068
- }
2069
- importJSON(json) {
2070
- let creds;
2071
- try {
2072
- creds = JSON.parse(json);
2073
- } catch {
2074
- return;
2075
- }
2076
- if (!Array.isArray(creds)) return;
2077
- for (const c2 of creds) {
2078
- if (c2 && typeof c2 === "object" && typeof c2.credentialId === "string" && typeof c2.publicKeyPem === "string") {
2079
- try {
2080
- this.save(c2);
2081
- } catch {
2082
- }
2083
- }
2084
- }
2085
- }
2086
- };
2087
- function generateRegistrationOptions(opts) {
2088
- if (!opts.rpId || !opts.rpName || !opts.userId || !opts.userName) {
2089
- throw new Error("rpId, rpName, userId, and userName are required");
2090
- }
2091
- const store = opts.challengeStore ?? new ChallengeStore();
2092
- return {
2093
- challenge: store.create(),
2094
- rp: { id: opts.rpId, name: opts.rpName },
2095
- user: { id: Buffer.from(opts.userId).toString("base64url"), name: opts.userName, displayName: opts.userDisplayName ?? opts.userName },
2096
- pubKeyCredParams: [{ type: "public-key", alg: -7 }, { type: "public-key", alg: -257 }],
2097
- timeout: 6e4,
2098
- attestation: "none",
2099
- authenticatorSelection: { userVerification: "preferred", residentKey: "preferred" }
2100
- };
2101
- }
2102
- function parseAuthenticatorData(buf) {
2103
- if (buf.length < 37) throw new Error("AuthenticatorData too short");
2104
- let offset = 0;
2105
- const rpIdHash = buf.slice(offset, offset + 32);
2106
- offset += 32;
2107
- const flags = buf[offset++];
2108
- const signCount = buf.readUInt32BE(offset);
2109
- offset += 4;
2110
- let aaguid;
2111
- let credentialId;
2112
- let coseKey;
2113
- if (flags & 64) {
2114
- if (offset + 18 > buf.length) throw new Error("AuthenticatorData truncated (attested credential data)");
2115
- aaguid = buf.slice(offset, offset + 16).toString("hex");
2116
- offset += 16;
2117
- const credIdLen = buf.readUInt16BE(offset);
2118
- offset += 2;
2119
- if (credIdLen > 1024) throw new Error("Credential ID too large");
2120
- if (offset + credIdLen > buf.length) throw new Error("AuthenticatorData truncated (credential ID)");
2121
- credentialId = buf.slice(offset, offset + credIdLen);
2122
- offset += credIdLen;
2123
- if (offset >= buf.length) throw new Error("AuthenticatorData truncated (COSE key)");
2124
- coseKey = cborDecode(buf.slice(offset));
2125
- }
2126
- return { rpIdHash, flags, signCount, aaguid, credentialId, coseKey };
2127
- }
2128
- function coseToPublicKeyPem(coseKey) {
2129
- if (!coseKey || typeof coseKey !== "object") throw new Error("Invalid COSE key");
2130
- const kty = coseKey[1];
2131
- coseKey[3];
2132
- if (kty === 2) {
2133
- const x = Buffer.isBuffer(coseKey[-2]) ? coseKey[-2] : Buffer.from(coseKey[-2]);
2134
- const y = Buffer.isBuffer(coseKey[-3]) ? coseKey[-3] : Buffer.from(coseKey[-3]);
2135
- if (x.length !== 32 || y.length !== 32) throw new Error("Invalid EC2 key coordinates");
2136
- const key = crypto6__namespace.createPublicKey({ key: { kty: "EC", crv: "P-256", x: x.toString("base64url"), y: y.toString("base64url") }, format: "jwk" });
2137
- return { pem: key.export({ type: "spki", format: "pem" }), alg: -7 };
2138
- }
2139
- if (kty === 3) {
2140
- const n = Buffer.isBuffer(coseKey[-1]) ? coseKey[-1] : Buffer.from(coseKey[-1]);
2141
- const e = Buffer.isBuffer(coseKey[-2]) ? coseKey[-2] : Buffer.from(coseKey[-2]);
2142
- if (n.length < 128) throw new Error("RSA key modulus too short");
2143
- const key = crypto6__namespace.createPublicKey({ key: { kty: "RSA", n: n.toString("base64url"), e: e.toString("base64url") }, format: "jwk" });
2144
- return { pem: key.export({ type: "spki", format: "pem" }), alg: -257 };
2145
- }
2146
- throw new Error(`Unsupported COSE key type: ${kty}`);
2147
- }
2148
- async function verifyRegistration(opts) {
2149
- try {
2150
- if (!opts.response || !opts.expectedChallenge || !opts.expectedOrigin || !opts.expectedRpId || !opts.userId) {
2151
- return { verified: false, error: "Missing required parameters" };
2152
- }
2153
- const clientDataBuf = Buffer.from(opts.response.clientDataJSON, "base64url");
2154
- if (clientDataBuf.length > MAX_CBOR_SIZE) return { verified: false, error: "clientDataJSON too large" };
2155
- const clientData = JSON.parse(clientDataBuf.toString());
2156
- if (clientData.type !== "webauthn.create") return { verified: false, error: "Wrong clientData type" };
2157
- if (clientData.challenge !== opts.expectedChallenge) return { verified: false, error: "Challenge mismatch" };
2158
- if (clientData.origin !== opts.expectedOrigin) return { verified: false, error: "Origin mismatch" };
2159
- const attObjBuf = Buffer.from(opts.response.attestationObject, "base64url");
2160
- if (attObjBuf.length > MAX_CBOR_SIZE) return { verified: false, error: "attestationObject too large" };
2161
- const attObj = cborDecode(attObjBuf);
2162
- const authData = parseAuthenticatorData(Buffer.isBuffer(attObj.authData) ? attObj.authData : Buffer.from(attObj.authData));
2163
- const expectedHash = crypto6__namespace.createHash("sha256").update(opts.expectedRpId).digest();
2164
- if (!expectedHash.equals(authData.rpIdHash)) return { verified: false, error: "RP ID hash mismatch" };
2165
- if (!(authData.flags & 1)) return { verified: false, error: "User presence not verified" };
2166
- if (!authData.credentialId || !authData.coseKey) return { verified: false, error: "No credential in authData" };
2167
- const { pem, alg } = coseToPublicKeyPem(authData.coseKey);
2168
- const credential = {
2169
- credentialId: authData.credentialId.toString("base64url"),
2170
- publicKeyPem: pem,
2171
- publicKeyAlg: alg,
2172
- userId: opts.userId,
2173
- userHandle: opts.userId,
2174
- signCount: authData.signCount,
2175
- createdAt: Date.now(),
2176
- lastUsedAt: Date.now(),
2177
- aaguid: authData.aaguid
2178
- };
2179
- return { verified: true, credential };
2180
- } catch (e) {
2181
- return { verified: false, error: e?.message ?? "Verification failed" };
2182
- }
2183
- }
2184
- function generateAuthenticationOptions(opts) {
2185
- if (!opts.rpId) throw new Error("rpId is required");
2186
- const store = opts.challengeStore ?? new ChallengeStore();
2187
- return {
2188
- challenge: store.create(),
2189
- rpId: opts.rpId,
2190
- timeout: 6e4,
2191
- userVerification: opts.userVerification ?? "preferred",
2192
- allowCredentials: opts.allowCredentialIds?.map((id) => ({ type: "public-key", id }))
2193
- };
2194
- }
2195
- async function verifyAuthentication(opts) {
2196
- try {
2197
- if (!opts.response || !opts.expectedChallenge || !opts.expectedOrigin || !opts.expectedRpId || !opts.storedCredential) {
2198
- return { verified: false, error: "Missing required parameters" };
2199
- }
2200
- const { response, storedCredential } = opts;
2201
- const clientDataBuf = Buffer.from(response.clientDataJSON, "base64url");
2202
- if (clientDataBuf.length > MAX_CBOR_SIZE) return { verified: false, error: "clientDataJSON too large" };
2203
- const clientData = JSON.parse(clientDataBuf.toString());
2204
- if (clientData.type !== "webauthn.get") return { verified: false, error: "Wrong clientData type" };
2205
- if (clientData.challenge !== opts.expectedChallenge) return { verified: false, error: "Challenge mismatch" };
2206
- if (clientData.origin !== opts.expectedOrigin) return { verified: false, error: "Origin mismatch" };
2207
- const authDataBuf = Buffer.from(response.authenticatorData, "base64url");
2208
- if (authDataBuf.length > MAX_CBOR_SIZE) return { verified: false, error: "authenticatorData too large" };
2209
- const authData = parseAuthenticatorData(authDataBuf);
2210
- const expectedHash = crypto6__namespace.createHash("sha256").update(opts.expectedRpId).digest();
2211
- if (!expectedHash.equals(authData.rpIdHash)) return { verified: false, error: "RP ID hash mismatch" };
2212
- if (!(authData.flags & 1)) return { verified: false, error: "User presence not verified" };
2213
- if (opts.requireUserVerification && !(authData.flags & 4)) {
2214
- return { verified: false, error: "User verification required but not performed" };
2215
- }
2216
- if (storedCredential.signCount > 0 && authData.signCount <= storedCredential.signCount) {
2217
- return { verified: false, error: `Sign count replay detected: got ${authData.signCount}, expected > ${storedCredential.signCount}` };
2218
- }
2219
- const clientDataHash = crypto6__namespace.createHash("sha256").update(Buffer.from(response.clientDataJSON, "base64url")).digest();
2220
- const signedData = Buffer.concat([authDataBuf, clientDataHash]);
2221
- const sigBuf = Buffer.from(response.signature, "base64url");
2222
- const pubKey = crypto6__namespace.createPublicKey(storedCredential.publicKeyPem);
2223
- const alg = storedCredential.publicKeyAlg === -7 ? "SHA256" : "RSA-SHA256";
2224
- const isValid = crypto6__namespace.verify(alg, signedData, pubKey, sigBuf);
2225
- if (!isValid) return { verified: false, error: "Signature verification failed" };
2226
- const updated = { ...storedCredential, signCount: authData.signCount, lastUsedAt: Date.now() };
2227
- return { verified: true, credential: updated };
2228
- } catch (e) {
2229
- return { verified: false, error: e?.message ?? "Authentication verification failed" };
2230
- }
2231
- }
2232
- function parseDotenv(content) {
2233
- const result = /* @__PURE__ */ Object.create(null);
2234
- if (typeof content !== "string") return result;
2235
- const lines = content.split(/\r?\n/);
2236
- for (const raw of lines) {
2237
- const line = raw.trim();
2238
- if (!line || line.startsWith("#")) continue;
2239
- const eqIdx = line.indexOf("=");
2240
- if (eqIdx === -1) continue;
2241
- const key = line.slice(0, eqIdx).trim();
2242
- let val = line.slice(eqIdx + 1).trim();
2243
- if (!key || key === "__proto__" || key === "constructor" || key === "prototype") continue;
2244
- if (!/^[a-zA-Z_][a-zA-Z0-9_.]*$/.test(key)) continue;
2245
- if (!val.startsWith('"') && !val.startsWith("'")) {
2246
- val = val.split(/\s+#/)[0].trim();
2247
- }
2248
- if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
2249
- val = val.slice(1, -1).replace(/\\n/g, "\n").replace(/\\t/g, " ");
2250
- }
2251
- result[key] = val;
2252
- }
2253
- return result;
2254
- }
2255
- function loadDotenvFile(filePath) {
2256
- const resolved = path__namespace.resolve(filePath);
2257
- if (!fs__namespace.existsSync(resolved)) return /* @__PURE__ */ Object.create(null);
2258
- try {
2259
- const stat = fs__namespace.statSync(resolved);
2260
- if (!stat.isFile() || stat.size > 1 * 1024 * 1024) return /* @__PURE__ */ Object.create(null);
2261
- return parseDotenv(fs__namespace.readFileSync(resolved, "utf-8"));
2262
- } catch {
2263
- return /* @__PURE__ */ Object.create(null);
2264
- }
2265
- }
2266
- function coerce(key, raw, spec) {
2267
- const val = raw.trim();
2268
- if (spec.type === "string") {
2269
- if (spec.minLength !== void 0 && val.length < spec.minLength) throw new Error(`${key}: too short (min ${spec.minLength} chars)`);
2270
- if (spec.maxLength !== void 0 && val.length > spec.maxLength) throw new Error(`${key}: too long (max ${spec.maxLength} chars)`);
2271
- if (spec.enum && !spec.enum.includes(val)) throw new Error(`${key}: must be one of [${spec.enum.join(", ")}]`);
2272
- if (spec.regex && !spec.regex.test(val)) throw new Error(`${key}: does not match expected pattern`);
2273
- return val;
2274
- }
2275
- if (spec.type === "number" || spec.type === "integer") {
2276
- const n = spec.type === "integer" ? parseInt(val, 10) : parseFloat(val);
2277
- if (isNaN(n)) throw new Error(`${key}: expected ${spec.type}`);
2278
- if (!isFinite(n)) throw new Error(`${key}: value must be finite`);
2279
- if (spec.min !== void 0 && n < spec.min) throw new Error(`${key}: too small (min ${spec.min})`);
2280
- if (spec.max !== void 0 && n > spec.max) throw new Error(`${key}: too large (max ${spec.max})`);
2281
- return n;
2282
- }
2283
- if (spec.type === "boolean") {
2284
- if (["true", "1", "yes", "on"].includes(val.toLowerCase())) return true;
2285
- if (["false", "0", "no", "off"].includes(val.toLowerCase())) return false;
2286
- throw new Error(`${key}: expected boolean (true/false/1/0/yes/no)`);
2287
- }
2288
- if (spec.type === "url") {
2289
- try {
2290
- new URL(val);
2291
- return val;
2292
- } catch {
2293
- throw new Error(`${key}: invalid URL`);
2294
- }
2295
- }
2296
- if (spec.type === "email") {
2297
- if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val)) throw new Error(`${key}: invalid email`);
2298
- return val;
2299
- }
2300
- if (spec.type === "json") {
2301
- try {
2302
- return JSON.parse(val);
2303
- } catch {
2304
- throw new Error(`${key}: invalid JSON`);
2305
- }
2306
- }
2307
- if (spec.type === "csv") {
2308
- return val.split(",").map((s) => s.trim()).filter(Boolean);
2309
- }
2310
- return val;
2311
- }
2312
- function parseEnv(schema, source = process.env) {
2313
- const errors = [];
2314
- const result = /* @__PURE__ */ Object.create(null);
2315
- for (const [key, spec] of Object.entries(schema)) {
2316
- if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
2317
- const raw = source[key] ?? spec.default;
2318
- const isRequired = spec.required !== false && spec.default === void 0;
2319
- if (raw === void 0 || raw === "") {
2320
- if (isRequired) {
2321
- const hint = spec.example ? ` (example: ${spec.example})` : "";
2322
- const desc = spec.description ? ` \u2014 ${spec.description}` : "";
2323
- errors.push(` \u2717 ${key}: required but missing${desc}${hint}`);
2324
- }
2325
- continue;
2326
- }
2327
- try {
2328
- result[key] = coerce(key, raw, spec);
2329
- } catch (e) {
2330
- const errorMsg = e.message ?? "validation failed";
2331
- if (spec.secret) {
2332
- errors.push(` \u2717 ${key}: ${errorMsg}`);
2333
- } else {
2334
- errors.push(` \u2717 ${errorMsg} (got: "${raw}")`);
2335
- }
2336
- }
2337
- }
2338
- if (errors.length > 0) {
2339
- const msg = `
2340
- \u{1F6A8} env-safe: ${errors.length} environment variable error(s):
2341
-
2342
- ${errors.join("\n")}
2343
-
2344
- Fix these in your .env file before starting.
2345
- `;
2346
- throw new Error(msg);
2347
- }
2348
- return result;
2349
- }
2350
- function loadEnv(schema, opts) {
2351
- const { envFiles = [".env", ".env.local"], override = false } = opts ?? {};
2352
- const merged = /* @__PURE__ */ Object.create(null);
2353
- for (const file of envFiles) {
2354
- if (typeof file !== "string") continue;
2355
- const fullPath = path__namespace.resolve(process.cwd(), file);
2356
- const parsed = loadDotenvFile(fullPath);
2357
- for (const [k, v] of Object.entries(parsed)) {
2358
- if (k === "__proto__" || k === "constructor" || k === "prototype") continue;
2359
- if (override || !(k in merged)) merged[k] = v;
2360
- }
2361
- }
2362
- const combined = { ...merged };
2363
- for (const [k, v] of Object.entries(process.env)) {
2364
- if (k === "__proto__" || k === "constructor" || k === "prototype") continue;
2365
- if (override || !(k in merged)) combined[k] = v;
2366
- }
2367
- return parseEnv(schema, combined);
2368
- }
2369
- function generateExample(schema) {
2370
- const lines = ["# Generated by devguard env-safe", ""];
2371
- for (const [key, spec] of Object.entries(schema)) {
2372
- if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
2373
- if (spec.description) lines.push(`# ${spec.description}`);
2374
- const hint = spec.example ?? (spec.enum ? spec.enum[0] : spec.type.toUpperCase());
2375
- const req = spec.required !== false && !spec.default ? " (required)" : spec.default ? ` (default: ${spec.default})` : "";
2376
- lines.push(`# Type: ${spec.type}${req}`);
2377
- lines.push(`${key}=${hint}`);
2378
- lines.push("");
2379
- }
2380
- return lines.join("\n");
2381
- }
2382
- var LEVEL_NUM = { trace: 10, debug: 20, info: 30, warn: 40, error: 50, fatal: 60 };
2383
- var LEVEL_COLOR = { trace: "\x1B[90m", debug: "\x1B[36m", info: "\x1B[32m", warn: "\x1B[33m", error: "\x1B[31m", fatal: "\x1B[35m" };
2384
- var RESET = "\x1B[0m";
2385
- var PROTECTED_FIELDS = /* @__PURE__ */ new Set(["level", "levelNum", "msg", "time", "pid", "hostname"]);
2386
- function redactValue(val) {
2387
- if (typeof val === "string" && val.length > 4) return val.slice(0, 2) + "****";
2388
- return "****";
2389
- }
2390
- function safeStringify(obj) {
2391
- const seen = /* @__PURE__ */ new WeakSet();
2392
- try {
2393
- return JSON.stringify(obj, (_key, value) => {
2394
- if (typeof value === "object" && value !== null) {
2395
- if (seen.has(value)) return "[Circular]";
2396
- seen.add(value);
2397
- }
2398
- if (typeof value === "bigint") return value.toString();
2399
- return value;
2400
- });
2401
- } catch {
2402
- return '"[Unserializable]"';
2403
- }
2404
- }
2405
- var OTLPExporter = class {
2406
- constructor(cfg) {
2407
- this.batch = [];
2408
- this._warnCount = 0;
2409
- this._isFlushing = false;
2410
- this.timer = null;
2411
- this.cfg = { headers: {}, batchSize: 100, flushIntervalMs: 5e3, ...cfg };
2412
- try {
2413
- new URL(this.cfg.endpoint);
2414
- } catch {
2415
- throw new Error(`Invalid OTLP endpoint URL: ${cfg.endpoint}`);
2416
- }
2417
- this.timer = setInterval(() => this.flush(), this.cfg.flushIntervalMs);
2418
- if (this.timer.unref) this.timer.unref();
2419
- }
2420
- push(record) {
2421
- this.batch.push(record);
2422
- if (this.batch.length >= this.cfg.batchSize) this.flush();
2423
- }
2424
- flush() {
2425
- if (this.batch.length === 0) return;
2426
- if (this._isFlushing) return;
2427
- this._isFlushing = true;
2428
- const records = this.batch.splice(0);
2429
- try {
2430
- const body = JSON.stringify({ resourceLogs: [{ resource: {}, scopeLogs: [{ logRecords: records.map((r) => ({
2431
- timeUnixNano: String(Date.parse(r.time) * 1e6),
2432
- severityNumber: r.levelNum,
2433
- severityText: r.level.toUpperCase(),
2434
- body: { stringValue: r.msg },
2435
- attributes: Object.entries(r).filter(([k]) => !PROTECTED_FIELDS.has(k)).map(([k, v]) => ({ key: k, value: { stringValue: String(v) } }))
2436
- })) }] }] });
2437
- const url = new URL(this.cfg.endpoint);
2438
- const lib = url.protocol === "https:" ? https__namespace : http__namespace;
2439
- const req = lib.request({
2440
- hostname: url.hostname,
2441
- port: url.port,
2442
- path: url.pathname,
2443
- method: "POST",
2444
- headers: { "Content-Type": "application/json", ...this.cfg.headers }
2445
- });
2446
- req.on("error", (e) => {
2447
- if (this._warnCount++ < 3) {
2448
- process.stderr.write(`[devguard/log-otlp] OTLP export failed: ${e.message} (further errors suppressed)
2449
- `);
2450
- }
2451
- });
2452
- req.write(body);
2453
- req.end();
2454
- } finally {
2455
- this._isFlushing = false;
2456
- }
2457
- }
2458
- destroy() {
2459
- if (this.timer) clearInterval(this.timer);
2460
- this.timer = null;
2461
- this.flush();
2462
- }
2463
- };
2464
- var _hostname = os__namespace.hostname();
2465
- var Logger = class _Logger {
2466
- constructor(opts, bindings = {}) {
2467
- this.opts = opts;
2468
- this.minLevel = LEVEL_NUM[opts.level ?? "info"];
2469
- this.redactSet = new Set(opts.redact ?? []);
2470
- this.exporter = opts.otlp ? new OTLPExporter(opts.otlp) : null;
2471
- this.dest = opts.destination ?? process.stdout;
2472
- this.bindings = bindings;
2473
- }
2474
- log(level, msg, data) {
2475
- if (LEVEL_NUM[level] < this.minLevel) return;
2476
- if (this.opts.output === "silent") return;
2477
- let traceCtx = {};
2478
- try {
2479
- traceCtx = this.opts.traceContext?.() ?? {};
2480
- } catch {
2481
- }
2482
- const record = {
2483
- level,
2484
- levelNum: LEVEL_NUM[level],
2485
- msg,
2486
- time: (/* @__PURE__ */ new Date()).toISOString(),
2487
- pid: process.pid,
2488
- hostname: _hostname,
2489
- ...this.bindings,
2490
- ...traceCtx,
2491
- ...this.opts.service ? { service: this.opts.service } : {}
2492
- };
2493
- if (data) {
2494
- for (const [k, v] of Object.entries(data)) {
2495
- if (PROTECTED_FIELDS.has(k)) continue;
2496
- if (k === "__proto__" || k === "constructor" || k === "prototype") continue;
2497
- record[k] = this.redactSet.has(k) ? redactValue(v) : v;
2498
- }
2499
- }
2500
- const output = this.opts.output ?? (process.env.NODE_ENV === "development" || process.stdout.isTTY ? "pretty" : "json");
2501
- if (output === "pretty") {
2502
- const color = LEVEL_COLOR[level];
2503
- const ts = record.time.slice(11, 23);
2504
- const extras = Object.entries(record).filter(([k]) => !PROTECTED_FIELDS.has(k));
2505
- const extraStr = extras.length ? " " + extras.map(([k, v]) => `${k}=${safeStringify(v)}`).join(" ") : "";
2506
- this.dest.write(`${color}${ts} ${level.toUpperCase().padEnd(5)}${RESET} ${msg}${extraStr}
2507
- `);
2508
- } else {
2509
- this.dest.write(safeStringify(record) + "\n");
2510
- }
2511
- this.exporter?.push(record);
2512
- }
2513
- trace(msg, data) {
2514
- this.log("trace", msg, data);
2515
- }
2516
- debug(msg, data) {
2517
- this.log("debug", msg, data);
2518
- }
2519
- info(msg, data) {
2520
- this.log("info", msg, data);
2521
- }
2522
- warn(msg, data) {
2523
- this.log("warn", msg, data);
2524
- }
2525
- error(msg, data) {
2526
- this.log("error", msg, data);
2527
- }
2528
- fatal(msg, data) {
2529
- this.log("fatal", msg, data);
2530
- }
2531
- /** Create a child logger with additional bound fields */
2532
- child(bindings) {
2533
- return new _Logger(this.opts, { ...this.bindings, ...bindings });
2534
- }
2535
- /** Attach trace context for automatic injection */
2536
- withTrace(ctx) {
2537
- return new _Logger({ ...this.opts, traceContext: () => ctx }, this.bindings);
2538
- }
2539
- /** Flush OTLP buffer before process exit */
2540
- flush() {
2541
- this.exporter?.flush();
2542
- }
2543
- destroy() {
2544
- this.exporter?.destroy();
2545
- }
2546
- /** Middleware: log all HTTP requests */
2547
- requestMiddleware() {
2548
- const log = this;
2549
- return function logRequest(req, res, next) {
2550
- const start = Date.now();
2551
- res.on("finish", () => {
2552
- log.info("http request", {
2553
- method: req.method,
2554
- url: req.url,
2555
- status: res.statusCode,
2556
- durationMs: Date.now() - start,
2557
- ip: req.ip ?? req.connection?.remoteAddress,
2558
- userAgent: req.headers["user-agent"]
2559
- });
2560
- });
2561
- next();
2562
- };
2563
- }
2564
- };
2565
- function createLogger(opts = {}) {
2566
- return new Logger(opts);
2567
- }
2568
- async function runAllChecks(root = process.cwd()) {
2569
- const resolvedRoot = path__namespace.resolve(root);
2570
- const [lockfile, hookFindings, unpinned] = await Promise.all([
2571
- Promise.resolve(verifyLockfile(resolvedRoot)),
2572
- Promise.resolve(scanProject(resolvedRoot)),
2573
- Promise.resolve(enforceExactPins(path__namespace.join(resolvedRoot, "package.json")))
2574
- ]);
2575
- const tokenNames = (process.env.DEVGUARD_TOKENS ?? "NPM_TOKEN,GITHUB_TOKEN").split(",").map((s) => s.trim()).filter(Boolean);
2576
- const tokenAlerts = checkTokenAge(loadTokensFromEnv(tokenNames));
2577
- const stale = tokenAlerts.filter((a) => a.status === "stale").map((a) => a.name);
2578
- const expiringSoon = tokenAlerts.filter((a) => a.status === "expiring_soon").map((a) => a.name);
2579
- const criticals = hookFindings.filter((h) => h.severity === "critical").length;
2580
- const passedAll = lockfile.valid && hookFindings.length === 0 && unpinned.length === 0 && stale.length === 0;
2581
- let score = 100;
2582
- if (!lockfile.valid) score -= 30;
2583
- score -= Math.min(40, criticals * 15 + (hookFindings.length - criticals) * 5);
2584
- score -= Math.min(15, unpinned.length * 2);
2585
- score -= stale.length * 10;
2586
- score -= expiringSoon.length * 3;
2587
- score = Math.max(0, score);
2588
- return {
2589
- lockfile: { valid: lockfile.valid, tampered: lockfile.tampered, missing: lockfile.missing },
2590
- hooks: { count: hookFindings.length, criticals, findings: hookFindings },
2591
- pins: { unpinned: unpinned.map((v) => v.name + "@" + v.specifier), autoFixAvailable: true },
2592
- tokens: { stale, expiringSoon },
2593
- passedAll,
2594
- score,
2595
- scannedAt: (/* @__PURE__ */ new Date()).toISOString()
2596
- };
2597
- }
2598
-
2599
- exports.AgentMemory = AgentMemory;
2600
- exports.ChallengeStore = ChallengeStore;
2601
- exports.CredentialStore = CredentialStore;
2602
- exports.FileSystemAdapter = FileSystemAdapter;
2603
- exports.IPRateLimiter = IPRateLimiter;
2604
- exports.JWTVerifier = JWTVerifier;
2605
- exports.LLMBudget = LLMBudget;
2606
- exports.Logger = Logger;
2607
- exports.MCPServerBuilder = MCPServerBuilder;
2608
- exports.RedisAdapter = RedisAdapter;
2609
- exports.RevocationList = RevocationList;
2610
- exports.autoPin = autoPin;
2611
- exports.c = c;
2612
- exports.checkTokenAge = checkTokenAge;
2613
- exports.cleanLLMOutput = cleanLLMOutput;
2614
- exports.createLogger = createLogger;
2615
- exports.createMiddleware = createMiddleware;
2616
- exports.createNpmToken = createNpmToken;
2617
- exports.createSnapshot = createSnapshot;
2618
- exports.decodeJWT = decodeJWT;
2619
- exports.detectAnomalies = detectAnomalies;
2620
- exports.enforceExactPins = enforceExactPins;
2621
- exports.extractLockfileSRI = extractLockfileSRI;
2622
- exports.extractTransitiveDeps = extractTransitiveDeps;
2623
- exports.fetchJWKS = fetchJWKS;
2624
- exports.fetchRegistrySRI = fetchRegistrySRI;
2625
- exports.generateAuthenticationOptions = generateAuthenticationOptions;
2626
- exports.generateExample = generateExample;
2627
- exports.generateRegistrationOptions = generateRegistrationOptions;
2628
- exports.hashFile = hashFile;
2629
- exports.inspectGitHubToken = inspectGitHubToken;
2630
- exports.inspectNpmToken = inspectNpmToken;
2631
- exports.jwksCacheUtils = jwksCacheUtils;
2632
- exports.loadDotenvFile = loadDotenvFile;
2633
- exports.loadEnv = loadEnv;
2634
- exports.loadTokensFromEnv = loadTokensFromEnv;
2635
- exports.maskToken = maskToken;
2636
- exports.parseDotenv = parseDotenv;
2637
- exports.parseEnv = parseEnv;
2638
- exports.parseSchema = parseSchema;
2639
- exports.parseWithRetry = parseWithRetry;
2640
- exports.runAllChecks = runAllChecks;
2641
- exports.s = c;
2642
- exports.scanNodeModules = scanNodeModules;
2643
- exports.scanPackage = scanPackage;
2644
- exports.scanProject = scanProject;
2645
- exports.scoreRequest = scoreRequest;
2646
- exports.signHMAC = signHMAC;
2647
- exports.typedFetch = typedFetch;
2648
- exports.validateBody = validateBody;
2649
- exports.validateQuery = validateQuery;
2650
- exports.verifyAuthentication = verifyAuthentication;
2651
- exports.verifyHMAC = verifyHMAC;
2652
- exports.verifyLockfile = verifyLockfile;
2653
- exports.verifyRS256 = verifyRS256;
2654
- exports.verifyRegistration = verifyRegistration;
2655
- exports.verifySRI = verifySRI;