socket-function 1.2.25 → 1.2.27

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "socket-function",
3
- "version": "1.2.25",
3
+ "version": "1.2.27",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "dependencies": {
@@ -13,6 +13,9 @@ import { createSingleton } from "./createSingleton";
13
13
  // the HTTP handler of any other copy. See createSingleton.
14
14
  const defaultHTTPCall = createSingleton("callHTTPHandler.defaultHTTPCall", 1, () => ({ call: undefined as CallType | undefined }));
15
15
 
16
+ // Statuses where the spec forbids a message body, so we must not write resultBuffer
17
+ const BODYLESS_STATUS_CODES = new Set([204, 205, 304]);
18
+
16
19
  export function setDefaultHTTPCall(call: CallType) {
17
20
  defaultHTTPCall.get().call = call;
18
21
  }
@@ -63,16 +66,29 @@ export async function httpCallHandler(request: http.IncomingMessage, response: h
63
66
  try {
64
67
  // Always set x-frame-options, to prevent iframe embedding click hijacking
65
68
  response.setHeader("X-Frame-Options", "SAMEORIGIN");
69
+ // Only frame-ancestors, as a full CSP would break our eval-based module loading
70
+ response.setHeader("Content-Security-Policy", "frame-ancestors 'self'");
71
+ response.setHeader("X-Content-Type-Options", "nosniff");
72
+ // Without this, the full URL (including classGuid/functionName/args in the query) leaks in the Referer to any third-party resource
73
+ response.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
66
74
  // Don't keep alive, to prevent issues with zombie sockets.
67
75
  response.setHeader("Connection", "close");
68
76
 
69
77
  // CORS bs (due to having to explictly allow subdomains)
78
+ let corsAllowed = false;
70
79
  {
71
80
  response.setHeader("Strict-Transport-Security", "max-age=63072000; includeSubDomains; preload");
72
81
  response.setHeader("Cross-Origin-Opener-Policy", SocketFunction.COOP);
73
82
  response.setHeader("Cross-Origin-Embedder-Policy", SocketFunction.COEP);
74
83
 
75
- let origin = request.headers.origin || request.headers.referer;
84
+ let origin = request.headers.origin;
85
+ if (!origin && request.headers.referer) {
86
+ // Referer is a full URL, but Access-Control-Allow-Origin must be a bare origin
87
+ try {
88
+ origin = new URL(request.headers.referer).origin;
89
+ } catch {
90
+ }
91
+ }
76
92
  let allowed = false;
77
93
  if (!origin) {
78
94
  // I guess it's a script, so just allow it (as it could easily set any header it wanted anyways)
@@ -98,15 +114,28 @@ export async function httpCallHandler(request: http.IncomingMessage, response: h
98
114
  response.setHeader("Cross-Origin-Resource-Policy", "same-site");
99
115
  }
100
116
 
101
- response.setHeader("vary", "Access-Control-Request-Headers");
117
+ response.setHeader("Vary", "Origin, Access-Control-Request-Headers");
102
118
  response.setHeader("Access-Control-Allow-Origin", allowed ? origin : "");
103
119
 
104
- if (allowed) {
120
+ // Browsers reject Allow-Credentials combined with a "*" origin, so don't emit that pair
121
+ if (allowed && origin !== "*") {
105
122
  response.setHeader("Access-Control-Allow-Credentials", "true");
106
123
  response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
107
124
  response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Content-Length, X-Requested-With, x-uncompressed-content-length, Cookie");
108
125
  }
109
126
  response.setHeader("Access-Control-Expose-Headers", "x-uncompressed-content-length");
127
+ corsAllowed = allowed;
128
+ }
129
+
130
+ // Preflights must never run the underlying call (they also arrive without credentials, so the call would be wrong anyways)
131
+ if (request.method === "OPTIONS") {
132
+ response.writeHead(204);
133
+ return;
134
+ }
135
+ // The browser discards the response for disallowed origins anyways, so running the call would only produce side effects the caller can't even see
136
+ if (!corsAllowed) {
137
+ response.writeHead(403);
138
+ return;
110
139
  }
111
140
 
112
141
 
@@ -227,14 +256,24 @@ export async function httpCallHandler(request: http.IncomingMessage, response: h
227
256
 
228
257
  if (headers) {
229
258
  for (let headerName in headers) {
259
+ // "status" is the status line, not a header, so don't emit it on the wire
260
+ if (headerName.toLowerCase() === "status") continue;
230
261
  response.setHeader(headerName, headers[headerName]);
231
262
  }
232
- let status = headers["status"];
263
+ let status = headers["status"] ?? headers["Status"];
233
264
  if (status) {
234
- response.writeHead(+status);
235
- return;
265
+ // Only set statusCode (NOT writeHead), as writeHead locks the header block, which would
266
+ // drop the Content-Type / Content-Length / compression handling below
267
+ response.statusCode = +status;
268
+ if (BODYLESS_STATUS_CODES.has(+status) || +status < 200) {
269
+ return;
270
+ }
236
271
  }
237
272
  }
273
+ // With nosniff set, an untyped response is unusable to the browser, so results without explicit headers need their actual type (JSON) declared
274
+ if (!response.getHeader("Content-Type")) {
275
+ response.setHeader("Content-Type", "application/json");
276
+ }
238
277
  let uncompressedLength = resultBuffer.length;
239
278
  if (SocketFunction.HTTP_COMPRESS && request.headers["accept-encoding"]?.includes("gzip") && !headers?.["Content-Encoding"]) {
240
279
  // NOTE: This is a BIT slow. To speed it up, functions can use an internal cache, according to their function,