Skip to content

fix(guards): resolve markdown links against a normalised repository root - #192

Open
OnLocation-acumbal-contractor wants to merge 1 commit into
theam:mainfrom
OnLocation-acumbal-contractor:fix/markdown-links-windows
Open

fix(guards): resolve markdown links against a normalised repository root#192
OnLocation-acumbal-contractor wants to merge 1 commit into
theam:mainfrom
OnLocation-acumbal-contractor:fix/markdown-links-windows

Conversation

@OnLocation-acumbal-contractor

Copy link
Copy Markdown

Closes #

The bug

git rev-parse --show-toplevel reports POSIX separators on every platform. On Windows it
answers C:/repo while path.resolve produces C:\repo, so the containment check in
targetExists compared

C:\repo\SECURITY.md   .startsWith(   "C:/repo" + "\"   )

which is false, and returned before existsSync was ever called. Every local link was
reported missing — on a Windows checkout node guards/run.mjs flags all 56 links in this
repository, LICENSE and SECURITY.md included, while actions-pinned passes in the same
run and the same checkout is clean on Linux.

Since pnpm verify runs pnpm guards, a Windows contributor could not get a green local
verify, and the output looks like a broken checkout rather than a platform bug.

The fix

Normalise the root with path.resolve before the comparison. The containment rule is
unchanged — targets escaping the repository are still rejected.

The resolution step is extracted as an exported resolveWithinRoot(root, source, target, path)
with the path module injectable, so Windows semantics can be exercised from any host via
node:path's win32 binding. That is the only structural change; targetExists keeps its
behaviour and its extension/index.md candidates.

Tests

scripts/markdown-links-guard.test.mjs, five cases, picked up by the existing
pnpm test:dev (node --test scripts/*.test.mjs):

  • a git-reported Windows root (C:/repo) resolves in-repo targets, including a ../ hop
  • a native Windows root (C:\repo) keeps working
  • POSIX roots are unaffected
  • traversal outside the repository still returns null, on both platforms
  • the repository root itself counts as inside the repository

The traversal cases are there deliberately: the containment check is a security property, and
this change must not loosen it.

Verification

Confirmed on a Windows host, which is where the bug is observable.

Before this change, node guards\run.mjs on Windows fails markdown-links with 56
violations — every local link in the repository, LICENSE and SECURITY.md included —
while actions-pinned passes in the same run. After applying this branch, same machine,
same checkout:

PS C:\...\facility> node guards\run.mjs
✓ actions-pinned
✓ markdown-links

2 guards ran, 0 failed.
PS C:\...\facility> node --test scripts/markdown-links-guard.test.mjs
# pass 5
# fail 0

On Linux:

node guards/run.mjs             # 2 guards ran, 0 failed
node --test scripts/*.test.mjs  # no new failures

Suggested follow-up

Not in this PR, but this is the third Windows issue on the quickstart path alongside #182 and
#167. A minimal Windows CI job running pnpm guards and pnpm test:dev would have caught all
three. Happy to open that separately if it's wanted.

`git rev-parse --show-toplevel` reports POSIX separators on every platform.
On Windows it answers `C:/repo` while `path.resolve` produces `C:\repo`, so
the containment check in `targetExists` compared `C:\repo\SECURITY.md`
against the prefix `C:/repo\` and bailed out before ever calling
`existsSync`. Every local link failed: `node guards/run.mjs` on Windows
reports all 56 links in this repository as missing, including `LICENSE` and
`SECURITY.md`, and `pnpm verify` cannot go green on a Windows checkout.

Normalise the root with `path.resolve` before comparing. The containment
rule itself is unchanged, so targets escaping the repository are still
rejected.

The resolution step is extracted as `resolveWithinRoot` with an injectable
`path` module, so the Windows behaviour is covered from any host. The tests
assert the git-reported root, the native Windows root, POSIX parity, and
that traversal outside the repository still returns null.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant