diff --git a/.changeset/knex-3-2-9-security.md b/.changeset/knex-3-2-9-security.md new file mode 100644 index 000000000..690487eca --- /dev/null +++ b/.changeset/knex-3-2-9-security.md @@ -0,0 +1,22 @@ +--- +--- + +fix(deps): bump knex to 3.2.9 for the search_path SQL injection (AIKIDO-2026-10469) + +knex 0.0.1–3.2.8 interpolates values passed to `searchPath` for the Postgres +dialect into generated SQL without validating or escaping them, so a value an +attacker can influence becomes an injected identifier. 3.2.9 escapes the double +quotes (#6411). This branch still declares `knex` 2.4.2 and still runs it: +`src/database/client.ts`, `src/clean-db.ts`, `src/cli/commands/invite.ts` and +`crates/nostream-storage/tests/capture/gen-storage-parity.mjs` all import it. + +nostream never sets `searchPath` — `grep -rn "search_path" src/` finds nothing — +so the reachable path is not exercised today, and the bump is taken to remove a +vulnerable dependency, not to close a reachable hole. + +knex 3.0.0 drops Node < 16, which the repo already requires (>=24.14.1), and 3.2.0 +tightens `RawBinding`. The one compile error that surfaced, +`src/scripts/benchmark-queries.ts` passing `bindings as readonly unknown[]` to +`db.raw()`, is fixed by dropping the assertion: `toSQL().bindings` is already the +accepted type. The emitted JavaScript is byte-identical with and without the +assertion. diff --git a/package.json b/package.json index 050fa91b9..fe389e4ad 100644 --- a/package.json +++ b/package.json @@ -179,7 +179,7 @@ "colorette": "^2.0.20", "express": "4.22.1", "js-yaml": "4.2.0", - "knex": "2.4.2", + "knex": "3.2.9", "ora": "^9.3.0", "pg": "8.9.0", "pg-query-stream": "4.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fbfa52202..0614ba4b8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,8 +48,8 @@ importers: specifier: 4.2.0 version: 4.2.0 knex: - specifier: 2.4.2 - version: 2.4.2(pg@8.9.0) + specifier: 3.2.9 + version: 3.2.9(pg-query-stream@4.3.0(pg@8.9.0))(pg@8.9.0) ora: specifier: ^9.3.0 version: 9.4.0 @@ -1234,10 +1234,6 @@ packages: resolution: {integrity: sha512-i0/MaqBtdbnJ4XQs4Pmyb+oFQl+q0lsAmokVUH92SlSw4fkeAcG3bVon+Qt7hmtF+u3Het6o4VgrcY3qAoEB6w==} engines: {node: ^12.20.0 || >=14} - commander@9.5.0: - resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} - engines: {node: ^12.20.0 || >=14} - commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} @@ -2054,9 +2050,9 @@ packages: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} - knex@2.4.2: - resolution: {integrity: sha512-tMI1M7a+xwHhPxjbl/H9K1kHX+VncEYcvCx5K00M16bWvpYPKAZd6QrCu68PtHAdIZNQPWZn0GVhqVBEthGWCg==} - engines: {node: '>=12'} + knex@3.2.9: + resolution: {integrity: sha512-dtAILTjBMaG8YloP5oBxohDIKyIsdQ/TkcVvSjhsksvsjeH63Y0PADyuMDfNZKbVT3Rlx3vEYVBlecbPT/KerA==} + engines: {node: '>=16'} hasBin: true peerDependencies: better-sqlite3: '*' @@ -2064,6 +2060,7 @@ packages: mysql2: '*' pg: '*' pg-native: '*' + pg-query-stream: ^4.14.0 sqlite3: '*' tedious: '*' peerDependenciesMeta: @@ -2077,6 +2074,8 @@ packages: optional: true pg-native: optional: true + pg-query-stream: + optional: true sqlite3: optional: true tedious: @@ -2591,8 +2590,8 @@ packages: pg-connection-string@2.12.0: resolution: {integrity: sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==} - pg-connection-string@2.5.0: - resolution: {integrity: sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==} + pg-connection-string@2.6.2: + resolution: {integrity: sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==} pg-cursor@2.19.0: resolution: {integrity: sha512-J5cF1MUz7LRJ9emOqF/06QjabMHMZy587rSPF0UuA8rCwKeeYl2co8Pp+6k5UU9YrAYHMzWkLxilfZB0hqsWWw==} @@ -4840,8 +4839,6 @@ snapshots: commander@9.1.0: {} - commander@9.5.0: {} - commondir@1.0.1: {} compare-func@2.0.0: @@ -5599,10 +5596,10 @@ snapshots: kind-of@6.0.3: {} - knex@2.4.2(pg@8.9.0): + knex@3.2.9(pg-query-stream@4.3.0(pg@8.9.0))(pg@8.9.0): dependencies: colorette: 2.0.19 - commander: 9.5.0 + commander: 10.0.1 debug: 4.3.4 escalade: 3.2.0 esm: 3.2.25 @@ -5610,13 +5607,14 @@ snapshots: getopts: 2.3.0 interpret: 2.2.0 lodash: 4.18.1 - pg-connection-string: 2.5.0 + pg-connection-string: 2.6.2 rechoir: 0.8.0 resolve-from: 5.0.0 tarn: 3.0.2 tildify: 2.0.0 optionalDependencies: pg: 8.9.0 + pg-query-stream: 4.3.0(pg@8.9.0) transitivePeerDependencies: - supports-color @@ -6169,7 +6167,7 @@ snapshots: pg-connection-string@2.12.0: {} - pg-connection-string@2.5.0: {} + pg-connection-string@2.6.2: {} pg-cursor@2.19.0(pg@8.9.0): dependencies: diff --git a/src/scripts/benchmark-queries.ts b/src/scripts/benchmark-queries.ts index ac76a91e7..8fe5d5c46 100644 --- a/src/scripts/benchmark-queries.ts +++ b/src/scripts/benchmark-queries.ts @@ -157,7 +157,7 @@ async function explain(db: Knex, query: Knex.QueryBuilder | Knex.Raw): Promise( `EXPLAIN (ANALYZE, BUFFERS, VERBOSE, FORMAT JSON) ${sql}`, - bindings as readonly unknown[], + bindings, ) return rows[0]['QUERY PLAN'][0] }