test(artifacts): verify native directory identity on supported Windows runtimes (#1069)

* fix(artifacts): guard Windows device 0 in directory open integrity check

On Windows, Node.js fs.lstat reports dev as 0 without opening a handle. When DirectoryArtifactReader.open inspects the opened handle using handle.stat(), stat.dev is populated with the volume serial number.

Consequently, the TOCTOU check observed.dev !== entry.sourceIdentity.device evaluated to true on Windows for every regular file, throwing an ArtifactReaderFailure integrity error.

Relax the device comparison when running on Windows or when the inventoried device is 0, while maintaining strict inode verification to ensure the target has not been replaced.

* test(artifacts): verify native directory identity without device bypasses

---------

Co-authored-by: N0zoM1z0 <[email protected]>
This commit is contained in:
Adarsh Kumar
2026-10-09 00:11:28 +08:00
committed by GitHub
co-authored by N0zoM1z0
parent 83defc054b
commit e94be95c66
2 changed files with 141 additions and 0 deletions
+15
View File
@@ -473,6 +473,21 @@ jobs:
tests/conformance/providers/ghidra/realGhidraWindowsWorkflow.test.ts
- run: npm run verify:windows-native
- run: npm run verify:package:windows
- name: Verify native directory identity on Node 24
run: >-
npx vitest run --coverage.enabled=false
tests/boundary/filesystem/directoryArtifactReaderOpen.test.ts
tests/boundary/filesystem/javascriptPackagePrecedence.test.ts
tests/boundary/filesystem/javascriptArtifactContentIdentity.test.ts
- uses: actions/setup-node@v4
with:
node-version: 22.19.0
- name: Verify native directory identity on Node 22
run: >-
npx vitest run --coverage.enabled=false
tests/boundary/filesystem/directoryArtifactReaderOpen.test.ts
tests/boundary/filesystem/javascriptPackagePrecedence.test.ts
tests/boundary/filesystem/javascriptArtifactContentIdentity.test.ts
- uses: actions/upload-artifact@v4
if: always()
with:
@@ -0,0 +1,126 @@
import { lstat, open, rename, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { buffer } from "node:stream/consumers";
import { describe, expect, it } from "vitest";
import { type ArtifactEntry } from "../../../src/artifacts/ArtifactReader.js";
import { DirectoryArtifactReader } from "../../../src/artifacts/DirectoryArtifactReader.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
const sampleEntry = async (
reader: DirectoryArtifactReader,
): Promise<ArtifactEntry> => {
for await (const entry of reader.entries()) {
if (entry.path === "sample.txt") return entry;
}
throw new Error("Fixture omitted sample.txt");
};
const fixture = async () => {
const root = await createTestTempDirectory("rea-dir-reader-open-");
const path = join(root, "sample.txt");
await writeFile(path, "test payload\n");
return { path, reader: new DirectoryArtifactReader(root) };
};
describe("DirectoryArtifactReader.open", () => {
it("preserves native path and handle identity while streaming an unchanged file", async () => {
const { path, reader } = await fixture();
try {
const entry = await sampleEntry(reader);
const metadata = await lstat(path);
const handle = await open(path, "r");
try {
const observed = await handle.stat();
expect(entry.sourceIdentity).toEqual({
device: metadata.dev,
inode: metadata.ino,
});
expect({ device: observed.dev, inode: observed.ino }).toEqual(
entry.sourceIdentity,
);
console.info(
JSON.stringify({
platform: process.platform,
node: process.version,
pathDevice: metadata.dev,
handleDevice: observed.dev,
pathInode: metadata.ino,
handleInode: observed.ino,
}),
);
} finally {
await handle.close();
}
expect((await buffer(await reader.open(entry))).toString("utf8")).toBe(
"test payload\n",
);
} finally {
await reader.close();
}
});
it("rejects an actual file replacement after enumeration", async () => {
const { path, reader } = await fixture();
try {
const entry = await sampleEntry(reader);
await rename(path, `${path}.original`);
await writeFile(path, "replacement payload\n");
await expect(reader.open(entry)).rejects.toMatchObject({
reason: "integrity",
});
} finally {
await reader.close();
}
});
it.each(["zero", "another"] as const)(
"requires the enumerated %s device to match the opened file",
async (kind) => {
const { reader } = await fixture();
try {
const entry = await sampleEntry(reader);
const identity = entry.sourceIdentity;
if (identity === undefined)
throw new Error("Fixture omitted source identity");
if (kind === "zero" && identity.device === 0) {
expect(
(await buffer(await reader.open(entry))).toString("utf8"),
).toBe("test payload\n");
return;
}
const device = kind === "zero" ? 0 : identity.device === 1 ? 2 : 1;
await expect(
reader.open({ ...entry, sourceIdentity: { ...identity, device } }),
).rejects.toMatchObject({ reason: "integrity" });
} finally {
await reader.close();
}
},
);
it("rejects a file without enumerated source identity", async () => {
const { reader } = await fixture();
try {
const { sourceIdentity: _identity, ...entry } = await sampleEntry(reader);
await expect(reader.open(entry)).rejects.toMatchObject({
reason: "integrity",
});
} finally {
await reader.close();
}
});
it("rejects entries that are not files", async () => {
const { reader } = await fixture();
try {
const entry = await sampleEntry(reader);
await expect(
reader.open({ ...entry, kind: "directory" }),
).rejects.toMatchObject({ reason: "format" });
} finally {
await reader.close();
}
});
});