I'm encountering ECONNRESET errors that are crashing my node server when I run a Nessus Essentials basic network scan:
node:events:505
throw er; // Unhandled 'error' event
^
Error: read ECONNRESET
at TCP.onStreamRead (node:internal/stream_base_commons:217:20)
Emitted 'error' event on Socket instance at:
at emitErrorNT (node:internal/streams/destroy:157:8)
at emitErrorCloseNT (node:internal/streams/destroy:122:3)
at processTicksAndRejections (node:internal/process/task_queues:83:21) {
errno: -54,
code: 'ECONNRESET',
syscall: 'read'
}
I don't know exactly what Nessus is doing and I haven't managed to reproduce the error any other way, but I've identified two interesting facts:
- The problem only occurs when running on Node versions < 16.16.0
- It also goes away if I remove socket.io, or if I instantiate socket.io using a different port or HTTP server than my express server
For context, I've simplified my application down to a minimal project still exhibits the error:
import express from 'express';
import { Server } from 'socket.io';
import morgan from 'morgan';
import winston from 'winston';
const logger = winston.createLogger({
level: 'debug',
format: winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss:ms' }),
winston.format.splat(),
winston.format.prettyPrint(),
winston.format(info => {
info.level = `[${info.level.toUpperCase()}]`;
return info;
})(),
winston.format.printf(info => {
if (typeof info.message === 'object') {
info.message = JSON.stringify(info.message, null, 3);
}
return `${info.timestamp} ${info.level} ${info.message}`;
}),
),
transports: [new winston.transports.Console()]
});
const morganMiddleware = morgan(
':remote-addr :method :url :status :response-time ms',
{
stream: {
write: message => logger.http(message.trim()),
},
});
const app = express();
app.use(morganMiddleware);
const port = 3001;
const httpServer = app.listen(port, () => {
logger.info(
`Server is now listening on port ${port} ✓`,
);
new Server(httpServer);
});
export default app;
If I remove the new Server(httpServer);
line, the app doesn't crash. So the error seems to be linked to socket.io and Express sharing a connection, and something going wrong during the vulnerability scan (something causing the socket to be dropped?), but I haven't managed to debug it further.
I have tried all sorts of ways of catching the error, but the only way that works is using process.on('uncaughtException')
. That's not helpful however, because at that point, there is no safe way to recover from the error. The error bypasses all the error handling of both Express and socket.io.
I could of course "solve" the problem by upgrading to a more recent version of node, but I need to understand the problem in order to be sure that it's actually fixed and won't surface again in some other form. Also, I would like to be able to make my app more resilient by catching this sort of error if they were to occur in the future.
Or perhaps there's some way to separate socket.io from express that without using separate ports (which wouldn't be practical in my use-case). Can I use proxy websocket related requests through Express to socket.io without sharing an HTTP server?
Any suggestions, either to help understand/debug the problem, or to work around it, would be welcome.