From d5c461b7adf1ac2147d6895322035db591730288 Mon Sep 17 00:00:00 2001 From: aakashcodonnier Date: Mon, 17 Aug 2026 18:01:10 +0530 Subject: [PATCH 1/4] fix(guards): resolve git toplevel path before comparing in markdown-links git rev-parse --show-toplevel always returns forward-slash paths, even on Windows, while path.resolve()/path.sep use backslashes there. The guard compared the two directly with startsWith(), so the containment check never matched on Windows and every local Markdown link in the repo was reported as missing, even though the files exist. Wrapping the git output in resolve() normalizes it to the platform's native separators before the comparison. Verified locally: the guard fails with 58 false positives on a clean Windows checkout before this change, and passes with 0 failures after it. --- guards/markdown-links.mjs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/guards/markdown-links.mjs b/guards/markdown-links.mjs index d55f7fdb..fe477ec5 100644 --- a/guards/markdown-links.mjs +++ b/guards/markdown-links.mjs @@ -138,9 +138,11 @@ export default { name: "markdown-links", description: "tracked Markdown files do not link to missing local targets", run() { - const root = execFileSync("git", ["rev-parse", "--show-toplevel"], { - encoding: "utf8", - }).trim(); + const root = resolve( + execFileSync("git", ["rev-parse", "--show-toplevel"], { + encoding: "utf8", + }).trim(), + ); const violations = []; for (const file of trackedMarkdownFiles(root)) { From 23a55fe31faf8b46f42faf50847bbc5bc4f0748c Mon Sep 17 00:00:00 2001 From: aakashcodonnier Date: Mon, 17 Aug 2026 18:06:11 +0530 Subject: [PATCH 2/4] fix(scripts): resolve patched image-size store dir by scanning, not hash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dependencies-security.test.mjs rebuilt the pnpm store path for the patched image-size@2.0.2 package by string-interpolating the patch hash read from pnpm-lock.yaml directly into a hardcoded "patch_hash=" directory name. On this Windows checkout the actual installed directory is named "patch_hash_72198d98..." (a different separator character AND a different hash), because patches/image-size@2.0.2.patch checks out with CRLF line endings here, so pnpm computes a different content hash for it than the lockfile recorded on the machine that produced the lockfile. The test then threw ERR_MODULE_NOT_FOUND trying to import a path that never existed, which is a false negative on a DoS-hardening regression test: the underlying security patch (rejecting non-advancing ICNS/HEIF/JXL boxes in dist/fromFile.cjs) is verified to be correctly applied in the actual installed package, but the test itself could not find it to check. Fix: scan node_modules/.pnpm for an entry matching image-size@2.0.2_patch_hash[=_] instead of constructing the exact path from the lockfile hash. This verifies the invariant the test actually cares about — a patched image-size@2.0.2 is installed — without depending on an exact hash match or separator character that can legitimately differ by platform/line-ending handling. Verified locally by toggling the change off/on with git stash in the same session: 0/3 passing before, 3/3 passing after, both times confirmed against the same real installed package. --- scripts/dependencies-security.test.mjs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/scripts/dependencies-security.test.mjs b/scripts/dependencies-security.test.mjs index 76f850ab..bea95055 100644 --- a/scripts/dependencies-security.test.mjs +++ b/scripts/dependencies-security.test.mjs @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; -import { readFile } from "node:fs/promises"; +import { readdir, readFile } from "node:fs/promises"; import { dirname, join, resolve } from "node:path"; import test from "node:test"; import { fileURLToPath, pathToFileURL } from "node:url"; @@ -11,16 +11,16 @@ async function patchedImageSizeModule() { const lockfile = await readFile(join(root, "pnpm-lock.yaml"), "utf8"); const match = lockfile.match(/^ image-size@2\.0\.2: ([a-f0-9]+)$/m); assert.ok(match, "image-size 2.0.2 must remain pinned to the reviewed local patch"); - return join( - root, - "node_modules", - ".pnpm", - `image-size@2.0.2_patch_hash=${match[1]}`, - "node_modules", - "image-size", - "dist", - "index.mjs", + + const storeDir = join(root, "node_modules", ".pnpm"); + const entries = await readdir(storeDir); + const patchedEntry = entries.find((entry) => /^image-size@2\.0\.2_patch_hash[=_][a-f0-9]+$/.test(entry)); + assert.ok( + patchedEntry, + "expected a patched image-size@2.0.2 entry under node_modules/.pnpm — run pnpm install", ); + + return join(storeDir, patchedEntry, "node_modules", "image-size", "dist", "index.mjs"); } function writeBox(buffer, offset, size, name) { From b09e35f22fba1e757c60e0b27b379659204ef75f Mon Sep 17 00:00:00 2001 From: aakashcodonnier Date: Mon, 17 Aug 2026 18:09:32 +0530 Subject: [PATCH 3/4] fix: add .gitattributes to pin LF line endings for tracked text files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both prior fixes in this branch trace back to the same root cause: this repository has no .gitattributes, so whether a checked-out file ends up with LF or CRLF line endings depends entirely on a contributor's local core.autocrlf setting. On a Windows machine with the (very common) core.autocrlf=true default, this silently introduces CRLF bytes into files the reviewed source never had them in, breaking anything that hashes or pattern-matches file content byte-for-byte: - guards/markdown-links.mjs and patches/image-size@2.0.2.patch (fixed earlier in this branch) - scripts/release.test.mjs and scripts/release.integration.test.mjs, which assert exact regex matches against generated workflow YAML and fail with 11 additional false negatives once CRLF is introduced Pinning `* text=auto eol=lf` makes every tracked text file check out with LF regardless of the contributor's autocrlf setting, so the guards, patches, and workflow-content tests behave identically for every contributor regardless of platform — closing off this entire class of Windows-only false positive/negative, not just the two instances already fixed in this branch. --- .gitattributes | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..d3c8713c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,18 @@ +# Normalize line endings to LF for every tracked text file, regardless of a +# contributor's local core.autocrlf setting. Several guards and tests compare +# file content or hash it (e.g. guards/markdown-links.mjs, patches applied via +# pnpm, CI-workflow regex assertions in scripts/release*.test.mjs) and break or +# silently diverge when a checkout introduces CRLF bytes that were not in the +# reviewed source. +* text=auto eol=lf + +# Explicitly binary — never subject to line-ending normalization or diffing. +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.woff binary +*.woff2 binary +*.ttf binary +*.otf binary From 6140efbc1db326cb926288b450f29b950243eaa1 Mon Sep 17 00:00:00 2001 From: aakashcodonnier Date: Mon, 17 Aug 2026 18:22:44 +0530 Subject: [PATCH 4/4] fix(scripts): pass --force-local to tar so Windows drive letters aren't parsed as remote hosts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both tar invocations in the release path build paths from os.tmpdir()/path.resolve(), which return drive-letter paths like C:\Users\... on Windows. GNU tar (bundled with Git for Windows, commonly the only tar on a Windows PATH) interprets a colon in a path as a remote-host separator unless told otherwise, so it tries to open an SSH-style connection to a host literally named "C" and fails with "Cannot connect to C: resolve failed" instead of reading the local path. Affected: - scripts/release.integration.test.mjs: the test fixture that builds a fake package tarball to exercise publishTarball()/registryState() - scripts/release.mjs inspectTarball(): reads package/package.json out of the real tarball before publishing — this is production release logic, not just test infrastructure, so this bug would also break running the actual release script from a Windows machine, not only the test suite --force-local tells tar to always treat the path as local regardless of any colon in it. It's a no-op on Linux/macOS where this ambiguity doesn't exist, so this doesn't change behavior in CI. Verified: reproduced the exact failure in isolation with a minimal tar invocation against a C:\ path, confirmed --force-local resolves it, then confirmed scripts/release.test.mjs and scripts/release.integration.test.mjs both regain previously-failing tests after applying it (13/31 passing before this fix in this session -> 24/31 after; remaining failures are a separate, pre-existing Windows npm-spawn issue noted in the PR description, not caused by this change). --- scripts/release.integration.test.mjs | 2 +- scripts/release.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/release.integration.test.mjs b/scripts/release.integration.test.mjs index cb75d7e6..796e7969 100644 --- a/scripts/release.integration.test.mjs +++ b/scripts/release.integration.test.mjs @@ -32,7 +32,7 @@ async function fixture(t) { join(packageDir, "lifecycle.mjs"), "import { writeFileSync } from 'node:fs';\nwriteFileSync(process.env.RELEASE_LIFECYCLE_MARKER, 'ran');\n", ); - execFileSync("tar", ["-czf", tarball, "-C", root, "package"]); + execFileSync("tar", ["--force-local", "-czf", tarball, "-C", root, "package"]); t.after(async () => { await rm(root, { recursive: true, force: true }); diff --git a/scripts/release.mjs b/scripts/release.mjs index 53c06501..b4bca626 100644 --- a/scripts/release.mjs +++ b/scripts/release.mjs @@ -129,7 +129,7 @@ export function inspectTarball(tarball, { exec = execFileSync } = {}) { let manifest; try { manifest = JSON.parse( - exec("tar", ["-xOf", absolute, "package/package.json"], { encoding: "utf8" }), + exec("tar", ["--force-local", "-xOf", absolute, "package/package.json"], { encoding: "utf8" }), ); } catch (error) { throw new Error(`cannot read package/package.json from ${absolute}: ${error.message}`);