sc-idp/lib/ldap/vendor.js
2026-06-01 16:40:54 -05:00

62 lines
2.8 KiB
JavaScript

// Single-require chokepoint + hardening WRAPPER for the PINNED ldapjs dependency
// (an npm package, NOT a vendored source copy/fork -- see VENDORING.md). This is
// the ONLY file that require()s ldapjs, so it is the one upgrade/audit point.
// Our hardening lives in OUR code at a layer we control: a
// per-connection inbound byte cap here (the BER parser has no size limit, so a
// declared multi-GB message would buffer unbounded), the per-IP bind lockout in
// harden.js, and the filter-nesting-depth guard in search.js. Re-exporting the
// error classes keeps the handlers off a direct ldapjs import.
const ldap = require("ldapjs");
const constants = require("../constants");
// createServer with a connectionRouter that caps inbound bytes PER LDAP MESSAGE
// and then hands off to ldapjs's own connection setup. ldapjs uses
// options.connectionRouter in place of its internal newConnection, so the router
// MUST call server.newConnection(conn) or no parser is wired up. The cap is reset
// on each parsed-message boundary (conn.parser emits 'message'): the goal is to
// bound a single unbounded BER message (the parser has no size limit), NOT to
// impose a connection-lifetime quota -- a per-connection counter would let an
// attacker kill a legitimate long-lived connection after a few normal operations
// (sequential binds/searches that each fit the cap but cumulatively exceed it).
const createHardenedServer = (opts) => {
let server;
const options = Object.assign({}, opts, {
connectionRouter: (conn) => {
server.newConnection(conn);
// Idle timeout: drop a connection that goes silent so slow-loris /
// idle sockets cannot hoard the (capped) connection pool. ldapjs never
// calls setTimeout itself, so without this connections live forever.
if (typeof conn.setTimeout === "function") {
conn.setTimeout(constants.LDAP_IDLE_TIMEOUT_MS);
conn.on("timeout", () => conn.destroy());
}
let total = 0;
if (conn.parser && typeof conn.parser.on === "function") {
conn.parser.on("message", () => {
total = 0;
});
}
conn.on("data", (chunk) => {
total += chunk.length;
if (total > constants.LDAP_MAX_MSG_BYTES) {
conn.destroy();
}
});
}
});
server = ldap.createServer(options);
return server;
};
module.exports = {
createHardenedServer,
InvalidCredentialsError: ldap.InvalidCredentialsError,
InsufficientAccessRightsError: ldap.InsufficientAccessRightsError,
OperationsError: ldap.OperationsError,
ProtocolError: ldap.ProtocolError,
SizeLimitExceededError: ldap.SizeLimitExceededError
};