Waarom toString een lijst werd in twee Node.js HTTP-adapters

Inversify is een dependency-injection-library voor TypeScript. De HTTP-pakketten hebben adapters voor Express, Fastify, Hono en uWebSockets. Ik vond deze bug toen ik vergeleek hoe de adapters querystrings en formulieren uitlezen.

De bug

De Hono- en uWebSockets-adapters verzamelden formuliervelden in een gewoon object, en de uWebSockets-adapter deed dat ook voor queryparameters. Om herhaalde sleutels zoals tag=a&tag=b te ondersteunen, keek de parser of een sleutel al een waarde had.

if (parsedBody[key] === undefined) {
  parsedBody[key] = value;
} else if (Array.isArray(parsedBody[key])) {
  parsedBody[key].push(value);
} else {
  parsedBody[key] = [parsedBody[key], value];
}

Een gewoon object erft van Object.prototype. Bij een sleutel als toString of constructor is parsedBody[key] niet undefined maar de geërfde functie. De eerste waarde werd daardoor als herhaling gezien, en het resultaat was een lijst met de functie en de waarde. De Express-adapter gaf voor hetzelfde verzoek gewoon toString: 'x' terug.

De oplossing

De query van uWebSockets komt nu in een object dat gemaakt is met Object.create(null). Dat heeft geen prototype, net als de query-objecten van de andere adapters. De body-parsers houden een gewoon object, controleren sleutels met Object.hasOwn en voegen nieuwe sleutels toe met Object.defineProperty.

const existingValue = Object.hasOwn(parsedBody, key) ? parsedBody[key] : undefined;

if (existingValue === undefined) {
  Object.defineProperty(parsedBody, key, {
    configurable: true,
    enumerable: true,
    value,
    writable: true,
  });
} else if (Array.isArray(existingValue)) {
  existingValue.push(value);
} else {
  parsedBody[key] = [existingValue, value];
}

Die defineProperty is nodig voor één sleutel. Een gewone toewijzing zoals parsedBody['__proto__'] = 'z' maakt geen veld aan. Het roept de __proto__-setter aan, die een string negeert, dus het veld verdwijnt zonder foutmelding. defineProperty maakt altijd een eigen property aan.

De maintainer vroeg om tests met een herhaalde prototype-sleutel en __proto__. De uiteindelijke tests sturen toString=x&toString=y&__proto__=z en verwachten dat toString gelijk is aan ["x", "y"] en __proto__ aan "z". Zonder defineProperty faalt de __proto__-test.

Wat je ervan meeneemt

Bouw je een object op uit gebruikersinvoer, controleer sleutels dan met Object.hasOwn in plaats van obj[key] === undefined. Heeft het object geen prototype-methodes nodig, begin dan met Object.create(null).

De oplossing is gemerged in inversify/monorepo#2166.