socket-function 0.65.0 → 0.67.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.
- package/package.json +1 -1
- package/require/RequireController.ts +17 -4
- package/require/require.ts +147 -41
- package/src/callHTTPHandler.ts +52 -0
- package/src/corsCheck.ts +0 -0
- package/temp.md.ai.temp +41 -0
package/package.json
CHANGED
|
@@ -134,8 +134,9 @@ class RequireControllerBase {
|
|
|
134
134
|
|
|
135
135
|
public async requireHTML(config?: {
|
|
136
136
|
requireCalls?: string[];
|
|
137
|
+
cacheTime?: number;
|
|
137
138
|
}) {
|
|
138
|
-
let { requireCalls } = config || {};
|
|
139
|
+
let { requireCalls, cacheTime } = config || {};
|
|
139
140
|
let result = resolvedHTMLFile();
|
|
140
141
|
if (beforeEntryText.length > 0) {
|
|
141
142
|
let resolved: string[] = [];
|
|
@@ -166,7 +167,13 @@ class RequireControllerBase {
|
|
|
166
167
|
} else {
|
|
167
168
|
result = result.replace(ENTRY_TEMPLATE, "");
|
|
168
169
|
}
|
|
169
|
-
|
|
170
|
+
let headers: Record<string, string> = {
|
|
171
|
+
"Content-Type": "text/html"
|
|
172
|
+
};
|
|
173
|
+
if (cacheTime) {
|
|
174
|
+
headers["Cache-Control"] = `max-age=${Math.floor(cacheTime / 1000)}`;
|
|
175
|
+
}
|
|
176
|
+
return setHTTPResultHeaders(Buffer.from(result), headers);
|
|
170
177
|
}
|
|
171
178
|
|
|
172
179
|
public async getModules(
|
|
@@ -347,8 +354,14 @@ class RequireControllerBase {
|
|
|
347
354
|
}]),
|
|
348
355
|
};
|
|
349
356
|
let key = sha256Hash(JSON.stringify(simplifiedResult));
|
|
350
|
-
let
|
|
351
|
-
|
|
357
|
+
let uncompressedBuffer = Buffer.from(JSON.stringify(result));
|
|
358
|
+
let buffer = await compressCached(key, () => uncompressedBuffer);
|
|
359
|
+
setHTTPResultHeaders(buffer, {
|
|
360
|
+
"Content-Type": "application/json",
|
|
361
|
+
"Content-Encoding": "gzip",
|
|
362
|
+
"Content-Length": buffer.length.toString(),
|
|
363
|
+
"X-Uncompressed-Content-Length": uncompressedBuffer.length.toString(),
|
|
364
|
+
});
|
|
352
365
|
return buffer as any;
|
|
353
366
|
}
|
|
354
367
|
|
package/require/require.ts
CHANGED
|
@@ -1,11 +1,86 @@
|
|
|
1
1
|
import { GetModulesArgs, SerializedModule } from "./RequireController";
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
declare global {
|
|
5
|
+
var onProgressHandler: undefined | ((progress: {
|
|
6
|
+
type: string;
|
|
7
|
+
addValue: number;
|
|
8
|
+
addMax: number;
|
|
9
|
+
}) => void);
|
|
10
|
+
var onErrorHandler: undefined | ((error: string) => void);
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
var BOOT_TIME: number;
|
|
14
|
+
var builtInModuleExports: {
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
3
19
|
export function requireMain() {
|
|
4
|
-
|
|
20
|
+
async function requestText(endpoint: string, values: any) {
|
|
21
|
+
let url = new URL(endpoint);
|
|
22
|
+
|
|
23
|
+
let json = JSON.stringify(values);
|
|
24
|
+
let response: Response;
|
|
25
|
+
if (json.length < 6000) {
|
|
26
|
+
// NOTE: Try to use a GET, as GETs can be cached! However, if the data is too large,
|
|
27
|
+
// we have to use a post, or else the request url will be too large
|
|
28
|
+
for (let key in values) {
|
|
29
|
+
url.searchParams.set(key, JSON.stringify(values[key]));
|
|
30
|
+
}
|
|
31
|
+
response = await fetch(url.toString(), {
|
|
32
|
+
method: "GET",
|
|
33
|
+
credentials: "include",
|
|
34
|
+
});
|
|
35
|
+
} else {
|
|
36
|
+
response = await fetch(url.toString(), {
|
|
37
|
+
method: "POST",
|
|
38
|
+
body: json,
|
|
39
|
+
credentials: "include",
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let compressionRatio = 1;
|
|
44
|
+
let uncompressedLength = response.headers.get("X-Uncompressed-Content-Length");
|
|
45
|
+
let compressedLength = response.headers.get("Content-Length");
|
|
46
|
+
if (uncompressedLength && compressedLength) {
|
|
47
|
+
compressionRatio = +uncompressedLength / +compressedLength;
|
|
48
|
+
}
|
|
49
|
+
let totalLength = +(uncompressedLength || compressedLength || 0);
|
|
50
|
+
if (totalLength) {
|
|
51
|
+
globalThis.onProgressHandler?.({
|
|
52
|
+
type: "Download",
|
|
53
|
+
addValue: 0,
|
|
54
|
+
addMax: Math.floor(totalLength / compressionRatio),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// If it's an error, set requestError
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
globalThis.onErrorHandler?.(response.statusText);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Stream it, so we can get progrss
|
|
64
|
+
let reader = response.body!.getReader();
|
|
65
|
+
let result = "";
|
|
66
|
+
while (true) {
|
|
67
|
+
let { done, value } = await reader.read();
|
|
68
|
+
if (done) break;
|
|
69
|
+
result += new TextDecoder().decode(value);
|
|
70
|
+
let cur = (value?.length || 0) / compressionRatio;
|
|
71
|
+
globalThis.onProgressHandler?.({
|
|
72
|
+
type: "Download",
|
|
73
|
+
addValue: cur,
|
|
74
|
+
addMax: 0,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
5
80
|
|
|
6
81
|
const g = globalThis as any;
|
|
7
82
|
let startTime = Date.now();
|
|
8
|
-
|
|
83
|
+
globalThis.BOOT_TIME = startTime;
|
|
9
84
|
|
|
10
85
|
(Symbol as any).dispose = Symbol.dispose || Symbol("dispose");
|
|
11
86
|
(Symbol as any).asyncDispose = Symbol.asyncDispose || Symbol("asyncDispose");
|
|
@@ -54,7 +129,8 @@ export function requireMain() {
|
|
|
54
129
|
child_process: {},
|
|
55
130
|
events: {},
|
|
56
131
|
};
|
|
57
|
-
|
|
132
|
+
globalThis.builtInModuleExports = globalThis.builtInModuleExports || {};
|
|
133
|
+
Object.assign(globalThis.builtInModuleExports, builtInModuleExports);
|
|
58
134
|
|
|
59
135
|
let lastTime = 0;
|
|
60
136
|
function nextTime() {
|
|
@@ -286,6 +362,12 @@ export function requireMain() {
|
|
|
286
362
|
rootResolveCache[requests[i]] = requestsResolvedPaths[i];
|
|
287
363
|
}
|
|
288
364
|
|
|
365
|
+
globalThis.onProgressHandler?.({
|
|
366
|
+
type: "Compile",
|
|
367
|
+
addValue: 0,
|
|
368
|
+
addMax: Object.keys(modules).length,
|
|
369
|
+
});
|
|
370
|
+
|
|
289
371
|
// Store the function, so we only call it if it exists BEFORE we import
|
|
290
372
|
// (which means we already have something loading, so this is likely hot reloading...)
|
|
291
373
|
let observerOnHotReload = g.observerOnHotReload;
|
|
@@ -300,13 +382,13 @@ export function requireMain() {
|
|
|
300
382
|
let requireModuleCount = Object.values(modules).filter((x) => !x.source).length;
|
|
301
383
|
let dependenciesOnlyText = requireModuleCount ? ` (+${requireModuleCount} dependencies only)` : "";
|
|
302
384
|
console.log(
|
|
303
|
-
`%cimport(${requests.join(", ")}) finished download ${time}ms, ${Math.ceil(
|
|
385
|
+
`%cimport(${requests.join(", ")}) finished download ${time.toFixed(0)}ms, ${Math.ceil(
|
|
304
386
|
rawText.length / 1024
|
|
305
|
-
)}KB, ${moduleCount} modules${dependenciesOnlyText} at ${Date.now() - startTime}ms`,
|
|
306
|
-
"color:
|
|
387
|
+
)}KB, ${moduleCount} modules${dependenciesOnlyText} at ${(Date.now() - startTime).toFixed(0)}ms`,
|
|
388
|
+
"color: lightgreen"
|
|
307
389
|
);
|
|
308
390
|
|
|
309
|
-
|
|
391
|
+
|
|
310
392
|
|
|
311
393
|
if (alreadyHave?.requireSeqNumProcessId !== requireSeqNumProcessId) {
|
|
312
394
|
alreadyHave = undefined;
|
|
@@ -319,13 +401,37 @@ export function requireMain() {
|
|
|
319
401
|
serializedModules[id] = module;
|
|
320
402
|
}
|
|
321
403
|
|
|
404
|
+
time = Date.now();
|
|
405
|
+
let lastWaitTime = time;
|
|
406
|
+
for (let key of Object.keys(modules)) {
|
|
407
|
+
getModule(key, "preload");
|
|
408
|
+
// Wait, so we can render progress (and in generals, so the UI remains somewhat responsive)
|
|
409
|
+
if (Date.now() - lastWaitTime > 10) {
|
|
410
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
411
|
+
lastWaitTime = Date.now();
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
time = Date.now() - time;
|
|
416
|
+
console.log(
|
|
417
|
+
`%cimport(${requests.join(", ")}) finished compile ${time.toFixed(0)}ms (${moduleCount} modules) at ${(Date.now() - startTime).toFixed(0)}ms`,
|
|
418
|
+
"color: hotpink"
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
globalThis.onProgressHandler?.({
|
|
423
|
+
type: "Evaluate",
|
|
424
|
+
addValue: 0,
|
|
425
|
+
addMax: Object.keys(modules).length,
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
time = Date.now();
|
|
322
429
|
try {
|
|
323
430
|
return requestsResolvedPaths.map((x) => getModule(x));
|
|
324
431
|
} finally {
|
|
325
432
|
time = Date.now() - time;
|
|
326
433
|
console.log(
|
|
327
|
-
`%cimport(${requests.join(", ")}) finished evaluate ${time}ms (${moduleCount} modules) at ${Date.now() - startTime
|
|
328
|
-
}ms`,
|
|
434
|
+
`%cimport(${requests.join(", ")}) finished evaluate ${time.toFixed(0)}ms (${moduleCount} modules) at ${(Date.now() - startTime).toFixed(0)}ms`,
|
|
329
435
|
"color: lightblue"
|
|
330
436
|
);
|
|
331
437
|
}
|
|
@@ -392,7 +498,6 @@ export function requireMain() {
|
|
|
392
498
|
"color: red",
|
|
393
499
|
"color: unset"
|
|
394
500
|
);
|
|
395
|
-
debugger;
|
|
396
501
|
}
|
|
397
502
|
return rootRequire(resolvedPath);
|
|
398
503
|
}
|
|
@@ -495,12 +600,17 @@ export function requireMain() {
|
|
|
495
600
|
|
|
496
601
|
let currentModuleEvaluationStack: string[] = [];
|
|
497
602
|
// See https://nodejs.org/api/modules.html
|
|
498
|
-
function getModule(resolvedId: string) {
|
|
603
|
+
function getModule(resolvedId: string, preload?: "preload") {
|
|
499
604
|
if (resolvedId === "") {
|
|
500
605
|
return {};
|
|
501
606
|
}
|
|
502
607
|
if (resolvedId in moduleCache) {
|
|
503
|
-
|
|
608
|
+
let module = moduleCache[resolvedId];
|
|
609
|
+
if (!preload && !module.loaded) {
|
|
610
|
+
module.loaded = true;
|
|
611
|
+
module.load();
|
|
612
|
+
}
|
|
613
|
+
return module;
|
|
504
614
|
}
|
|
505
615
|
|
|
506
616
|
let serializedModule = serializedModules[resolvedId];
|
|
@@ -513,13 +623,25 @@ export function requireMain() {
|
|
|
513
623
|
module.exports.default = module.exports;
|
|
514
624
|
module.children = [];
|
|
515
625
|
for (let key in serializedModule.flags || {}) {
|
|
626
|
+
if (key === "loaded") continue;
|
|
516
627
|
module[key] = true;
|
|
517
628
|
}
|
|
518
629
|
|
|
519
630
|
module.load = load;
|
|
520
631
|
|
|
521
|
-
|
|
522
|
-
module.
|
|
632
|
+
let originalSource = serializedModule.source;
|
|
633
|
+
let moduleFnc = wrapSafe(module.id, originalSource);
|
|
634
|
+
|
|
635
|
+
globalThis.onProgressHandler?.({
|
|
636
|
+
type: "Compile",
|
|
637
|
+
addValue: 1,
|
|
638
|
+
addMax: 0,
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
if (!preload) {
|
|
642
|
+
module.loaded = true;
|
|
643
|
+
module.load();
|
|
644
|
+
}
|
|
523
645
|
|
|
524
646
|
function load() {
|
|
525
647
|
let serializedModule = serializedModules[resolvedId];
|
|
@@ -560,7 +682,10 @@ export function requireMain() {
|
|
|
560
682
|
module.size = source.length;
|
|
561
683
|
module.source = source;
|
|
562
684
|
|
|
563
|
-
|
|
685
|
+
if (source !== originalSource) {
|
|
686
|
+
originalSource = source;
|
|
687
|
+
moduleFnc = wrapSafe(module.id, originalSource);
|
|
688
|
+
}
|
|
564
689
|
|
|
565
690
|
let dirname = module.filename.replace(/\\/g, "/").split("/").slice(0, -1).join("/");
|
|
566
691
|
|
|
@@ -597,34 +722,15 @@ export function requireMain() {
|
|
|
597
722
|
} finally {
|
|
598
723
|
module.isPreloading = false;
|
|
599
724
|
currentModuleEvaluationStack.pop();
|
|
725
|
+
|
|
726
|
+
globalThis.onProgressHandler?.({
|
|
727
|
+
type: "Evaluate",
|
|
728
|
+
addValue: 1,
|
|
729
|
+
addMax: 0,
|
|
730
|
+
});
|
|
600
731
|
}
|
|
601
732
|
}
|
|
602
733
|
|
|
603
734
|
return module;
|
|
604
735
|
}
|
|
605
|
-
|
|
606
|
-
async function requestText(endpoint: string, values: any) {
|
|
607
|
-
let url = new URL(endpoint);
|
|
608
|
-
|
|
609
|
-
let json = JSON.stringify(values);
|
|
610
|
-
if (json.length < 6000) {
|
|
611
|
-
// NOTE: Try to use a GET, as GETs can be cached! However, if the data is too large,
|
|
612
|
-
// we have to use a post, or else the request url will be too large
|
|
613
|
-
for (let key in values) {
|
|
614
|
-
url.searchParams.set(key, JSON.stringify(values[key]));
|
|
615
|
-
}
|
|
616
|
-
let response = await fetch(url.toString(), {
|
|
617
|
-
method: "GET",
|
|
618
|
-
credentials: "include",
|
|
619
|
-
});
|
|
620
|
-
return await response.text();
|
|
621
|
-
} else {
|
|
622
|
-
let response = await fetch(url.toString(), {
|
|
623
|
-
method: "POST",
|
|
624
|
-
body: json,
|
|
625
|
-
credentials: "include",
|
|
626
|
-
});
|
|
627
|
-
return await response.text();
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
}
|
|
736
|
+
}
|
package/src/callHTTPHandler.ts
CHANGED
|
@@ -61,6 +61,53 @@ export async function httpCallHandler(request: http.IncomingMessage, response: h
|
|
|
61
61
|
// Don't keep alive, to prevent issues with zombie sockets.
|
|
62
62
|
response.setHeader("Connection", "close");
|
|
63
63
|
|
|
64
|
+
// CORS bs (due to having to explictly allow subdomains)
|
|
65
|
+
{
|
|
66
|
+
response.setHeader("Strict-Transport-Security", "max-age=63072000; includeSubDomains; preload");
|
|
67
|
+
response.setHeader("Cross-Origin-Opener-Policy", "same-origin-allow-popups");
|
|
68
|
+
// NOTE: "credentialless" would work here too
|
|
69
|
+
response.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
|
|
70
|
+
|
|
71
|
+
let origin = request.headers.origin || request.headers.referer;
|
|
72
|
+
let allowed = false;
|
|
73
|
+
if (!origin) {
|
|
74
|
+
// I guess it's a script, so just allow it (as it could easily set any header it wanted anyways)
|
|
75
|
+
allowed = true;
|
|
76
|
+
origin = "*";
|
|
77
|
+
} else {
|
|
78
|
+
let host = request.headers.host;
|
|
79
|
+
if (!host) {
|
|
80
|
+
throw new Error("Missing host in request headers");
|
|
81
|
+
}
|
|
82
|
+
function rootDomain(hostname: string) {
|
|
83
|
+
let parts = hostname.split(".");
|
|
84
|
+
if (parts.length > 2) {
|
|
85
|
+
return parts.slice(-2).join(".");
|
|
86
|
+
}
|
|
87
|
+
return hostname;
|
|
88
|
+
}
|
|
89
|
+
allowed = rootDomain(host) === rootDomain(origin);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Allow if it has no origin, as that means it isn't a CORS request?
|
|
93
|
+
if (allowed) {
|
|
94
|
+
response.setHeader("Cross-Origin-Resource-Policy", "cross-origin");
|
|
95
|
+
} else {
|
|
96
|
+
response.setHeader("Cross-Origin-Resource-Policy", "same-site");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
response.setHeader("vary", "Access-Control-Request-Headers");
|
|
100
|
+
response.setHeader("Access-Control-Allow-Origin", allowed ? origin : "");
|
|
101
|
+
|
|
102
|
+
if (allowed) {
|
|
103
|
+
response.setHeader("Access-Control-Allow-Credentials", "true");
|
|
104
|
+
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
|
105
|
+
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Content-Length, X-Requested-With, x-uncompressed-content-length, Cookie");
|
|
106
|
+
}
|
|
107
|
+
response.setHeader("Access-Control-Expose-Headers", "x-uncompressed-content-length");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
64
111
|
let urlBase = request.url;
|
|
65
112
|
if (!urlBase) {
|
|
66
113
|
throw new Error("Missing URL");
|
|
@@ -189,6 +236,7 @@ export async function httpCallHandler(request: http.IncomingMessage, response: h
|
|
|
189
236
|
return;
|
|
190
237
|
}
|
|
191
238
|
}
|
|
239
|
+
let uncompressedLength = resultBuffer.length;
|
|
192
240
|
if (SocketFunction.HTTP_COMPRESS && request.headers["accept-encoding"]?.includes("gzip") && !headers?.["Content-Encoding"]) {
|
|
193
241
|
// NOTE: This is a BIT slow. To speed it up, functions can use an internal cache, according to their function,
|
|
194
242
|
// and return a Buffer (which they can as any cast to make the returned type allowed, as returned Buffers will
|
|
@@ -205,6 +253,10 @@ export async function httpCallHandler(request: http.IncomingMessage, response: h
|
|
|
205
253
|
});
|
|
206
254
|
});
|
|
207
255
|
}
|
|
256
|
+
response.setHeader("Content-Length", resultBuffer.length.toString());
|
|
257
|
+
if (!response.getHeader("X-Uncompressed-Content-Length")) {
|
|
258
|
+
response.setHeader("X-Uncompressed-Content-Length", uncompressedLength.toString());
|
|
259
|
+
}
|
|
208
260
|
response.write(resultBuffer);
|
|
209
261
|
if (SocketFunction.logMessages) {
|
|
210
262
|
console.log(`HTTP response ${formatNumberSuffixed(resultBuffer.length)}B (${request.method}) ${url}`);
|
package/src/corsCheck.ts
ADDED
|
File without changes
|
package/temp.md.ai.temp
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"instructions": [
|
|
3
|
+
{
|
|
4
|
+
"messages": [
|
|
5
|
+
{
|
|
6
|
+
"role": "user",
|
|
7
|
+
"header": "# user",
|
|
8
|
+
"content": "How secure is comparing the Host and Origin headers, checking if they have the same root domain, and then adding \"Access-Control-Allow-Origin\"\n\n",
|
|
9
|
+
"slot": "",
|
|
10
|
+
"chStart": 0,
|
|
11
|
+
"chEnd": 7
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"params": {
|
|
15
|
+
"model": "openrouter-deepseek/deepseek-chat",
|
|
16
|
+
"max_tokens": 9999
|
|
17
|
+
},
|
|
18
|
+
"startIndex": 150,
|
|
19
|
+
"endIndex": 150
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"messages": [
|
|
23
|
+
{
|
|
24
|
+
"role": "user",
|
|
25
|
+
"header": "# user",
|
|
26
|
+
"content": "How secure is comparing the Host and Origin headers, checking if they have the same root domain, and then adding \"Access-Control-Allow-Origin\"\n\n",
|
|
27
|
+
"slot": "",
|
|
28
|
+
"chStart": 0,
|
|
29
|
+
"chEnd": 7
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"params": {
|
|
33
|
+
"model": "openrouter-deepseek/deepseek-r1",
|
|
34
|
+
"max_tokens": 9999
|
|
35
|
+
},
|
|
36
|
+
"startIndex": 219,
|
|
37
|
+
"endIndex": 219
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"rawResult": "Comparing the `Host` and `Origin` headers to check if they have the same root domain and then dynamically setting the `Access-Control-Allow-Origin` header can be a reasonable approach to enforce cross-origin resource sharing (CORS) policies, but it has its nuances and potential pitfalls. Here's a detailed analysis of its security implications:\n\n### How It Works\n1. **Host Header**: Represents the domain of the server receiving the request.\n2. **Origin Header**: Represents the domain from which the request originates (e.g., in a cross-origin request).\n3. **Root Domain Comparison**: Check if the `Origin` and `Host` share the same root domain (e.g., `example.com` and `api.example.com`).\n4. **Access-Control-Allow-Origin**: If they match, set this header to the value of the `Origin` header to allow the request.\n\n### Security Considerations\n\n#### **1. Same-Origin Policy**\nThis approach ensures that only requests from the same root domain are allowed, which aligns with the same-origin policy. This can prevent unauthorized cross-origin requests.\n\n#### **2. Root Domain Matching**\nComparing the root domain ensures that subdomains of the same root domain are allowed. For example, `api.example.com` and `app.example.com` share the same root domain (`example.com`). This can be useful for microservices or APIs hosted on subdomains.\n\n#### **3. Potential Misconfigurations**\n- **Weak Root Domain Matching**: If the root domain matching logic is not strict, it could lead to vulnerabilities. For example, improperly parsing domains could allow `example.com.evil.com` to be treated as the same root domain.\n- **HTTP Header Spoofing**: If the server blindly trusts the `Origin` header, an attacker could spoof it to bypass CORS restrictions. Always validate the `Origin` header against a whitelist of allowed domains.\n\n#### **4. Security Best Practices**\n- **Whitelist Allowed Origins**: Instead of dynamically allowing any origin that matches the root domain, maintain a whitelist of trusted origins. This reduces the risk of allowing unintended domains.\n- **Validate Headers**: Ensure that the `Origin` and `Host` headers are properly validated to prevent spoofing or injection attacks.\n- **Use HTTPS**: Always enforce HTTPS to prevent man-in-the-middle attacks that could tamper with headers.\n- **Limit Scopes**: Only allow CORS for specific endpoints that require it, and restrict methods (e.g., `GET`, `POST`) and headers as needed.\n- **Avoid Wildcards**: Avoid using `Access-Control-Allow-Origin: *` unless absolutely necessary, as it allows access from any domain.\n\n#### **5. Additional Security Headers**\n- **Access-Control-Allow-Credentials**: Only set this to `true` if the request includes credentials (e.g., cookies) and you explicitly need to allow them.\n- **Vary: Origin**: Include this header to prevent caching issues with CORS responses.\n\n### Example Implementation (Pseudocode)\n```pseudocode\nif request.headers['Origin'] is not null:\n root_domain_origin = extract_root_domain(request.headers['Origin'])\n root_domain_host = extract_root_domain(request.headers['Host'])\n \n if root_domain_origin == root_domain_host:\n response.headers['Access-Control-Allow-Origin'] = request.headers['Origin']\n response.headers['Vary'] = 'Origin'\n else:\n deny_request()\nelse:\n deny_request()\n```\n\n### Conclusion\nThis approach can be secure if implemented carefully, but it's essential to follow best practices like strict domain validation, whitelisting origins, and enforcing HTTPS. Always test your CORS implementation thoroughly to ensure it doesn't introduce vulnerabilities."
|
|
41
|
+
}
|