From 8f743b34d68a001771e725e9427836fc04605e9f Mon Sep 17 00:00:00 2001 From: ch-asimakopoulos Date: Fri, 31 Jul 2026 17:42:47 +0300 Subject: [PATCH] Fix hoek prototype pollution in lib via @hapi/joi and @hapi/boom Chain 3 of 4. Resolves alert #33 (hoek, prototype pollution via the clone function) in lib/package-lock.json. hoek was not a direct dependency; it arrived by three paths, all of which are now gone: boom 7.3.0 -> hoek 6.1.3 joi 14.3.1 -> hoek 6.1.3 (and via topo 3.0.3) koa-joi-validate -> joi 10.6.0 -> hoek 4.3.1 (and via topo 2.1.1) Changes: boom ^7.3.0 -> @hapi/boom ^10.0.1 (-> @hapi/hoek 11.0.7) joi ^14.3.1 -> @hapi/joi ^15.1.1 (-> @hapi/hoek 8.5.1) koa-joi-validate ^0.5.1 -> removed The unscoped boom and joi packages were deprecated in favour of the @hapi/* scope years ago and never received the hoek fix. koa-joi-validate is removed because it is dead weight: nothing in lib requires it, and it pins joi at exactly 10.6.0, which dragged in the oldest hoek in the tree. @hapi/joi 15 rather than the current joi 18 deliberately. joi 16 removed Joi.validate(), removed schema.isJoi and renamed describe().children to describe().keys - all three of which this code depends on: lib/validate.js:29 joi.validate(ctx.request.body, schema, opts) lib/build.js:78 if (!joiSchema.isJoi) lib/build.js:82 joiSchema.describe().children @hapi/joi 15 is the last release keeping that surface, so clearing the advisory needs no code migration and, more importantly, does not break the schemas that consumers of apicco-lib write in their own api files, or change the /discovery endpoint's output. apicco-lib stays on 1.x. The trade-off is that @hapi/joi 15 is itself end-of-life and will not receive future fixes. It is currently clean of advisories, but a later migration to joi 18 with an apicco-lib 2.0.0 release is the real long-term fix. Source changes are only the require paths, in build.js, validate.js, build.test.js and validate.test.js. Every call site is unchanged, and each was verified directly against @hapi/joi 15: isJoi returns true, describe().children still yields the key map, Joi.validate still returns { error, value } with error.details[].type and .path, and Boom.badData / Boom.notFound still map to 422 / 404. The hoek advisory is cleared, and so is the moderate joi link() RangeError, which @hapi/joi 15 does not have. Only brace-expansion remains, and it has no patched version at any release. 9/9 tests pass with the snapshot unchanged, including the specs that assert joi's error messages. A full runtime tree diff shows only the intended swap: the boom, joi and koa-joi-validate subtrees are replaced by @hapi/boom and @hapi/joi, with koa-router, koa-compose, lodash and require-directory untouched. Note that examples/ still uses joi 13 and boom 7 and passes its schemas into this code, so chain 4 migrates it to match. Co-Authored-By: Claude Opus 5 (1M context) --- lib/build.js | 4 +- lib/build.test.js | 4 +- lib/package-lock.json | 144 ++++++++++++++++-------------------------- lib/package.json | 5 +- lib/validate.js | 2 +- lib/validate.test.js | 2 +- 6 files changed, 64 insertions(+), 97 deletions(-) diff --git a/lib/build.js b/lib/build.js index a0c2d37..77eea05 100644 --- a/lib/build.js +++ b/lib/build.js @@ -1,6 +1,6 @@ const path = require('path'); -const Boom = require('boom'); -const Joi = require('joi'); +const Boom = require('@hapi/boom'); +const Joi = require('@hapi/joi'); const Router = require('koa-router'); const compose = require('koa-compose'); const enforceBodyValidations = require('./validate'); diff --git a/lib/build.test.js b/lib/build.test.js index 091d359..4a75215 100644 --- a/lib/build.test.js +++ b/lib/build.test.js @@ -16,7 +16,7 @@ describe('buildAPI(opts)', () => { beforeAll(() => { this.mockedDirectory = jest.fn(() => { - const Joi = require('joi'); + const Joi = require('@hapi/joi'); return { movies: { @@ -95,7 +95,7 @@ describe('buildAPI(opts)', () => { const next = jest.fn(); this.mockedDirectory = jest.fn(() => { - const Joi = require('joi'); + const Joi = require('@hapi/joi'); return { movies: { diff --git a/lib/package-lock.json b/lib/package-lock.json index 98a77fe..7389ff8 100644 --- a/lib/package-lock.json +++ b/lib/package-lock.json @@ -388,6 +388,62 @@ "tslib": "^2.4.0" } }, + "@hapi/address": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", + "integrity": "sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ==" + }, + "@hapi/boom": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", + "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", + "requires": { + "@hapi/hoek": "^11.0.2" + } + }, + "@hapi/bourne": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-1.3.2.tgz", + "integrity": "sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA==" + }, + "@hapi/hoek": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.7.tgz", + "integrity": "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==" + }, + "@hapi/joi": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-15.1.1.tgz", + "integrity": "sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ==", + "requires": { + "@hapi/address": "2.x.x", + "@hapi/bourne": "1.x.x", + "@hapi/hoek": "8.x.x", + "@hapi/topo": "3.x.x" + }, + "dependencies": { + "@hapi/hoek": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.1.tgz", + "integrity": "sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow==" + } + } + }, + "@hapi/topo": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.6.tgz", + "integrity": "sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ==", + "requires": { + "@hapi/hoek": "^8.3.0" + }, + "dependencies": { + "@hapi/hoek": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.1.tgz", + "integrity": "sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow==" + } + } + }, "@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1171,14 +1227,6 @@ "integrity": "sha512-zAgkquC2WYF0PIc6XbNYkA2uuxxFavzgmX61R+dHDUa558V8Ejf8ozTZFR6QzM24RWu4kBcRkhJ5kpz77j9fnQ==", "dev": true }, - "boom": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/boom/-/boom-7.3.0.tgz", - "integrity": "sha512-Swpoyi2t5+GhOEGw8rEsKvTxFLIDiiKoUc2gsoV6Lyr43LHBIzch3k2MvYUs8RTROrIkVJ3Al0TkaOGjnb+B6A==", - "requires": { - "hoek": "6.x.x" - } - }, "brace-expansion": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", @@ -1594,11 +1642,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "hoek": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.3.tgz", - "integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==" - }, "html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -1683,14 +1726,6 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, - "isemail": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz", - "integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==", - "requires": { - "punycode": "2.x.x" - } - }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -1767,11 +1802,6 @@ "istanbul-lib-report": "^3.0.0" } }, - "items": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/items/-/items-2.1.2.tgz", - "integrity": "sha512-kezcEqgB97BGeZZYtX/MA8AG410ptURstvnz5RAgyFZ8wQFPMxHY8GpTq+/ZHKT3frSlIthUq7EvLt9xn3TvXg==" - }, "jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", @@ -2213,16 +2243,6 @@ } } }, - "joi": { - "version": "14.3.1", - "resolved": "https://registry.npmjs.org/joi/-/joi-14.3.1.tgz", - "integrity": "sha512-LQDdM+pkOrpAn4Lp+neNIFV3axv1Vna3j38bisbQhETPMANYRbFJFUyOZcOClYvM/hppMhGWuKSFEK9vjrB+bQ==", - "requires": { - "hoek": "6.x.x", - "isemail": "3.x.x", - "topo": "3.x.x" - } - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -2262,45 +2282,6 @@ "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==" }, - "koa-joi-validate": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/koa-joi-validate/-/koa-joi-validate-0.5.1.tgz", - "integrity": "sha512-ey9o3/ftd9GapP7g5rvMzrDFkDowZ17yJN6SKe/kGPymeakAkWD/SSagLiHZhUkb1jY73UcVVlEyUPOnZo7Uyg==", - "requires": { - "joi": "10.6.0" - }, - "dependencies": { - "hoek": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.3.1.tgz", - "integrity": "sha512-v7E+yIjcHECn973i0xHm4kJkEpv3C8sbYS4344WXbzYqRyiDD7rjnnKo4hsJkejQBAFdRMUGNHySeSPKSH9Rqw==" - }, - "isemail": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/isemail/-/isemail-2.2.1.tgz", - "integrity": "sha1-A1PT2aYpUQgMJiwqoKQrjqjp4qY=" - }, - "joi": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-10.6.0.tgz", - "integrity": "sha512-hBF3LcqyAid+9X/pwg+eXjD2QBZI5eXnBFJYaAkH4SK3mp9QSRiiQnDYlmlz5pccMvnLcJRS4whhDOTCkmsAdQ==", - "requires": { - "hoek": "4.x.x", - "isemail": "2.x.x", - "items": "2.x.x", - "topo": "2.x.x" - } - }, - "topo": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/topo/-/topo-2.1.1.tgz", - "integrity": "sha512-ZPrPP5nwzZy1fw9abHQH2k+YarTgp9UMAztcB3MmlcZSif63Eg+az05p6wTDaZmnqpS3Mk7K+2W60iHarlz8Ug==", - "requires": { - "hoek": "4.x.x" - } - } - } - }, "koa-router": { "version": "7.4.0", "resolved": "https://registry.npmjs.org/koa-router/-/koa-router-7.4.0.tgz", @@ -2623,11 +2604,6 @@ } } }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, "pure-rand": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", @@ -2931,14 +2907,6 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, - "topo": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.3.tgz", - "integrity": "sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ==", - "requires": { - "hoek": "6.x.x" - } - }, "tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", diff --git a/lib/package.json b/lib/package.json index 24f584e..0485d69 100644 --- a/lib/package.json +++ b/lib/package.json @@ -26,10 +26,9 @@ "node": "~8.11.4" }, "dependencies": { - "boom": "^7.3.0", - "joi": "^14.3.1", + "@hapi/boom": "^10.0.1", + "@hapi/joi": "^15.1.1", "koa-compose": "^4.1.0", - "koa-joi-validate": "^0.5.1", "koa-router": "^7.4.0", "lodash": "^4.18.1", "require-directory": "^2.1.1" diff --git a/lib/validate.js b/lib/validate.js index 2130c56..3295e20 100644 --- a/lib/validate.js +++ b/lib/validate.js @@ -1,4 +1,4 @@ -const Boom = require('boom'); +const Boom = require('@hapi/boom'); const merge = require('lodash/merge'); const isEmpty = require('lodash/isEmpty'); diff --git a/lib/validate.test.js b/lib/validate.test.js index f797b85..801ee0b 100644 --- a/lib/validate.test.js +++ b/lib/validate.test.js @@ -1,4 +1,4 @@ -const Joi = require('joi'); +const Joi = require('@hapi/joi'); const validate = require('./validate'); describe('validate({ schema, joi, joiOptions }) middleware', () => {