Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
88005bd705 fix: HTTPS req.socket instanceof TLSSocket returns true (#16834)
Make `NodeHTTPServerSocket` instances report correct prototype chains:
- HTTPS connections: `instanceof tls.TLSSocket` and `instanceof net.Socket` return `true`
- HTTP connections: `instanceof net.Socket` returns `true`

This matches Node.js behavior where HTTPS server request sockets are
`TLSSocket` instances and HTTP server request sockets are `net.Socket`
instances.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-20 02:28:08 +00:00
2 changed files with 144 additions and 0 deletions

View File

@@ -822,6 +822,13 @@ const NodeHTTPServerSocket = class Socket extends Duplex {
#pendingCallback = null;
constructor(server: Server, handle, encrypted) {
super();
// Switch prototype chain based on whether this is a TLS connection
// so that instanceof checks match Node.js behavior.
if (encrypted) {
Object.setPrototypeOf(this, getTLSSocketProto());
} else {
Object.setPrototypeOf(this, getNetSocketProto());
}
this.server = server;
this[kHandle] = handle;
this._secureEstablished = !!handle?.secureEstablished;
@@ -1107,6 +1114,42 @@ const NodeHTTPServerSocket = class Socket extends Duplex {
}
} as unknown as typeof import("node:net").Socket;
// Create alternative prototype chains so that:
// - For HTTPS connections: req.socket instanceof TLSSocket === true
// - For HTTP connections: req.socket instanceof net.Socket === true
// This matches Node.js behavior (see issue #16834).
const NodeHTTPServerSocketPrototype = NodeHTTPServerSocket.prototype;
// Lazily resolve prototypes to avoid circular dependency issues at module load time.
let _netSocketProto: object | undefined;
let _tlsSocketProto: object | undefined;
function getNetSocketProto() {
if (!_netSocketProto) {
const { Socket } = require("node:net");
// Create a new prototype that has all NodeHTTPServerSocket methods
// but inherits from net.Socket.prototype instead of Duplex.prototype
_netSocketProto = Object.create(Socket.prototype);
const descriptors = Object.getOwnPropertyDescriptors(NodeHTTPServerSocketPrototype);
delete descriptors.constructor;
Object.defineProperties(_netSocketProto, descriptors);
}
return _netSocketProto;
}
function getTLSSocketProto() {
if (!_tlsSocketProto) {
const { TLSSocket } = require("node:tls");
// Create a new prototype that has all NodeHTTPServerSocket methods
// but inherits from TLSSocket.prototype (which itself inherits from net.Socket.prototype)
_tlsSocketProto = Object.create(TLSSocket.prototype);
const descriptors = Object.getOwnPropertyDescriptors(NodeHTTPServerSocketPrototype);
delete descriptors.constructor;
Object.defineProperties(_tlsSocketProto, descriptors);
}
return _tlsSocketProto;
}
function _writeHead(statusCode, reason, obj, response) {
const originalStatusCode = statusCode;
let hasContentLength = response.hasHeader("content-length");

View File

@@ -0,0 +1,101 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe, tls } from "harness";
test("HTTPS req.socket instanceof TLSSocket", async () => {
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`
const https = require("node:https");
const tls = require("node:tls");
const net = require("node:net");
const server = https.createServer(
{ cert: process.env.TLS_CERT, key: process.env.TLS_KEY },
(req, res) => {
const results = {
instanceOfTLSSocket: req.socket instanceof tls.TLSSocket,
instanceOfNetSocket: req.socket instanceof net.Socket,
encrypted: req.socket.encrypted,
};
res.end(JSON.stringify(results));
server.close();
}
);
server.listen(0, () => {
const port = server.address().port;
https.get("https://localhost:" + port, { rejectUnauthorized: false }, (res) => {
let data = "";
res.on("data", (chunk) => data += chunk);
res.on("end", () => {
console.log(data);
});
});
});
`,
],
env: { ...bunEnv, TLS_CERT: tls.cert, TLS_KEY: tls.key },
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
if (exitCode !== 0) {
throw new Error(`Process exited with code ${exitCode}: ${stderr}`);
}
const results = JSON.parse(stdout.trim());
expect(results.instanceOfTLSSocket).toBe(true);
expect(results.instanceOfNetSocket).toBe(true);
expect(results.encrypted).toBe(true);
});
test("HTTP req.socket instanceof net.Socket", async () => {
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`
const http = require("node:http");
const tls = require("node:tls");
const net = require("node:net");
const server = http.createServer((req, res) => {
const results = {
instanceOfTLSSocket: req.socket instanceof tls.TLSSocket,
instanceOfNetSocket: req.socket instanceof net.Socket,
encrypted: !!req.socket.encrypted,
};
res.end(JSON.stringify(results));
server.close();
});
server.listen(0, () => {
const port = server.address().port;
http.get("http://localhost:" + port, (res) => {
let data = "";
res.on("data", (chunk) => data += chunk);
res.on("end", () => {
console.log(data);
});
});
});
`,
],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
if (exitCode !== 0) {
throw new Error(`Process exited with code ${exitCode}: ${stderr}`);
}
const results = JSON.parse(stdout.trim());
expect(results.instanceOfTLSSocket).toBe(false);
expect(results.instanceOfNetSocket).toBe(true);
expect(results.encrypted).toBe(false);
});