fullshield 0.4.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,1260 +1,5 @@
1
- import dns from 'dns';
2
- import { createRequire } from 'module';
3
- import { AsyncLocalStorage } from 'async_hooks';
4
-
5
- // src/runtime/rasp/nosql.ts
6
- function isOperatorObject(value) {
7
- if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
8
- return Object.keys(value).some((k) => k.startsWith("$"));
9
- }
10
- function deepEqual(a, b) {
11
- if (a === b) return true;
12
- if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
13
- if (Array.isArray(a) || Array.isArray(b)) {
14
- if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
15
- return a.every((v, i) => deepEqual(v, b[i]));
16
- }
17
- const ak = Object.keys(a);
18
- const bk = Object.keys(b);
19
- if (ak.length !== bk.length) return false;
20
- return ak.every((k) => deepEqual(a[k], b[k]));
21
- }
22
- function collect(value, out, depth) {
23
- if (depth > 8 || value === null || typeof value !== "object") return;
24
- out.push(value);
25
- if (Array.isArray(value)) {
26
- for (const item of value) collect(item, out, depth + 1);
27
- return;
28
- }
29
- for (const child of Object.values(value)) collect(child, out, depth + 1);
30
- }
31
- function nosqlUserOperators(sink, ctx) {
32
- const userObjects = [];
33
- for (const obj of ctx.objects) collect(obj, userObjects, 0);
34
- const userOps = userObjects.filter(isOperatorObject);
35
- if (userOps.length === 0) return false;
36
- const seen = /* @__PURE__ */ new Set();
37
- const walk2 = (value, depth) => {
38
- if (depth > 8 || value === null || typeof value !== "object") return false;
39
- if (seen.has(value)) return false;
40
- seen.add(value);
41
- if (isOperatorObject(value) && userOps.some((u) => u === value || deepEqual(u, value))) {
42
- return true;
43
- }
44
- if (Array.isArray(value)) return value.some((item) => walk2(item, depth + 1));
45
- return Object.values(value).some((child) => walk2(child, depth + 1));
46
- };
47
- return walk2(sink, 0);
48
- }
49
-
50
- // src/runtime/rasp/path.ts
51
- function logicalResolve(p) {
52
- const isAbs = p.startsWith("/") || /^[A-Za-z]:[\\/]/.test(p);
53
- const parts = [];
54
- for (const seg of p.split(/[\\/]+/)) {
55
- if (seg === "" || seg === ".") continue;
56
- if (seg === "..") {
57
- parts.pop();
58
- continue;
59
- }
60
- parts.push(seg);
61
- }
62
- return (isAbs ? "/" : "") + parts.join("/");
63
- }
64
- function isInside(base, target) {
65
- const b = logicalResolve(base).replace(/\/+$/, "");
66
- const t = logicalResolve(target);
67
- if (b === "") return !t.startsWith("/") && !t.split("/").includes("..");
68
- return t === b || t.startsWith(`${b}/`);
69
- }
70
- function inferBase(sink, input) {
71
- const idx = sink.indexOf(input);
72
- if (idx <= 0) return "";
73
- return sink.slice(0, idx);
74
- }
75
- function lastSeg(p) {
76
- const parts = logicalResolve(p).split("/").filter(Boolean);
77
- return parts[parts.length - 1];
78
- }
79
- function pathInputEscapes(sink, input, base) {
80
- if (!input) return false;
81
- if (input.includes("\0") || sink.includes("\0")) return true;
82
- if (sink.includes(input)) {
83
- if (input.includes("..")) return true;
84
- const intended = base !== void 0 && base.length > 0 ? base : inferBase(sink, input);
85
- if (intended === "") {
86
- return input.startsWith("/") || /^[A-Za-z]:[\\/]/.test(input) || input.includes("..");
87
- }
88
- return !isInside(intended, sink);
89
- }
90
- if (input.includes("..") || input.startsWith("/") || /^[A-Za-z]:[\\/]/.test(input)) {
91
- const a = lastSeg(input);
92
- const b = lastSeg(sink);
93
- if (a && a === b) return true;
94
- }
95
- return false;
96
- }
97
-
98
- // src/runtime/rasp/proto.ts
99
- var DANGEROUS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
100
- function walk(value, depth, seen) {
101
- if (depth > 8 || value === null || typeof value !== "object") return false;
102
- if (seen.has(value)) return false;
103
- seen.add(value);
104
- for (const key of Object.getOwnPropertyNames(value)) {
105
- if (DANGEROUS.has(key)) return true;
106
- }
107
- if (Array.isArray(value)) return value.some((item) => walk(item, depth + 1, seen));
108
- return Object.values(value).some((child) => walk(child, depth + 1, seen));
109
- }
110
- function protoPolluted(ctx) {
111
- const seen = /* @__PURE__ */ new Set();
112
- return ctx.objects.some((obj) => walk(obj, 0, seen));
113
- }
114
-
115
- // src/runtime/rasp/shell.ts
116
- function tokenizeShell(command) {
117
- const tokens = [];
118
- let i = 0;
119
- const n = command.length;
120
- while (i < n) {
121
- const c = command[i];
122
- if (c === " " || c === " " || c === "\n") {
123
- const start2 = i;
124
- while (i < n && (command[i] === " " || command[i] === " " || command[i] === "\n")) i += 1;
125
- tokens.push({ kind: "space", start: start2, end: i });
126
- continue;
127
- }
128
- if (c === "'" || c === '"') {
129
- const quote = c;
130
- const start2 = i;
131
- i += 1;
132
- while (i < n && command[i] !== quote) {
133
- if (quote === '"' && command[i] === "\\") i += 1;
134
- i += 1;
135
- }
136
- if (i < n) i += 1;
137
- tokens.push({ kind: "word", start: start2, end: i });
138
- continue;
139
- }
140
- if (c === "`") {
141
- const start2 = i;
142
- i += 1;
143
- while (i < n && command[i] !== "`") i += 1;
144
- if (i < n) i += 1;
145
- tokens.push({ kind: "op", start: start2, end: i });
146
- continue;
147
- }
148
- if (c === "$" && (command[i + 1] === "(" || command[i + 1] === "{")) {
149
- const open = command[i + 1];
150
- const close = open === "(" ? ")" : "}";
151
- const start2 = i;
152
- i += 2;
153
- let depth = 1;
154
- while (i < n && depth > 0) {
155
- if (command[i] === open) depth += 1;
156
- else if (command[i] === close) depth -= 1;
157
- i += 1;
158
- }
159
- tokens.push({ kind: "op", start: start2, end: i });
160
- continue;
161
- }
162
- if (command.startsWith("||", i) || command.startsWith("&&", i) || command.startsWith(">>", i) || command.startsWith("<<", i)) {
163
- tokens.push({ kind: "op", start: i, end: i + 2 });
164
- i += 2;
165
- continue;
166
- }
167
- if (";|&<>()".includes(c)) {
168
- tokens.push({ kind: "op", start: i, end: i + 1 });
169
- i += 1;
170
- continue;
171
- }
172
- const start = i;
173
- while (i < n && !/[\s;|&$`<>()]/.test(command[i])) i += 1;
174
- tokens.push({ kind: "word", start, end: i });
175
- }
176
- return tokens;
177
- }
178
- function shellInputChangesStructure(command, input) {
179
- if (!input) return false;
180
- const tokens = tokenizeShell(command);
181
- let from = 0;
182
- while (from <= command.length) {
183
- const idx = command.indexOf(input, from);
184
- if (idx === -1) return false;
185
- const end = idx + input.length;
186
- const hit = tokens.filter((t) => t.kind !== "space" && t.start < end && t.end > idx);
187
- if (!(hit.length === 1 && hit[0].kind === "word" && hit[0].start <= idx && hit[0].end >= end)) {
188
- return true;
189
- }
190
- from = idx + Math.max(input.length, 1);
191
- }
192
- return false;
193
- }
194
-
195
- // src/runtime/rasp/sql.ts
196
- function tokenizeSql(sql) {
197
- const tokens = [];
198
- let i = 0;
199
- const n = sql.length;
200
- while (i < n) {
201
- const c = sql[i];
202
- if (c <= " ") {
203
- const start2 = i;
204
- while (i < n && sql[i] <= " ") i += 1;
205
- tokens.push({ kind: "space", start: start2, end: i });
206
- continue;
207
- }
208
- if (c === "'" || c === '"' || c === "`") {
209
- const quote = c;
210
- const start2 = i;
211
- i += 1;
212
- while (i < n) {
213
- if (sql[i] === quote) {
214
- if (sql[i + 1] === quote) {
215
- i += 2;
216
- continue;
217
- }
218
- i += 1;
219
- break;
220
- }
221
- i += 1;
222
- }
223
- tokens.push({ kind: "string", start: start2, end: i });
224
- continue;
225
- }
226
- if (c === "-" && sql[i + 1] === "-") {
227
- const start2 = i;
228
- i += 2;
229
- while (i < n && sql[i] !== "\n") i += 1;
230
- tokens.push({ kind: "comment", start: start2, end: i });
231
- continue;
232
- }
233
- if (c === "/" && sql[i + 1] === "*") {
234
- const start2 = i;
235
- i += 2;
236
- while (i < n && !(sql[i] === "*" && sql[i + 1] === "/")) i += 1;
237
- if (i + 1 < n) i += 2;
238
- tokens.push({ kind: "comment", start: start2, end: i });
239
- continue;
240
- }
241
- if (c === "#") {
242
- const start2 = i;
243
- while (i < n && sql[i] !== "\n") i += 1;
244
- tokens.push({ kind: "comment", start: start2, end: i });
245
- continue;
246
- }
247
- if (c >= "0" && c <= "9" || c === "." && i + 1 < n && sql[i + 1] >= "0" && sql[i + 1] <= "9") {
248
- const start2 = i;
249
- while (i < n && /[0-9.eE+-]/.test(sql[i])) i += 1;
250
- tokens.push({ kind: "number", start: start2, end: i });
251
- continue;
252
- }
253
- if (/[A-Za-z_]/.test(c)) {
254
- const start2 = i;
255
- while (i < n && /[A-Za-z0-9_$]/.test(sql[i])) i += 1;
256
- tokens.push({ kind: "ident", start: start2, end: i });
257
- continue;
258
- }
259
- const start = i;
260
- if (sql.startsWith("<=", i) || sql.startsWith(">=", i) || sql.startsWith("<>", i) || sql.startsWith("!=", i) || sql.startsWith("||", i) || sql.startsWith("&&", i)) {
261
- i += 2;
262
- } else {
263
- i += 1;
264
- }
265
- tokens.push({ kind: "op", start, end: i });
266
- }
267
- return tokens;
268
- }
269
- function spanUnsafe(tokens, start, end) {
270
- const hit = tokens.filter((t) => t.kind !== "space" && t.start < end && t.end > start);
271
- if (hit.length === 1) {
272
- const t = hit[0];
273
- const safe = t.kind === "string" || t.kind === "number" || t.kind === "comment";
274
- return !safe || t.start > start || t.end < end;
275
- }
276
- return hit.length > 0;
277
- }
278
- function sqlInputChangesStructure(query, input) {
279
- if (!input) return false;
280
- const tokens = tokenizeSql(query);
281
- let from = 0;
282
- while (from <= query.length) {
283
- const idx = query.indexOf(input, from);
284
- if (idx === -1) return false;
285
- if (spanUnsafe(tokens, idx, idx + input.length)) return true;
286
- from = idx + Math.max(input.length, 1);
287
- }
288
- return false;
289
- }
290
- function normalizeHost(host) {
291
- let h = host.trim().toLowerCase();
292
- h = h.replace(/\.+$/, "");
293
- if (h.startsWith("[") && h.endsWith("]")) h = h.slice(1, -1);
294
- if (h.startsWith("::ffff:")) h = h.slice(7);
295
- if (h.startsWith("[") && h.endsWith("]")) h = h.slice(1, -1);
296
- return h;
297
- }
298
- function parseIpv4Part(part) {
299
- if (/^0x[0-9a-f]+$/i.test(part)) {
300
- const n = Number.parseInt(part, 16);
301
- return Number.isFinite(n) ? n : void 0;
302
- }
303
- if (/^0[0-7]+$/.test(part)) {
304
- const n = Number.parseInt(part, 8);
305
- return Number.isFinite(n) ? n : void 0;
306
- }
307
- if (/^\d+$/.test(part)) {
308
- const n = Number(part);
309
- return Number.isFinite(n) ? n : void 0;
310
- }
311
- return void 0;
312
- }
313
- function parseIpv4(host) {
314
- const h = normalizeHost(host);
315
- if (!h || h.includes(":")) return void 0;
316
- const parts = h.split(".");
317
- if (parts.length === 0 || parts.length > 4) return void 0;
318
- const nums = parts.map(parseIpv4Part);
319
- if (nums.some((n) => n === void 0)) return void 0;
320
- const a = nums[0];
321
- if (parts.length === 1) {
322
- if (a > 4294967295) return void 0;
323
- return [a >>> 24 & 255, a >>> 16 & 255, a >>> 8 & 255, a & 255];
324
- }
325
- if (parts.length === 2) {
326
- const b2 = nums[1];
327
- if (a > 255 || b2 > 16777215) return void 0;
328
- return [a, b2 >>> 16 & 255, b2 >>> 8 & 255, b2 & 255];
329
- }
330
- if (parts.length === 3) {
331
- const b2 = nums[1];
332
- const c2 = nums[2];
333
- if (a > 255 || b2 > 255 || c2 > 65535) return void 0;
334
- return [a, b2, c2 >>> 8 & 255, c2 & 255];
335
- }
336
- const b = nums[1];
337
- const c = nums[2];
338
- const d = nums[3];
339
- if ([a, b, c, d].some((n) => n > 255)) return void 0;
340
- return [a, b, c, d];
341
- }
342
- function isPrivateV4(octets) {
343
- const [a, b] = octets;
344
- if (a === 127 || a === 0 || a === 10) return true;
345
- if (a === 169 && b === 254) return true;
346
- if (a === 192 && b === 168) return true;
347
- if (a === 172 && b >= 16 && b <= 31) return true;
348
- return false;
349
- }
350
- function isPrivateIpv6(h) {
351
- if (h === "::1" || h === "::" || h === "0:0:0:0:0:0:0:1") return true;
352
- if (h.startsWith("fc") || h.startsWith("fd") || h.startsWith("fe80:")) return true;
353
- return false;
354
- }
355
- function isPrivateOrMetadata(host) {
356
- const h = normalizeHost(host);
357
- if (h === "localhost" || h.endsWith(".localhost") || h === "metadata.google.internal") return true;
358
- const v4 = parseIpv4(h);
359
- if (v4) return isPrivateV4(v4);
360
- if (h.includes(":")) return isPrivateIpv6(h);
361
- return false;
362
- }
363
- function defaultLookup(host) {
364
- const h = normalizeHost(host);
365
- if (parseIpv4(h) || h.includes(":")) return [h];
366
- try {
367
- const lookupSync = dns.lookupSync;
368
- if (typeof lookupSync === "function") {
369
- return lookupSync(h, { all: true }).map((row) => row.address);
370
- }
371
- } catch {
372
- }
373
- return [];
374
- }
375
- function resolveHostAddresses(host) {
376
- try {
377
- return defaultLookup(host);
378
- } catch {
379
- return [];
380
- }
381
- }
382
- function hostIsBlocked(host) {
383
- if (isPrivateOrMetadata(host)) return true;
384
- for (const addr of resolveHostAddresses(host)) {
385
- if (isPrivateOrMetadata(addr)) return true;
386
- }
387
- return false;
388
- }
389
- function ssrfHostFromUser(url, input) {
390
- if (!input || !url.includes(input)) return false;
391
- let parsed;
392
- try {
393
- parsed = new URL(url);
394
- } catch {
395
- return false;
396
- }
397
- const host = parsed.hostname;
398
- if (!host) return false;
399
- const rawAuth = /^https?:\/\/([^/?#]+)/i.exec(url)?.[1] ?? host;
400
- const rawHost = rawAuth.replace(/:\d+$/, "");
401
- const inputTouchesHost = input === url || input.includes(host) || input.includes(rawHost) || rawHost.includes(input) && input.length >= 3;
402
- if (!inputTouchesHost) return false;
403
- return hostIsBlocked(host) || hostIsBlocked(rawHost);
404
- }
405
- function urlFromHttpArgs(args) {
406
- const first = args[0];
407
- if (typeof first === "string") return first;
408
- if (first instanceof URL) return first.href;
409
- if (first && typeof first === "object") {
410
- const o = first;
411
- if (typeof o.href === "string") return o.href;
412
- const proto = o.protocol ?? "http:";
413
- const host = o.hostname ?? o.host ?? "";
414
- const path = o.path ?? "/";
415
- return `${proto}//${host}${path}`;
416
- }
417
- return "";
418
- }
419
- function fetchUrl(input) {
420
- if (typeof input === "string") return input;
421
- if (input instanceof URL) return input.href;
422
- if (input && typeof input === "object" && "url" in input && typeof input.url === "string") {
423
- return input.url;
424
- }
425
- return "";
426
- }
427
-
428
- // src/runtime/rasp/types.ts
429
- var RASP_RULE = {
430
- sql: "RASP-SQL-001",
431
- shell: "RASP-SHELL-001",
432
- path: "RASP-PATH-001",
433
- nosql: "RASP-NOSQL-001",
434
- ssrf: "RASP-SSRF-001",
435
- proto: "RASP-PROTO-001"
436
- };
437
- var RASP_FAIL_OPEN = "RASP-FAIL-OPEN-001";
438
- var RaspBlockedError = class extends Error {
439
- ruleId;
440
- constructor(ruleId) {
441
- super(`fullshield rasp blocked ${ruleId}`);
442
- this.name = "RaspBlockedError";
443
- this.ruleId = ruleId;
444
- }
445
- };
446
-
447
- // src/runtime/rasp/decide.ts
448
- function sinkTexts(sink) {
449
- if (typeof sink === "string") return [sink];
450
- if (typeof sink === "number" || typeof sink === "boolean") return [String(sink)];
451
- return [];
452
- }
453
- function whereJsChangesStructure(sink, ctx) {
454
- let code = "";
455
- if (typeof sink === "string") code = sink;
456
- else if (sink !== null && typeof sink === "object" && !Array.isArray(sink) && "$where" in sink) {
457
- const where = sink.$where;
458
- if (typeof where === "string") code = where;
459
- }
460
- if (!code) return false;
461
- for (const input of ctx.strings) {
462
- if (input && sqlInputChangesStructure(code, input)) return true;
463
- }
464
- return false;
465
- }
466
- function detectStructureChange(kind, sink, ctx, base) {
467
- if (kind === "proto") return protoPolluted(ctx);
468
- if (kind === "nosql") {
469
- if (nosqlUserOperators(sink, ctx)) return true;
470
- return whereJsChangesStructure(sink, ctx);
471
- }
472
- for (const text of sinkTexts(sink)) {
473
- for (const input of ctx.strings) {
474
- if (!input) continue;
475
- if (kind === "sql" && sqlInputChangesStructure(text, input)) return true;
476
- if (kind === "shell" && shellInputChangesStructure(text, input)) return true;
477
- if (kind === "path" && pathInputEscapes(text, input, base)) return true;
478
- if (kind === "ssrf" && ssrfHostFromUser(text, input)) return true;
479
- }
480
- }
481
- return false;
482
- }
483
- function decideSink(kind, sink, ctx, base) {
484
- const hit = detectStructureChange(kind, sink, ctx, base);
485
- if (hit) return { block: true, ruleId: RASP_RULE[kind] };
486
- return { block: false };
487
- }
488
- var SKIP_HEADERS = /* @__PURE__ */ new Set([
489
- "host",
490
- "connection",
491
- "user-agent",
492
- "accept",
493
- "accept-encoding",
494
- "accept-language",
495
- "content-type",
496
- "content-length",
497
- "transfer-encoding",
498
- "cookie"
499
- ]);
500
- var storage = new AsyncLocalStorage();
501
- function flatten(value, out, depth) {
502
- if (depth > 8 || out.length > 200) return;
503
- if (typeof value === "string") {
504
- out.push(value);
505
- return;
506
- }
507
- if (typeof value === "number" || typeof value === "boolean") {
508
- out.push(String(value));
509
- return;
510
- }
511
- if (Array.isArray(value)) {
512
- for (const item of value) flatten(item, out, depth + 1);
513
- return;
514
- }
515
- if (value !== null && typeof value === "object") {
516
- for (const child of Object.values(value)) flatten(child, out, depth + 1);
517
- }
518
- }
519
- function collectObjects(value, out, depth) {
520
- if (depth > 8 || value === null || typeof value !== "object") return;
521
- out.push(value);
522
- if (Array.isArray(value)) {
523
- for (const item of value) collectObjects(item, out, depth + 1);
524
- return;
525
- }
526
- for (const child of Object.values(value)) collectObjects(child, out, depth + 1);
527
- }
528
- function contextFromRequest(req) {
529
- const strings = [];
530
- const objects = [];
531
- for (const value of [req.query, req.body, req.params, req.cookies]) {
532
- flatten(value, strings, 0);
533
- collectObjects(value, objects, 0);
534
- }
535
- if (req.headers && typeof req.headers === "object") {
536
- for (const [key, value] of Object.entries(req.headers)) {
537
- if (SKIP_HEADERS.has(key.toLowerCase())) continue;
538
- flatten(value, strings, 0);
539
- }
540
- collectObjects(req.headers, objects, 0);
541
- }
542
- return {
543
- path: req.path ?? req.url?.split("?")[0] ?? "/",
544
- ip: req.ip ?? "unknown",
545
- strings,
546
- objects
547
- };
548
- }
549
- function enterRaspContext(ctx, fn) {
550
- return storage.run(ctx, fn);
551
- }
552
- function currentRaspContext() {
553
- return storage.getStore();
554
- }
555
-
556
- // src/runtime/rasp/egress.ts
557
- function egressAllowed(host, allow) {
558
- const h = host.toLowerCase().replace(/\.+$/, "");
559
- for (const rule of allow) {
560
- const r = rule.toLowerCase();
561
- if (r.startsWith("*.")) {
562
- const suffix = r.slice(1);
563
- if (h.endsWith(suffix) && h !== r.slice(2)) return true;
564
- } else if (h === r) {
565
- return true;
566
- }
567
- }
568
- return false;
569
- }
570
-
571
- // src/runtime/rasp/threat.ts
572
- var scores = /* @__PURE__ */ new Map();
573
- var blockUntil = /* @__PURE__ */ new Map();
574
- var WEIGHT = {
575
- block: 2,
576
- honey: 5,
577
- anomaly: 1
578
- };
579
- var DEFAULT_MAX_BLOCK_MS = 15 * 60 * 1e3;
580
- var autoBlock = false;
581
- var maxBlockMs = DEFAULT_MAX_BLOCK_MS;
582
- function configureThreat(opts = {}) {
583
- autoBlock = opts.autoBlock === true;
584
- if (typeof opts.maxBlockMs === "number" && opts.maxBlockMs > 0) maxBlockMs = opts.maxBlockMs;
585
- }
586
- function addThreat(ip, kind, now = Date.now()) {
587
- const next = (scores.get(ip) ?? 0) + WEIGHT[kind];
588
- scores.set(ip, next);
589
- if (autoBlock && next >= 10) {
590
- blockUntil.set(ip, now + maxBlockMs);
591
- }
592
- return next;
593
- }
594
- function isIpBlocked(ip, now = Date.now()) {
595
- if (!autoBlock) return false;
596
- const until = blockUntil.get(ip);
597
- if (until === void 0) return false;
598
- if (now >= until) {
599
- blockUntil.delete(ip);
600
- return false;
601
- }
602
- return true;
603
- }
604
-
605
- // src/runtime/rasp/honey.ts
606
- var DECOY_ROUTES = ["/admin.php", "/.git/config", "/wp-login.php"];
607
- var registered = /* @__PURE__ */ new Set();
608
- var ROUTE_WINDOW_MS = 6e4;
609
- var routeHits = /* @__PURE__ */ new Map();
610
- function isHoneyRoute(path) {
611
- const p = path.split("?")[0] ?? path;
612
- return DECOY_ROUTES.includes(p);
613
- }
614
- function isHoneyCanary(text) {
615
- if (!text) return false;
616
- for (const c of registered) {
617
- if (c && text.includes(c)) return true;
618
- }
619
- return false;
620
- }
621
- function noteHoneyRouteHit(ip, now = Date.now()) {
622
- const prev = routeHits.get(ip);
623
- if (prev === void 0 || now - prev.start > ROUTE_WINDOW_MS) {
624
- routeHits.set(ip, { count: 1, start: now });
625
- return 1;
626
- }
627
- prev.count += 1;
628
- return prev.count;
629
- }
630
-
631
- // src/runtime/rasp/middleware.ts
632
- function raspContext() {
633
- return function raspContextMiddleware(req, res, next) {
634
- const ctx = contextFromRequest(req);
635
- try {
636
- const credHit = ctx.strings.some((s) => isHoneyCanary(s));
637
- const routeHit = isHoneyRoute(ctx.path);
638
- if (routeHit && !credHit) {
639
- const count = noteHoneyRouteHit(ctx.ip);
640
- addThreat(ctx.ip, "honey");
641
- emitRaspEvent({
642
- ruleId: "RASP-HONEY-001",
643
- path: ctx.path,
644
- ip: ctx.ip,
645
- sample: "honeytoken",
646
- severity: "low",
647
- count
648
- });
649
- } else if (credHit) {
650
- addThreat(ctx.ip, "honey");
651
- emitRaspEvent({
652
- ruleId: "RASP-HONEY-001",
653
- path: ctx.path,
654
- ip: ctx.ip,
655
- sample: "canary-credential",
656
- severity: "critical"
657
- });
658
- if (isIpBlocked(ctx.ip)) {
659
- if (typeof res.status === "function") {
660
- res.status(403).json({ error: "blocked", ruleId: "RASP-HONEY-001" });
661
- return;
662
- }
663
- res.statusCode = 403;
664
- res.end?.(JSON.stringify({ error: "blocked", ruleId: "RASP-HONEY-001" }));
665
- return;
666
- }
667
- }
668
- const proto = runDecide("proto", null, ctx);
669
- if (proto.block && proto.ruleId) {
670
- emitRaspEvent({
671
- ruleId: proto.ruleId,
672
- path: ctx.path,
673
- ip: ctx.ip,
674
- sample: "prototype-key"
675
- });
676
- if (raspModeFor(ctx.path, "proto") === "block") {
677
- if (typeof res.status === "function") {
678
- res.status(403).json({ error: "blocked", ruleId: proto.ruleId });
679
- return;
680
- }
681
- res.statusCode = 403;
682
- res.end?.(JSON.stringify({ error: "blocked", ruleId: proto.ruleId }));
683
- return;
684
- }
685
- }
686
- } catch (err) {
687
- if (err instanceof RaspBlockedError) {
688
- if (typeof res.status === "function") {
689
- res.status(403).json({ error: "blocked", ruleId: err.ruleId });
690
- return;
691
- }
692
- throw err;
693
- }
694
- emitRaspEvent({
695
- ruleId: RASP_FAIL_OPEN,
696
- path: ctx.path,
697
- ip: ctx.ip,
698
- sample: "hook-error"
699
- });
700
- }
701
- enterRaspContext(ctx, () => {
702
- next();
703
- });
704
- };
705
- }
706
-
707
- // src/runtime/rasp/hooks.ts
708
- var require2 = createRequire(import.meta.url);
709
- var restorers = [];
710
- var wrappedPackages = /* @__PURE__ */ new WeakSet();
711
- var hooksLive = false;
712
- var sqliteHookRegistered = false;
713
- function sampleOf(sink) {
714
- if (typeof sink === "string") return sink.slice(0, 40);
715
- if (sink === null || sink === void 0) return "";
716
- return "[object]";
717
- }
718
- function inferPathBase(p) {
719
- const ctx = currentRaspContext();
720
- if (!ctx) return void 0;
721
- for (const s of ctx.strings) {
722
- if (!s) continue;
723
- const idx = p.indexOf(s);
724
- if (idx > 0) return p.slice(0, idx);
725
- }
726
- return void 0;
727
- }
728
- function guardSink(kind, sink, base) {
729
- try {
730
- const ctx = currentRaspContext();
731
- if (ctx) {
732
- const decision = runDecide(kind, sink, ctx, base);
733
- if (decision.block && decision.ruleId) {
734
- emitRaspEvent({
735
- ruleId: decision.ruleId,
736
- path: ctx.path,
737
- ip: ctx.ip,
738
- sample: sampleOf(sink)
739
- });
740
- addThreat(ctx.ip, "block");
741
- if (raspModeFor(ctx.path, kind) === "block") throw new RaspBlockedError(decision.ruleId);
742
- }
743
- }
744
- } catch (err) {
745
- if (err instanceof RaspBlockedError) throw err;
746
- const ctx = currentRaspContext();
747
- emitRaspEvent({
748
- ruleId: RASP_FAIL_OPEN,
749
- path: ctx?.path ?? "/",
750
- ip: ctx?.ip ?? "unknown",
751
- sample: "hook-error"
752
- });
753
- }
754
- }
755
- function applySink(kind, sink, base, original) {
756
- guardSink(kind, sink, base);
757
- return original();
758
- }
759
- function patchMethod(obj, key, wrapper) {
760
- const record = obj;
761
- const orig = record[key];
762
- if (typeof orig !== "function") return;
763
- const wrapped = wrapper(orig);
764
- try {
765
- Object.defineProperty(wrapped, "name", { value: orig.name });
766
- } catch {
767
- }
768
- record[key] = wrapped;
769
- restorers.push(() => {
770
- record[key] = orig;
771
- });
772
- }
773
- function sqlTextFrom(sql) {
774
- if (typeof sql === "string") return sql;
775
- if (Array.isArray(sql)) {
776
- return sql.filter((part) => typeof part === "string").join(" ");
777
- }
778
- if (sql !== null && typeof sql === "object") {
779
- const rec = sql;
780
- if (typeof rec.text === "string") return rec.text;
781
- if (typeof rec.sql === "string") return rec.sql;
782
- if (Array.isArray(rec.strings)) {
783
- return rec.strings.filter((part) => typeof part === "string").join(" ");
784
- }
785
- }
786
- return "";
787
- }
788
- function wrapSqlMethod(obj, key) {
789
- patchMethod(obj, key, (orig) => {
790
- return function(sql, ...rest) {
791
- return applySink(
792
- "sql",
793
- sqlTextFrom(sql),
794
- void 0,
795
- () => orig.call(this, sql, ...rest)
796
- );
797
- };
798
- });
799
- }
800
- function wrapNosqlWhere(obj, key) {
801
- patchMethod(obj, key, (orig) => {
802
- return function(expr, ...rest) {
803
- const sink = typeof expr === "string" ? { $where: expr } : expr ?? {};
804
- return applySink("nosql", sink, void 0, () => orig.call(this, expr, ...rest));
805
- };
806
- });
807
- }
808
- function wrapShellCommand(obj, key, shellAlways) {
809
- patchMethod(obj, key, (orig) => {
810
- return function(...args) {
811
- const opts = args[1] && typeof args[1] === "object" ? args[1] : args[2] && typeof args[2] === "object" ? args[2] : void 0;
812
- const useShell = shellAlways || opts?.shell === true;
813
- if (!useShell) return orig.call(this, ...args);
814
- const cmd = typeof args[0] === "string" ? args[0] : "";
815
- return applySink("shell", cmd, void 0, () => orig.call(this, ...args));
816
- };
817
- });
818
- }
819
- function wrapFsPath(obj, key) {
820
- patchMethod(obj, key, (orig) => {
821
- return function(pathArg, ...rest) {
822
- const p = typeof pathArg === "string" ? pathArg : Buffer.isBuffer(pathArg) ? pathArg.toString() : pathArg instanceof URL ? pathArg.pathname : typeof pathArg === "number" || typeof pathArg === "boolean" ? String(pathArg) : "";
823
- return applySink("path", p, inferPathBase(p), () => orig.call(this, pathArg, ...rest));
824
- };
825
- });
826
- }
827
- function checkEgress(url) {
828
- const allow = raspEgressAllow();
829
- if (!allow || allow.length === 0) return;
830
- let host = "";
831
- try {
832
- host = new URL(url).hostname;
833
- } catch {
834
- return;
835
- }
836
- if (host && !egressAllowed(host, allow)) {
837
- const ctx = currentRaspContext();
838
- emitRaspEvent({
839
- ruleId: "RASP-EGRESS-001",
840
- path: ctx?.path ?? "/",
841
- ip: ctx?.ip ?? "unknown",
842
- sample: host.slice(0, 40)
843
- });
844
- if (ctx) addThreat(ctx.ip, "block");
845
- if (raspModeFor(ctx?.path ?? "/", "ssrf") === "block") throw new RaspBlockedError("RASP-EGRESS-001");
846
- }
847
- }
848
- function wrapSsrf(obj, key, urlOf) {
849
- patchMethod(obj, key, (orig) => {
850
- return function(...args) {
851
- const url = urlOf(args);
852
- checkEgress(url);
853
- return applySink("ssrf", url, void 0, () => orig.call(this, ...args));
854
- };
855
- });
856
- }
857
- var WRAP_SQLITE = /* @__PURE__ */ Symbol.for("fullshield.rasp.wrapSqlite");
858
- var SQLITE_WRAPPED = /* @__PURE__ */ Symbol.for("fullshield.rasp.sqliteWrapped");
859
- var origModuleLoad;
860
- function protoFrom(value) {
861
- if (!value || typeof value !== "object") return void 0;
862
- const proto = value.DatabaseSync?.prototype;
863
- if (proto && typeof proto === "object") return proto;
864
- return void 0;
865
- }
866
- function sqlitePrototype(hint) {
867
- const fromHint = protoFrom(hint);
868
- if (fromHint) return fromHint;
869
- const getBuiltin = process.getBuiltinModule;
870
- if (typeof getBuiltin === "function") {
871
- try {
872
- const fromBuiltin = protoFrom(getBuiltin.call(process, "node:sqlite") ?? getBuiltin.call(process, "sqlite"));
873
- if (fromBuiltin) return fromBuiltin;
874
- } catch {
875
- }
876
- }
877
- if (origModuleLoad) {
878
- try {
879
- return protoFrom(origModuleLoad("node:sqlite", void 0, false));
880
- } catch {
881
- }
882
- }
883
- return void 0;
884
- }
885
- function sqliteAlreadyLoaded() {
886
- const list = process.moduleLoadList;
887
- return Array.isArray(list) && list.some((entry) => /(?:^| )sqlite$/i.test(entry));
888
- }
889
- function wrapSqliteNow(hint) {
890
- try {
891
- const proto = sqlitePrototype(hint);
892
- if (!proto || proto[SQLITE_WRAPPED]) return;
893
- Object.defineProperty(proto, SQLITE_WRAPPED, { value: true, configurable: true });
894
- wrapSqlMethod(proto, "exec");
895
- wrapSqlMethod(proto, "prepare");
896
- restorers.push(() => {
897
- delete proto[SQLITE_WRAPPED];
898
- });
899
- } catch {
900
- }
901
- }
902
- function wrapBuiltins() {
903
- globalThis[WRAP_SQLITE] = wrapSqliteNow;
904
- const cp = require2("node:child_process");
905
- const cpAlias = require2("child_process");
906
- for (const mod of /* @__PURE__ */ new Set([cp, cpAlias])) {
907
- wrapShellCommand(mod, "exec", true);
908
- wrapShellCommand(mod, "execSync", true);
909
- wrapShellCommand(mod, "spawn", false);
910
- wrapShellCommand(mod, "spawnSync", false);
911
- wrapShellCommand(mod, "execFile", false);
912
- wrapShellCommand(mod, "execFileSync", false);
913
- }
914
- const fsNames = [
915
- "readFile",
916
- "readFileSync",
917
- "writeFile",
918
- "writeFileSync",
919
- "appendFile",
920
- "appendFileSync",
921
- "open",
922
- "openSync",
923
- "createReadStream",
924
- "createWriteStream",
925
- "stat",
926
- "statSync",
927
- "lstat",
928
- "lstatSync",
929
- "access",
930
- "accessSync",
931
- "readdir",
932
- "readdirSync",
933
- "unlink",
934
- "unlinkSync",
935
- "rm",
936
- "rmSync"
937
- ];
938
- const fs = require2("node:fs");
939
- const fsAlias = require2("fs");
940
- for (const mod of /* @__PURE__ */ new Set([fs, fsAlias])) {
941
- for (const name of fsNames) wrapFsPath(mod, name);
942
- }
943
- try {
944
- const fsp = require2("node:fs/promises");
945
- for (const name of ["readFile", "writeFile", "appendFile", "open", "stat", "lstat", "access", "readdir", "unlink", "rm"]) {
946
- wrapFsPath(fsp, name);
947
- }
948
- } catch {
949
- }
950
- const http = require2("node:http");
951
- const https = require2("node:https");
952
- wrapSsrf(http, "request", urlFromHttpArgs);
953
- wrapSsrf(http, "get", urlFromHttpArgs);
954
- wrapSsrf(https, "request", urlFromHttpArgs);
955
- wrapSsrf(https, "get", urlFromHttpArgs);
956
- if (typeof globalThis.fetch === "function") {
957
- const origFetch = globalThis.fetch;
958
- globalThis.fetch = function(input, init) {
959
- const start = fetchUrl(input);
960
- checkEgress(start);
961
- const mode = init?.redirect ?? "follow";
962
- if (mode !== "follow") {
963
- return applySink("ssrf", start, void 0, () => origFetch.call(this, input, init));
964
- }
965
- guardSink("ssrf", start);
966
- return (async () => {
967
- let current = start;
968
- let nextInput = input;
969
- for (let hop = 0; hop < 8; hop += 1) {
970
- const res = await origFetch.call(this, nextInput, { ...init, redirect: "manual" });
971
- if (res.status < 300 || res.status >= 400) return res;
972
- const loc = res.headers.get("location");
973
- if (!loc) return res;
974
- current = new URL(loc, current).href;
975
- guardSink("ssrf", current);
976
- nextInput = current;
977
- }
978
- return origFetch.call(this, current, init);
979
- })();
980
- };
981
- restorers.push(() => {
982
- globalThis.fetch = origFetch;
983
- });
984
- }
985
- const vm = require2("node:vm");
986
- for (const name of ["runInNewContext", "runInThisContext", "runInContext", "compileFunction"]) {
987
- patchMethod(vm, name, (orig) => {
988
- return function(code, ...rest) {
989
- return applySink(
990
- "sql",
991
- typeof code === "string" ? code : "",
992
- void 0,
993
- () => orig.call(this, code, ...rest)
994
- );
995
- };
996
- });
997
- }
998
- const Module = require2("node:module");
999
- origModuleLoad = Module._load.bind(Module);
1000
- const origLoad = Module._load;
1001
- Module._load = function(request, parent, isMain) {
1002
- const exported = origLoad.call(this, request, parent, isMain);
1003
- try {
1004
- return wrapKnownPackage(String(request), exported);
1005
- } catch {
1006
- emitRaspEvent({
1007
- ruleId: RASP_FAIL_OPEN,
1008
- path: currentRaspContext()?.path ?? "/",
1009
- ip: currentRaspContext()?.ip ?? "unknown",
1010
- sample: "hook-error"
1011
- });
1012
- return exported;
1013
- }
1014
- };
1015
- restorers.push(() => {
1016
- Module._load = origLoad;
1017
- origModuleLoad = void 0;
1018
- });
1019
- const registerHooks = Module.registerHooks;
1020
- if (typeof registerHooks === "function" && !sqliteHookRegistered) {
1021
- sqliteHookRegistered = true;
1022
- registerHooks({
1023
- load(url, context, nextLoad) {
1024
- const result = nextLoad(url, context);
1025
- if (hooksLive && typeof url === "string" && (url === "node:sqlite" || url.startsWith("node:sqlite"))) {
1026
- wrapSqliteNow();
1027
- }
1028
- return result;
1029
- }
1030
- });
1031
- }
1032
- if (sqliteAlreadyLoaded()) wrapSqliteNow();
1033
- }
1034
- function wrapExpressLike(exported) {
1035
- if (typeof exported !== "function") return;
1036
- const orig = exported;
1037
- if (wrappedPackages.has(orig)) return;
1038
- function patched(...args) {
1039
- const app = orig.apply(this, args);
1040
- try {
1041
- if (app && typeof app.use === "function" && !app.__fullshieldRasp) {
1042
- app.use(raspContext());
1043
- app.__fullshieldRasp = true;
1044
- }
1045
- } catch {
1046
- emitRaspEvent({
1047
- ruleId: RASP_FAIL_OPEN,
1048
- path: "/",
1049
- ip: "unknown",
1050
- sample: "hook-error"
1051
- });
1052
- }
1053
- return app;
1054
- }
1055
- Object.assign(patched, orig);
1056
- Object.setPrototypeOf(patched, orig);
1057
- wrappedPackages.add(orig);
1058
- exported.__fullshieldExpressPatch = patched;
1059
- }
1060
- function wrapKnownPackage(request, exported) {
1061
- if (exported === null || typeof exported !== "object" && typeof exported !== "function") return exported;
1062
- const obj = exported;
1063
- if (wrappedPackages.has(obj)) return exported;
1064
- const name = request.replace(/^node:/, "");
1065
- if (name === "sqlite") {
1066
- wrapSqliteNow(exported);
1067
- return exported;
1068
- }
1069
- if (name === "pg" || name.endsWith("/pg")) {
1070
- const pg = exported;
1071
- if (pg.Client?.prototype) wrapSqlMethod(pg.Client.prototype, "query");
1072
- if (pg.Pool?.prototype) wrapSqlMethod(pg.Pool.prototype, "query");
1073
- wrappedPackages.add(obj);
1074
- return exported;
1075
- }
1076
- if (name === "mysql2" || name.endsWith("/mysql2") || name.includes("mysql2/")) {
1077
- const mysql = exported;
1078
- const proto = mysql.Connection?.prototype ?? mysql.prototype;
1079
- if (proto) {
1080
- wrapSqlMethod(proto, "query");
1081
- wrapSqlMethod(proto, "execute");
1082
- }
1083
- wrappedPackages.add(obj);
1084
- return exported;
1085
- }
1086
- if (name === "better-sqlite3" || name.endsWith("/better-sqlite3")) {
1087
- const proto = exported.prototype;
1088
- if (proto) {
1089
- wrapSqlMethod(proto, "exec");
1090
- wrapSqlMethod(proto, "prepare");
1091
- }
1092
- wrappedPackages.add(obj);
1093
- return exported;
1094
- }
1095
- if (name === "@prisma/client" || name === "prisma" || name.endsWith("/@prisma/client")) {
1096
- const client = exported.PrismaClient?.prototype;
1097
- if (client) {
1098
- wrapSqlMethod(client, "$queryRawUnsafe");
1099
- wrapSqlMethod(client, "$executeRawUnsafe");
1100
- wrapSqlMethod(client, "$queryRaw");
1101
- wrapSqlMethod(client, "$executeRaw");
1102
- }
1103
- const Prisma = exported.Prisma;
1104
- if (Prisma) wrapSqlMethod(Prisma, "raw");
1105
- wrappedPackages.add(obj);
1106
- return exported;
1107
- }
1108
- if (name === "sequelize" || name.endsWith("/sequelize")) {
1109
- const rec = exported;
1110
- const proto = rec.Sequelize?.prototype ?? rec.prototype;
1111
- if (proto) wrapSqlMethod(proto, "query");
1112
- if (typeof rec.literal === "function") wrapSqlMethod(rec, "literal");
1113
- if (rec.Sequelize && typeof rec.Sequelize.literal === "function") wrapSqlMethod(rec.Sequelize, "literal");
1114
- wrappedPackages.add(obj);
1115
- return exported;
1116
- }
1117
- if (name === "knex" || name.endsWith("/knex")) {
1118
- if (typeof exported.raw === "function") wrapSqlMethod(obj, "raw");
1119
- const client = exported.Client?.prototype;
1120
- if (client) {
1121
- wrapSqlMethod(client, "raw");
1122
- wrapSqlMethod(client, "whereRaw");
1123
- wrapSqlMethod(client, "orderByRaw");
1124
- wrapSqlMethod(client, "query");
1125
- }
1126
- wrappedPackages.add(obj);
1127
- return exported;
1128
- }
1129
- if (name === "typeorm" || name.endsWith("/typeorm")) {
1130
- const rec = exported;
1131
- for (const cls of ["DataSource", "EntityManager", "QueryRunner", "Connection"]) {
1132
- const proto = rec[cls]?.prototype;
1133
- if (proto) wrapSqlMethod(proto, "query");
1134
- }
1135
- const qb = rec.SelectQueryBuilder?.prototype ?? rec.QueryBuilder?.prototype;
1136
- if (qb) wrapSqlMethod(qb, "where");
1137
- wrappedPackages.add(obj);
1138
- return exported;
1139
- }
1140
- if (name === "drizzle-orm" || name.endsWith("/drizzle-orm")) {
1141
- const sql = exported.sql;
1142
- if (sql) wrapSqlMethod(sql, "raw");
1143
- wrappedPackages.add(obj);
1144
- return exported;
1145
- }
1146
- if (name === "mongodb" || name.includes("mongodb")) {
1147
- const col = exported.Collection?.prototype;
1148
- if (col) {
1149
- patchMethod(col, "find", (orig) => {
1150
- return function(query, ...rest) {
1151
- return applySink("nosql", query ?? {}, void 0, () => orig.call(this, query, ...rest));
1152
- };
1153
- });
1154
- }
1155
- wrappedPackages.add(obj);
1156
- return exported;
1157
- }
1158
- if (name === "mongoose" || name.includes("mongoose")) {
1159
- const q = exported.Query?.prototype;
1160
- if (q) {
1161
- patchMethod(q, "find", (orig) => {
1162
- return function(query, ...rest) {
1163
- return applySink("nosql", query ?? {}, void 0, () => orig.call(this, query, ...rest));
1164
- };
1165
- });
1166
- wrapNosqlWhere(q, "$where");
1167
- }
1168
- const model = exported.Model?.prototype;
1169
- if (model) wrapNosqlWhere(model, "$where");
1170
- wrappedPackages.add(obj);
1171
- return exported;
1172
- }
1173
- if (name === "axios") {
1174
- const ax = exported;
1175
- const urlOf = (args) => {
1176
- const a = args[0];
1177
- if (typeof a === "string") return a;
1178
- if (a && typeof a === "object" && "url" in a && typeof a.url === "string") return a.url;
1179
- return "";
1180
- };
1181
- wrapSsrf(ax, "request", urlOf);
1182
- wrapSsrf(ax, "get", urlOf);
1183
- wrapSsrf(ax, "post", urlOf);
1184
- wrappedPackages.add(obj);
1185
- return exported;
1186
- }
1187
- if (name === "express") {
1188
- wrapExpressLike(exported);
1189
- const patched = exported.__fullshieldExpressPatch;
1190
- if (typeof patched === "function") {
1191
- const resp = exported.response;
1192
- if (resp && typeof resp.redirect === "function") {
1193
- patchMethod(resp, "redirect", (orig) => {
1194
- return function(...args) {
1195
- const url = typeof args[0] === "string" ? args[0] : typeof args[1] === "string" ? args[1] : "";
1196
- return applySink("ssrf", url, void 0, () => orig.call(this, ...args));
1197
- };
1198
- });
1199
- }
1200
- wrappedPackages.add(obj);
1201
- return patched;
1202
- }
1203
- }
1204
- return exported;
1205
- }
1206
- function installHooks() {
1207
- hooksLive = true;
1208
- wrapBuiltins();
1209
- }
1210
-
1211
- // src/runtime/rasp/vpatch.ts
1212
- function patchForcesBlock(patches, route, kind) {
1213
- return patches.some((p) => {
1214
- const routeHit = p.route === void 0 || p.route === route;
1215
- const sinkHit = p.sink === void 0 || p.sink === kind;
1216
- return routeHit && sinkHit;
1217
- });
1218
- }
1219
-
1220
- // src/runtime/rasp/enable.ts
1221
- var enabled = false;
1222
- var options = {};
1223
- function raspMode() {
1224
- return options.mode ?? "monitor";
1225
- }
1226
- function raspModeFor(route, kind) {
1227
- if (patchForcesBlock(options.virtualPatches ?? [], route, kind)) return "block";
1228
- return raspMode();
1229
- }
1230
- function raspEgressAllow() {
1231
- return options.egress?.allow;
1232
- }
1233
- function runDecide(kind, sink, ctx, base) {
1234
- if (options.decide) return options.decide(kind, sink, ctx);
1235
- return decideSink(kind, sink, ctx, base);
1236
- }
1237
- function emitRaspEvent(event) {
1238
- options.onEvent?.(event);
1239
- }
1240
- function enableRasp(opts = {}) {
1241
- if (!opts.experimental && process.env.FULLSHIELD_EXPERIMENTAL_RASP !== "1") {
1242
- return;
1243
- }
1244
- options = { ...opts, experimental: true };
1245
- configureThreat({ autoBlock: opts.threatAutoBlock === true, maxBlockMs: opts.threatMaxBlockMs });
1246
- if (!enabled) {
1247
- installHooks();
1248
- enabled = true;
1249
- }
1250
- }
1251
- function enableRaspFromEnv() {
1252
- if (process.env.FULLSHIELD_EXPERIMENTAL_RASP !== "1") return;
1253
- enableRasp({
1254
- experimental: true,
1255
- mode: process.env.FULLSHIELD_RASP_MODE === "block" ? "block" : "monitor"
1256
- });
1257
- }
1258
-
1259
- // src/runtime/auto.ts
1260
- enableRaspFromEnv();
1
+ import Un from'dns';import {createRequire}from'module';import {AsyncLocalStorage}from'async_hooks';function J(n){return n===null||typeof n!="object"||Array.isArray(n)?false:Object.keys(n).some(t=>t.startsWith("$"))}function H(n,t){if(n===t)return true;if(n===null||t===null||typeof n!="object"||typeof t!="object")return false;if(Array.isArray(n)||Array.isArray(t))return !Array.isArray(n)||!Array.isArray(t)||n.length!==t.length?false:n.every((o,s)=>H(o,t[s]));let e=Object.keys(n),r=Object.keys(t);return e.length!==r.length?false:e.every(o=>H(n[o],t[o]))}function M(n,t,e){if(!(e>8||n===null||typeof n!="object")){if(t.push(n),Array.isArray(n)){for(let r of n)M(r,t,e+1);return}for(let r of Object.values(n))M(r,t,e+1);}}function nn(n,t){let e=[];for(let i of t.objects)M(i,e,0);let r=e.filter(J);if(r.length===0)return false;let o=new Set,s=(i,u)=>u>8||i===null||typeof i!="object"||o.has(i)?false:(o.add(i),J(i)&&r.some(c=>c===i||H(c,i))?true:Array.isArray(i)?i.some(c=>s(c,u+1)):Object.values(i).some(c=>s(c,u+1)));return s(n,0)}function W(n){let t=n.startsWith("/")||/^[A-Za-z]:[\\/]/.test(n),e=[];for(let r of n.split(/[\\/]+/))if(!(r===""||r===".")){if(r===".."){e.pop();continue}e.push(r);}return (t?"/":"")+e.join("/")}function Hn(n,t){let e=W(n).replace(/\/+$/,""),r=W(t);return e===""?!r.startsWith("/")&&!r.split("/").includes(".."):r===e||r.startsWith(`${e}/`)}function Mn(n,t){let e=n.indexOf(t);return e<=0?"":n.slice(0,e)}function tn(n){let t=W(n).split("/").filter(Boolean);return t[t.length-1]}function en(n,t,e){if(!t)return false;if(t.includes("\0")||n.includes("\0"))return true;if(n.includes(t)){if(t.includes(".."))return true;let r=e!==void 0&&e.length>0?e:Mn(n,t);return r===""?t.startsWith("/")||/^[A-Za-z]:[\\/]/.test(t)||t.includes(".."):!Hn(r,n)}if(t.includes("..")||t.startsWith("/")||/^[A-Za-z]:[\\/]/.test(t)){let r=tn(t),o=tn(n);if(r&&r===o)return true}return false}var Wn=new Set(["__proto__","constructor","prototype"]);function B(n,t,e){if(t>8||n===null||typeof n!="object"||e.has(n))return false;e.add(n);for(let r of Object.getOwnPropertyNames(n))if(Wn.has(r))return true;return Array.isArray(n)?n.some(r=>B(r,t+1,e)):Object.values(n).some(r=>B(r,t+1,e))}function rn(n){let t=new Set;return n.objects.some(e=>B(e,0,t))}function Bn(n){let t=[],e=0,r=n.length;for(;e<r;){let o=n[e];if(o===" "||o===" "||o===`
2
+ `){let i=e;for(;e<r&&(n[e]===" "||n[e]===" "||n[e]===`
3
+ `);)e+=1;t.push({kind:"space",start:i,end:e});continue}if(o==="'"||o==='"'){let i=o,u=e;for(e+=1;e<r&&n[e]!==i;)i==='"'&&n[e]==="\\"&&(e+=1),e+=1;e<r&&(e+=1),t.push({kind:"word",start:u,end:e});continue}if(o==="`"){let i=e;for(e+=1;e<r&&n[e]!=="`";)e+=1;e<r&&(e+=1),t.push({kind:"op",start:i,end:e});continue}if(o==="$"&&(n[e+1]==="("||n[e+1]==="{")){let i=n[e+1],u=i==="("?")":"}",c=e;e+=2;let k=1;for(;e<r&&k>0;)n[e]===i?k+=1:n[e]===u&&(k-=1),e+=1;t.push({kind:"op",start:c,end:e});continue}if(n.startsWith("||",e)||n.startsWith("&&",e)||n.startsWith(">>",e)||n.startsWith("<<",e)){t.push({kind:"op",start:e,end:e+2}),e+=2;continue}if(";|&<>()".includes(o)){t.push({kind:"op",start:e,end:e+1}),e+=1;continue}let s=e;for(;e<r&&!/[\s;|&$`<>()]/.test(n[e]);)e+=1;t.push({kind:"word",start:s,end:e});}return t}function on(n,t){if(!t)return false;let e=Bn(n),r=0;for(;r<=n.length;){let o=n.indexOf(t,r);if(o===-1)return false;let s=o+t.length,i=e.filter(u=>u.kind!=="space"&&u.start<s&&u.end>o);if(!(i.length===1&&i[0].kind==="word"&&i[0].start<=o&&i[0].end>=s))return true;r=o+Math.max(t.length,1);}return false}function Kn(n){let t=[],e=0,r=n.length;for(;e<r;){let o=n[e];if(o<=" "){let i=e;for(;e<r&&n[e]<=" ";)e+=1;t.push({kind:"space",start:i,end:e});continue}if(o==="'"||o==='"'||o==="`"){let i=o,u=e;for(e+=1;e<r;){if(n[e]===i){if(n[e+1]===i){e+=2;continue}e+=1;break}e+=1;}t.push({kind:"string",start:u,end:e});continue}if(o==="-"&&n[e+1]==="-"){let i=e;for(e+=2;e<r&&n[e]!==`
4
+ `;)e+=1;t.push({kind:"comment",start:i,end:e});continue}if(o==="/"&&n[e+1]==="*"){let i=e;for(e+=2;e<r&&!(n[e]==="*"&&n[e+1]==="/");)e+=1;e+1<r&&(e+=2),t.push({kind:"comment",start:i,end:e});continue}if(o==="#"){let i=e;for(;e<r&&n[e]!==`
5
+ `;)e+=1;t.push({kind:"comment",start:i,end:e});continue}if(o>="0"&&o<="9"||o==="."&&e+1<r&&n[e+1]>="0"&&n[e+1]<="9"){let i=e;for(;e<r&&/[0-9.eE+-]/.test(n[e]);)e+=1;t.push({kind:"number",start:i,end:e});continue}if(/[A-Za-z_]/.test(o)){let i=e;for(;e<r&&/[A-Za-z0-9_$]/.test(n[e]);)e+=1;t.push({kind:"ident",start:i,end:e});continue}let s=e;n.startsWith("<=",e)||n.startsWith(">=",e)||n.startsWith("<>",e)||n.startsWith("!=",e)||n.startsWith("||",e)||n.startsWith("&&",e)?e+=2:e+=1,t.push({kind:"op",start:s,end:e});}return t}function Nn(n,t,e){let r=n.filter(o=>o.kind!=="space"&&o.start<e&&o.end>t);if(r.length===1){let o=r[0];return !(o.kind==="string"||o.kind==="number"||o.kind==="comment")||o.start>t||o.end<e}return r.length>0}function K(n,t){if(!t)return false;let e=Kn(n),r=0;for(;r<=n.length;){let o=n.indexOf(t,r);if(o===-1)return false;if(Nn(e,o,o+t.length))return true;r=o+Math.max(t.length,1);}return false}function N(n){let t=n.trim().toLowerCase();return t=t.replace(/\.+$/,""),t.startsWith("[")&&t.endsWith("]")&&(t=t.slice(1,-1)),t.startsWith("::ffff:")&&(t=t.slice(7)),t.startsWith("[")&&t.endsWith("]")&&(t=t.slice(1,-1)),t}function $n(n){if(/^0x[0-9a-f]+$/i.test(n)){let t=Number.parseInt(n,16);return Number.isFinite(t)?t:void 0}if(/^0[0-7]+$/.test(n)){let t=Number.parseInt(n,8);return Number.isFinite(t)?t:void 0}if(/^\d+$/.test(n)){let t=Number(n);return Number.isFinite(t)?t:void 0}}function fn(n){let t=N(n);if(!t||t.includes(":"))return;let e=t.split(".");if(e.length===0||e.length>4)return;let r=e.map($n);if(r.some(c=>c===void 0))return;let o=r[0];if(e.length===1)return o>4294967295?void 0:[o>>>24&255,o>>>16&255,o>>>8&255,o&255];if(e.length===2){let c=r[1];return o>255||c>16777215?void 0:[o,c>>>16&255,c>>>8&255,c&255]}if(e.length===3){let c=r[1],k=r[2];return o>255||c>255||k>65535?void 0:[o,c,k>>>8&255,k&255]}let s=r[1],i=r[2],u=r[3];if(![o,s,i,u].some(c=>c>255))return [o,s,i,u]}function Dn(n){let[t,e]=n;return t===127||t===0||t===10||t===169&&e===254||t===192&&e===168||t===172&&e>=16&&e<=31}function zn(n){return !!(n==="::1"||n==="::"||n==="0:0:0:0:0:0:0:1"||n.startsWith("fc")||n.startsWith("fd")||n.startsWith("fe80:"))}function un(n){let t=N(n);if(t==="localhost"||t.endsWith(".localhost")||t==="metadata.google.internal")return true;let e=fn(t);return e?Dn(e):t.includes(":")?zn(t):false}function Qn(n){let t=N(n);if(fn(t)||t.includes(":"))return [t];try{let e=Un.lookupSync;if(typeof e=="function")return e(t,{all:!0}).map(r=>r.address)}catch{}return []}function Vn(n){try{return Qn(n)}catch{return []}}function cn(n){if(un(n))return true;for(let t of Vn(n))if(un(t))return true;return false}function an(n,t){if(!t||!n.includes(t))return false;let e;try{e=new URL(n);}catch{return false}let r=e.hostname;if(!r)return false;let s=(/^https?:\/\/([^/?#]+)/i.exec(n)?.[1]??r).replace(/:\d+$/,"");return t===n||t.includes(r)||t.includes(s)||s.includes(t)&&t.length>=3?cn(r)||cn(s):false}function C(n){let t=n[0];if(typeof t=="string")return t;if(t instanceof URL)return t.href;if(t&&typeof t=="object"){let e=t;if(typeof e.href=="string")return e.href;let r=e.protocol??"http:",o=e.hostname??e.host??"",s=e.path??"/";return `${r}//${o}${s}`}return ""}function ln(n){return typeof n=="string"?n:n instanceof URL?n.href:n&&typeof n=="object"&&"url"in n&&typeof n.url=="string"?n.url:""}var pn={sql:"RASP-SQL-001",shell:"RASP-SHELL-001",path:"RASP-PATH-001",nosql:"RASP-NOSQL-001",ssrf:"RASP-SSRF-001",proto:"RASP-PROTO-001"},j="RASP-FAIL-OPEN-001",w=class extends Error{ruleId;constructor(t){super(`fullshield rasp blocked ${t}`),this.name="RaspBlockedError",this.ruleId=t;}};function Zn(n){return typeof n=="string"?[n]:typeof n=="number"||typeof n=="boolean"?[String(n)]:[]}function Yn(n,t){let e="";if(typeof n=="string")e=n;else if(n!==null&&typeof n=="object"&&!Array.isArray(n)&&"$where"in n){let r=n.$where;typeof r=="string"&&(e=r);}if(!e)return false;for(let r of t.strings)if(r&&K(e,r))return true;return false}function Gn(n,t,e,r){if(n==="proto")return rn(e);if(n==="nosql")return nn(t,e)?true:Yn(t,e);for(let o of Zn(t))for(let s of e.strings)if(s&&(n==="sql"&&K(o,s)||n==="shell"&&on(o,s)||n==="path"&&en(o,s,r)||n==="ssrf"&&an(o,s)))return true;return false}function dn(n,t,e,r){return Gn(n,t,e,r)?{block:true,ruleId:pn[n]}:{block:false}}var Jn=new Set(["host","connection","user-agent","accept","accept-encoding","accept-language","content-type","content-length","transfer-encoding","cookie"]),hn=new AsyncLocalStorage;function v(n,t,e){if(!(e>8||t.length>200)){if(typeof n=="string"){t.push(n);return}if(typeof n=="number"||typeof n=="boolean"){t.push(String(n));return}if(Array.isArray(n)){for(let r of n)v(r,t,e+1);return}if(n!==null&&typeof n=="object")for(let r of Object.values(n))v(r,t,e+1);}}function O(n,t,e){if(!(e>8||n===null||typeof n!="object")){if(t.push(n),Array.isArray(n)){for(let r of n)O(r,t,e+1);return}for(let r of Object.values(n))O(r,t,e+1);}}function gn(n){let t=[],e=[];for(let r of [n.query,n.body,n.params,n.cookies])v(r,t,0),O(r,e,0);if(n.headers&&typeof n.headers=="object"){for(let[r,o]of Object.entries(n.headers))Jn.has(r.toLowerCase())||v(o,t,0);O(n.headers,e,0);}return {path:n.path??n.url?.split("?")[0]??"/",ip:n.ip??"unknown",strings:t,objects:e}}function kn(n,t){return hn.run(n,t)}function R(){return hn.getStore()}function yn(n,t){let e=n.toLowerCase().replace(/\.+$/,"");for(let r of t){let o=r.toLowerCase();if(o.startsWith("*.")){let s=o.slice(1);if(e.endsWith(s)&&e!==o.slice(2))return true}else if(e===o)return true}return false}var mn=new Map,U=new Map,nt={block:2,honey:5,anomaly:1},tt=900*1e3,$=false,bn=tt;function wn(n={}){$=n.autoBlock===true,typeof n.maxBlockMs=="number"&&n.maxBlockMs>0&&(bn=n.maxBlockMs);}function A(n,t,e=Date.now()){let r=(mn.get(n)??0)+nt[t];return mn.set(n,r),$&&r>=10&&U.set(n,e+bn),r}function Rn(n,t=Date.now()){if(!$)return false;let e=U.get(n);return e===void 0?false:t>=e?(U.delete(n),false):true}var et=["/admin.php","/.git/config","/wp-login.php"],rt=new Set,ot=6e4,Sn=new Map;function xn(n){let t=n.split("?")[0]??n;return et.includes(t)}function jn(n){if(!n)return false;for(let t of rt)if(t&&n.includes(t))return true;return false}function An(n,t=Date.now()){let e=Sn.get(n);return e===void 0||t-e.start>ot?(Sn.set(n,{count:1,start:t}),1):(e.count+=1,e.count)}function En(){return function(t,e,r){let o=gn(t);try{let s=o.strings.some(c=>jn(c));if(xn(o.path)&&!s){let c=An(o.ip);A(o.ip,"honey"),g({ruleId:"RASP-HONEY-001",path:o.path,ip:o.ip,sample:"honeytoken",severity:"low",count:c});}else if(s&&(A(o.ip,"honey"),g({ruleId:"RASP-HONEY-001",path:o.path,ip:o.ip,sample:"canary-credential",severity:"critical"}),Rn(o.ip))){if(typeof e.status=="function"){e.status(403).json({error:"blocked",ruleId:"RASP-HONEY-001"});return}e.statusCode=403,e.end?.(JSON.stringify({error:"blocked",ruleId:"RASP-HONEY-001"}));return}let u=q("proto",null,o);if(u.block&&u.ruleId&&(g({ruleId:u.ruleId,path:o.path,ip:o.ip,sample:"prototype-key"}),L(o.path,"proto")==="block")){if(typeof e.status=="function"){e.status(403).json({error:"blocked",ruleId:u.ruleId});return}e.statusCode=403,e.end?.(JSON.stringify({error:"blocked",ruleId:u.ruleId}));return}}catch(s){if(s instanceof w){if(typeof e.status=="function"){e.status(403).json({error:"blocked",ruleId:s.ruleId});return}throw s}g({ruleId:j,path:o.path,ip:o.ip,sample:"hook-error"});}kn(o,()=>{r();});}}var m=createRequire(import.meta.url),T=[],l=new WeakSet,Ln=false,In=false;function it(n){return typeof n=="string"?n.slice(0,40):n==null?"":"[object]"}function ut(n){let t=R();if(t)for(let e of t.strings){if(!e)continue;let r=n.indexOf(e);if(r>0)return n.slice(0,r)}}function Q(n,t,e){try{let r=R();if(r){let o=q(n,t,r,e);if(o.block&&o.ruleId&&(g({ruleId:o.ruleId,path:r.path,ip:r.ip,sample:it(t)}),A(r.ip,"block"),L(r.path,n)==="block"))throw new w(o.ruleId)}}catch(r){if(r instanceof w)throw r;let o=R();g({ruleId:j,path:o?.path??"/",ip:o?.ip??"unknown",sample:"hook-error"});}}function y(n,t,e,r){return Q(n,t,e),r()}function b(n,t,e){let r=n,o=r[t];if(typeof o!="function")return;let s=e(o);try{Object.defineProperty(s,"name",{value:o.name});}catch{}r[t]=s,T.push(()=>{r[t]=o;});}function ct(n){if(typeof n=="string")return n;if(Array.isArray(n))return n.filter(t=>typeof t=="string").join(" ");if(n!==null&&typeof n=="object"){let t=n;if(typeof t.text=="string")return t.text;if(typeof t.sql=="string")return t.sql;if(Array.isArray(t.strings))return t.strings.filter(e=>typeof e=="string").join(" ")}return ""}function a(n,t){b(n,t,e=>function(r,...o){return y("sql",ct(r),void 0,()=>e.call(this,r,...o))});}function Pn(n,t){b(n,t,e=>function(r,...o){return y("nosql",typeof r=="string"?{$where:r}:r??{},void 0,()=>e.call(this,r,...o))});}function E(n,t,e){b(n,t,r=>function(...o){let s=o[1]&&typeof o[1]=="object"?o[1]:o[2]&&typeof o[2]=="object"?o[2]:void 0;if(!(e||s?.shell===true))return r.call(this,...o);let u=typeof o[0]=="string"?o[0]:"";return y("shell",u,void 0,()=>r.call(this,...o))});}function Cn(n,t){b(n,t,e=>function(r,...o){let s=typeof r=="string"?r:Buffer.isBuffer(r)?r.toString():r instanceof URL?r.pathname:typeof r=="number"||typeof r=="boolean"?String(r):"";return y("path",s,ut(s),()=>e.call(this,r,...o))});}function vn(n){let t=qn();if(!t||t.length===0)return;let e="";try{e=new URL(n).hostname;}catch{return}if(e&&!yn(e,t)){let r=R();if(g({ruleId:"RASP-EGRESS-001",path:r?.path??"/",ip:r?.ip??"unknown",sample:e.slice(0,40)}),r&&A(r.ip,"block"),L(r?.path??"/","ssrf")==="block")throw new w("RASP-EGRESS-001")}}function S(n,t,e){b(n,t,r=>function(...o){let s=e(o);return vn(s),y("ssrf",s,void 0,()=>r.call(this,...o))});}var ft=Symbol.for("fullshield.rasp.wrapSqlite"),D=Symbol.for("fullshield.rasp.sqliteWrapped"),F;function z(n){if(!n||typeof n!="object")return;let t=n.DatabaseSync?.prototype;if(t&&typeof t=="object")return t}function at(n){let t=z(n);if(t)return t;let e=process.getBuiltinModule;if(typeof e=="function")try{let r=z(e.call(process,"node:sqlite")??e.call(process,"sqlite"));if(r)return r}catch{}if(F)try{return z(F("node:sqlite",void 0,!1))}catch{}}function lt(){let n=process.moduleLoadList;return Array.isArray(n)&&n.some(t=>/(?:^| )sqlite$/i.test(t))}function _(n){try{let t=at(n);if(!t||t[D])return;Object.defineProperty(t,D,{value:!0,configurable:!0}),a(t,"exec"),a(t,"prepare"),T.push(()=>{delete t[D];});}catch{}}function pt(){globalThis[ft]=_;let n=m("node:child_process"),t=m("child_process");for(let f of new Set([n,t]))E(f,"exec",true),E(f,"execSync",true),E(f,"spawn",false),E(f,"spawnSync",false),E(f,"execFile",false),E(f,"execFileSync",false);let e=["readFile","readFileSync","writeFile","writeFileSync","appendFile","appendFileSync","open","openSync","createReadStream","createWriteStream","stat","statSync","lstat","lstatSync","access","accessSync","readdir","readdirSync","unlink","unlinkSync","rm","rmSync"],r=m("node:fs"),o=m("fs");for(let f of new Set([r,o]))for(let p of e)Cn(f,p);try{let f=m("node:fs/promises");for(let p of ["readFile","writeFile","appendFile","open","stat","lstat","access","readdir","unlink","rm"])Cn(f,p);}catch{}let s=m("node:http"),i=m("node:https");if(S(s,"request",C),S(s,"get",C),S(i,"request",C),S(i,"get",C),typeof globalThis.fetch=="function"){let f=globalThis.fetch;globalThis.fetch=function(p,d){let h=ln(p);return vn(h),(d?.redirect??"follow")!=="follow"?y("ssrf",h,void 0,()=>f.call(this,p,d)):(Q("ssrf",h),(async()=>{let I=h,Y=p;for(let G=0;G<8;G+=1){let P=await f.call(this,Y,{...d,redirect:"manual"});if(P.status<300||P.status>=400)return P;let X=P.headers.get("location");if(!X)return P;I=new URL(X,I).href,Q("ssrf",I),Y=I;}return f.call(this,I,d)})())},T.push(()=>{globalThis.fetch=f;});}let u=m("node:vm");for(let f of ["runInNewContext","runInThisContext","runInContext","compileFunction"])b(u,f,p=>function(d,...h){return y("sql",typeof d=="string"?d:"",void 0,()=>p.call(this,d,...h))});let c=m("node:module");F=c._load.bind(c);let k=c._load;c._load=function(f,p,d){let h=k.call(this,f,p,d);try{return ht(String(f),h)}catch{return g({ruleId:j,path:R()?.path??"/",ip:R()?.ip??"unknown",sample:"hook-error"}),h}},T.push(()=>{c._load=k,F=void 0;});let Z=c.registerHooks;typeof Z=="function"&&!In&&(In=true,Z({load(f,p,d){let h=d(f,p);return Ln&&typeof f=="string"&&(f==="node:sqlite"||f.startsWith("node:sqlite"))&&_(),h}})),lt()&&_();}function dt(n){if(typeof n!="function")return;let t=n;if(l.has(t))return;function e(...r){let o=t.apply(this,r);try{o&&typeof o.use=="function"&&!o.__fullshieldRasp&&(o.use(En()),o.__fullshieldRasp=!0);}catch{g({ruleId:j,path:"/",ip:"unknown",sample:"hook-error"});}return o}Object.assign(e,t),Object.setPrototypeOf(e,t),l.add(t),n.__fullshieldExpressPatch=e;}function ht(n,t){if(t===null||typeof t!="object"&&typeof t!="function")return t;let e=t;if(l.has(e))return t;let r=n.replace(/^node:/,"");if(r==="sqlite")return _(t),t;if(r==="pg"||r.endsWith("/pg")){let o=t;return o.Client?.prototype&&a(o.Client.prototype,"query"),o.Pool?.prototype&&a(o.Pool.prototype,"query"),l.add(e),t}if(r==="mysql2"||r.endsWith("/mysql2")||r.includes("mysql2/")){let o=t,s=o.Connection?.prototype??o.prototype;return s&&(a(s,"query"),a(s,"execute")),l.add(e),t}if(r==="better-sqlite3"||r.endsWith("/better-sqlite3")){let o=t.prototype;return o&&(a(o,"exec"),a(o,"prepare")),l.add(e),t}if(r==="@prisma/client"||r==="prisma"||r.endsWith("/@prisma/client")){let o=t.PrismaClient?.prototype;o&&(a(o,"$queryRawUnsafe"),a(o,"$executeRawUnsafe"),a(o,"$queryRaw"),a(o,"$executeRaw"));let s=t.Prisma;return s&&a(s,"raw"),l.add(e),t}if(r==="sequelize"||r.endsWith("/sequelize")){let o=t,s=o.Sequelize?.prototype??o.prototype;return s&&a(s,"query"),typeof o.literal=="function"&&a(o,"literal"),o.Sequelize&&typeof o.Sequelize.literal=="function"&&a(o.Sequelize,"literal"),l.add(e),t}if(r==="knex"||r.endsWith("/knex")){typeof t.raw=="function"&&a(e,"raw");let o=t.Client?.prototype;return o&&(a(o,"raw"),a(o,"whereRaw"),a(o,"orderByRaw"),a(o,"query")),l.add(e),t}if(r==="typeorm"||r.endsWith("/typeorm")){let o=t;for(let i of ["DataSource","EntityManager","QueryRunner","Connection"]){let u=o[i]?.prototype;u&&a(u,"query");}let s=o.SelectQueryBuilder?.prototype??o.QueryBuilder?.prototype;return s&&a(s,"where"),l.add(e),t}if(r==="drizzle-orm"||r.endsWith("/drizzle-orm")){let o=t.sql;return o&&a(o,"raw"),l.add(e),t}if(r==="mongodb"||r.includes("mongodb")){let o=t.Collection?.prototype;return o&&b(o,"find",s=>function(i,...u){return y("nosql",i??{},void 0,()=>s.call(this,i,...u))}),l.add(e),t}if(r==="mongoose"||r.includes("mongoose")){let o=t.Query?.prototype;o&&(b(o,"find",i=>function(u,...c){return y("nosql",u??{},void 0,()=>i.call(this,u,...c))}),Pn(o,"$where"));let s=t.Model?.prototype;return s&&Pn(s,"$where"),l.add(e),t}if(r==="axios"){let o=t,s=i=>{let u=i[0];return typeof u=="string"?u:u&&typeof u=="object"&&"url"in u&&typeof u.url=="string"?u.url:""};return S(o,"request",s),S(o,"get",s),S(o,"post",s),l.add(e),t}if(r==="express"){dt(t);let o=t.__fullshieldExpressPatch;if(typeof o=="function"){let s=t.response;return s&&typeof s.redirect=="function"&&b(s,"redirect",i=>function(...u){let c=typeof u[0]=="string"?u[0]:typeof u[1]=="string"?u[1]:"";return y("ssrf",c,void 0,()=>i.call(this,...u))}),l.add(e),o}}return t}function On(){Ln=true,pt();}function _n(n,t,e){return n.some(r=>{let o=r.route===void 0||r.route===t,s=r.sink===void 0||r.sink===e;return o&&s})}var Tn=false,x={};function kt(){return x.mode??"monitor"}function L(n,t){return _n(x.virtualPatches??[],n,t)?"block":kt()}function qn(){return x.egress?.allow}function q(n,t,e,r){return x.decide?x.decide(n,t,e):dn(n,t,e,r)}function g(n){x.onEvent?.(n);}function yt(n={}){!n.experimental&&process.env.FULLSHIELD_EXPERIMENTAL_RASP!=="1"||(x={...n,experimental:true},wn({autoBlock:n.threatAutoBlock===true,maxBlockMs:n.threatMaxBlockMs}),Tn||(On(),Tn=true));}function Fn(){process.env.FULLSHIELD_EXPERIMENTAL_RASP==="1"&&yt({experimental:true,mode:process.env.FULLSHIELD_RASP_MODE==="block"?"block":"monitor"});}Fn();