fix(deps-restorer): include the loader runtime in git dependencies (#17388)

* fix(deps-restorer): include the loader runtime in git dependencies

* test(esm-loader): reject stale bundles without rewriting them

* fix(deps): update compression crates for advisory fixes

* fix(store): retry concurrent SQLite index initialization

* chore: correct release metadata and module ordering

* test(store): verify concurrent index rows survive initialization

* style(store): retain trailing comma in concurrency assertion
This commit is contained in:
Zoltan Kochan authored and GitHub committed 2026-10-26 18:49:20 +00:00
1 parent 316ec14c4b
commit 20ec9feaee
12 files changed
+293 -36

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
---
"pacquet": patch
"@pnpm/pnpr": patch
---
Fixed memory exposure and allocation handling vulnerabilities when processing zstd-compressed data [#17388](https://github.com/pnpm/pnpm/pull/17388).
@@ -0,0 +1,5 @@
---
"pacquet": patch
---
Concurrent workspace installs no longer lose store-index rows when initializing the shared SQLite index [#17388](https://github.com/pnpm/pnpm/pull/17388).
-1
View File
@@ -96,4 +96,3 @@ pnpm/npm/wasm/
pnpr-bins-*
# Generated by the `prepare` script of pnpm/esm-loader.
pnpm/crates/deps-restorer/src/cas-loader.mjs.inc
Generated
+10 -10
View File
@@ -1081,9 +1081,9 @@ checksum = "b1b4fe32ea3f2a8d975b6c5cdd3f02ac358f471ca24dbb18d7a4ca58b3193d2d"
[[package]]
name = "compression-codecs"
version = "0.4.38"
version = "0.4.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf"
checksum = "cae36683101b2899f48bd31f8db9839437356188e3b1ff23096e73dab09ba8fe"
dependencies = [
"compression-core",
"flate2",
@@ -1094,9 +1094,9 @@ dependencies = [
[[package]]
name = "compression-core"
version = "0.4.32"
version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789"
checksum = "2ebdda89edc534bf8d5dd2857562633b6495557e5846552ade994b99ed13c36a"
[[package]]
name = "concurrent-queue"
@@ -10960,27 +10960,27 @@ dependencies = [
[[package]]
name = "zstd"
version = "0.13.3"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
checksum = "db3ae9bf90af6e40197bef767eeefc46ad6397420fe873830bd85e5fbbfb3c70"
dependencies = [
"zstd-safe",
]
[[package]]
name = "zstd-safe"
version = "7.3.0"
version = "8.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64d80649ab6db9d9f6f9c80a40becd948eda4714a0a5ac8c4d157a32231c7882"
checksum = "ded520f16b29e1a9b771c722221b2b0a0398c2316d9bb3c42b458a5414d7434b"
dependencies = [
"zstd-sys",
]
[[package]]
name = "zstd-sys"
version = "2.1.0+zstd.1.5.7"
version = "2.1.2+zstd.1.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ef0a8027ec3ee71300ab3bcbcd0393f434aa72b91ca6d635a39941deae8eea0"
checksum = "481ada9c543b2323cbb7bbdbb5249177fbf0188ed959ed2c15e4b22421c84ed3"
dependencies = [
"cc",
"pkg-config",
+1 -1
View File
@@ -268,7 +268,7 @@ yamlpath = { version = "1.25.2" }
wax = { version = "0.7.0" }
which = { version = "8.0.2" }
zip = { version = "8", default-features = false, features = ["deflate"] }
zstd = { version = "0.13.3", default-features = false }
zstd = { version = "0.14.3", default-features = false }
zune-inflate = { version = "0.2.54" }
# Dev dependencies
File diff suppressed because one or more lines are too long.
+3 -20
View File
@@ -74,26 +74,7 @@ impl StoreIndex {
let conn = Connection::open(&db_path)
.map_err(|source| StoreIndexError::Open { path: db_path.clone(), source })?;
// Busy-timeout FIRST so the internal busy handler is active during the
// rest of the setup — on Windows file locking is mandatory and
// concurrent pacquet / pnpm invocations can contend.
conn.execute_batch(
"
PRAGMA busy_timeout=5000;
PRAGMA journal_mode=WAL;
PRAGMA synchronous=NORMAL;
PRAGMA mmap_size=536870912;
PRAGMA cache_size=-32000;
PRAGMA temp_store=MEMORY;
PRAGMA wal_autocheckpoint=10000;
CREATE TABLE IF NOT EXISTS package_index (
key TEXT PRIMARY KEY,
data BLOB NOT NULL
) WITHOUT ROWID;
",
)
.and_then(|()| conn.execute_batch(aliases::CREATE_ALIAS_TABLE))
.map_err(|source| StoreIndexError::InitSchema { source })?;
schema::initialize(&conn).map_err(|source| StoreIndexError::InitSchema { source })?;
Ok(StoreIndex::sqlite(
conn,
@@ -524,6 +505,8 @@ mod writer;
mod queries;
mod schema;
mod aliases;
mod error;
@@ -0,0 +1,39 @@
//! Initialize a shared index even when another installer is switching it to WAL.
use rusqlite::{Connection, ErrorCode};
use std::time::Duration;
/// `SQLite` can return `SQLITE_BUSY` from a journal-mode transition without
/// invoking its busy handler. Retry the idempotent setup within the same
/// pnpm v11 retry policy: up to 100 retries, 50 milliseconds apart.
pub(super) fn initialize(connection: &Connection) -> rusqlite::Result<()> {
connection.busy_timeout(Duration::from_secs(5))?;
let mut retries = 0;
loop {
let result = connection
.execute_batch(
"PRAGMA journal_mode=WAL;
PRAGMA synchronous=NORMAL;
PRAGMA mmap_size=536870912;
PRAGMA cache_size=-32000;
PRAGMA temp_store=MEMORY;
PRAGMA wal_autocheckpoint=10000;
CREATE TABLE IF NOT EXISTS package_index (
key TEXT PRIMARY KEY,
data BLOB NOT NULL
) WITHOUT ROWID;",
)
.and_then(|()| connection.execute_batch(super::aliases::CREATE_ALIAS_TABLE));
match result {
Err(error)
if error.sqlite_error_code() == Some(ErrorCode::DatabaseBusy) && retries < 100 =>
{
retries += 1;
std::thread::sleep(Duration::from_millis(50));
}
result => return result,
}
}
}
#[cfg(test)]
mod tests;
@@ -0,0 +1,41 @@
use crate::{StoreIndex, store_index::tests::sample_index};
use std::{path::Path, sync::Barrier};
#[test]
fn concurrent_first_open_persists_every_writer_row() {
for _ in 0..16 {
let store = tempfile::tempdir().unwrap();
open_concurrently(store.path());
}
}
fn open_concurrently(store: &Path) {
let start = Barrier::new(8);
std::thread::scope(|scope| {
let writers = (0..8)
.map(|worker| {
let start = &start;
scope.spawn(move || {
start.wait();
let index =
StoreIndex::open(store).expect("initialize a concurrent store writer");
index
.set(&format!("fixture-{worker}"), &sample_index())
.unwrap();
})
})
.collect::<Vec<_>>();
for writer in writers {
writer.join().unwrap();
}
});
let index = StoreIndex::open_readonly(store).unwrap();
for worker in 0..8 {
assert_eq!(
index
.get(&format!("fixture-{worker}"))
.unwrap(),
Some(sample_index()),
);
}
}
+2 -1
View File
@@ -15,7 +15,8 @@
"node": "^24.18.0 || >=26.2.0"
},
"scripts": {
"prepare": "node scripts/bundle-runtime.mjs",
"prepare": "node scripts/bundle-runtime.mjs --check",
"build:runtime": "node scripts/bundle-runtime.mjs",
"test": "node --test test/*.test.mjs",
".test": "node --test test/*.test.mjs"
},
+9 -3
View File
@@ -16,6 +16,12 @@ const result = await esbuild.build({
})
const output = result.outputFiles[0]
const tempPath = `${output.path}.${process.pid}.tmp`
fs.writeFileSync(tempPath, output.contents)
fs.renameSync(tempPath, output.path)
if (process.argv.includes('--check')) {
if (!fs.readFileSync(output.path).equals(output.contents)) {
throw new Error('The embedded loader is stale. Run pnpm --filter @pnpm/esm-loader build:runtime and commit the generated file.')
}
} else {
const tempPath = `${output.path}.${process.pid}.tmp`
fs.writeFileSync(tempPath, output.contents)
fs.renameSync(tempPath, output.path)
}
@@ -0,0 +1,37 @@
import assert from 'node:assert/strict'
import { execFileSync, spawnSync } from 'node:child_process'
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import process from 'node:process'
import { test } from 'node:test'
import { fileURLToPath } from 'node:url'
const loader = fileURLToPath(new URL('../', import.meta.url))
test('the committed loader matches the runtime sources', () => {
execFileSync(process.execPath, [path.join(loader, 'scripts/bundle-runtime.mjs'), '--check'], { stdio: 'inherit' })
})
test('check rejects a stale bundle without rewriting it', () => {
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'pnpm-loader-check-'))
try {
const isolatedLoader = path.join(directory, 'esm-loader')
fs.cpSync(loader, isolatedLoader, {
recursive: true,
filter: source => !['node_modules', 'test'].includes(path.basename(source)),
})
fs.symlinkSync(fs.realpathSync(path.join(loader, 'node_modules')), path.join(isolatedLoader, 'node_modules'), 'junction')
const output = path.join(directory, 'crates/deps-restorer/src/cas-loader.mjs.inc')
fs.mkdirSync(path.dirname(output), { recursive: true })
const stale = '// deliberately stale runtime\n'
fs.writeFileSync(output, stale)
const result = spawnSync(process.execPath, [path.join(isolatedLoader, 'scripts/bundle-runtime.mjs'), '--check'], { encoding: 'utf8' })
assert.ifError(result.error)
assert.equal(result.status, 1, result.stderr)
assert.match(result.stderr, /The embedded loader is stale/)
assert.equal(fs.readFileSync(output, 'utf8'), stale)
} finally {
fs.rmSync(directory, { recursive: true, force: true })
}
})