Repository navigation
Conversation
…hing Give the workflow a read-only token, don't persist checkout credentials, cancel superseded PR runs, and add timeouts. Run tests and the package smoke test on Ubuntu, macOS and Windows with Node 22 and 24, with autocrlf off so fixtures keep LF. Move lint, format check and typecheck into one job. Lint src, test, scripts and config files (fixtures ignored) and fix what that found; add format:check and ignore dist, coverage and fixtures in Prettier. Skip read-permission tests on Windows, where chmod can't revoke reads.
… dirs allowedSnippetRoots compared a realpath'd snippet against roots that weren't realpath'd, so every snippet was rejected as path traversal when the working directory path went through a symlink or a Windows 8.3 short name. Realpath the working directory and map the snippet root onto it lexically, so a symlinked snippets directory is still not followed. Route all Markdown globbing through src/glob.ts, which converts backslashes to glob separators on Windows and returns platform-native paths. Make test path normalization portable.
eject still called fast-glob directly. The absolute-path traversal test now uses a file that exists on every platform, and the ENOTDIR case is POSIX-only.
Only map the snippet root lexically onto the real working directory when it's inside the working directory; realpath an outside root on its own, so a symlinked working dir can't allow an unconfigured directory. Pass findMarkdownFiles an options object. Document backslashes in Windows globs. Keep LF via .gitattributes instead of a CI step. Update AGENTS.md and CONTRIBUTING.md, and name skip reasons in tests.
Second-pass reviewThree passes: security (twice) and standards. Standards: the standards pass built Security, first pass: found a regression in the first fix. It mapped the snippet root relative to the realpathed working directory even when the root was outside it. With Fixed in 46ff37c
Security, second pass on 46ff37c: 28 containment cases against One accepted inconsistency: a snippet root outside the working directory that is itself a symlink is now followed. That root is user configuration, and config can already point an absolute root anywhere. A symlinked Not changed: npm isn't cached in CI (each job runs a cold CI green on Ubuntu, macOS and Windows × Node 22 and 24, including |
Summary
CI now runs on Ubuntu, macOS and Windows, and lints and formats more than
src/. Turning Windows on found two product bugs; on its first run, 77 of 317 tests failed.Bugs fixed
1. Snippets rejected as "path traversal" when the working directory path goes through a symlink or a Windows 8.3 short name.
readLocalSnippetrealpaths the snippet, butallowedSnippetRootsdidn't realpath the roots it compared against. Through the library on macOS:On Windows this hits the CLI whenever the cwd uses a short name, such as the default
%TEMP%(C:\Users\RUNNER~1\…). On macOS and Linux,process.cwd()is already a real path, so only library callers are affected.Fix: realpath the working directory. A snippet root inside it is mapped onto the real working directory by its relative path, so a symlinked
snippets/directory still isn't followed and a committedsnippets -> /etclink is rejected as before. A snippet root outside the working directory (../shared, or an absolute path) is realpathed on its own. Mapping it relative to the link target would allow a directory nobody configured.2. Globs containing
\matched nothing on Windows. fast-glob treats\as an escape character, so--markdown-glob docs\**\*.md, or any absolute pattern built withpath.join, found no files. Every fast-glob call now goes throughsrc/glob.ts→findMarkdownFiles({ markdownGlob, excludeGlob, workingDir }):planSync(used bysyncandcheck),extract, discovery andeject. It converts\to/on Windows only, because elsewhere\is a real escape, and it returns platform-native absolute paths. The README now says Windows users should write a literal[or(as a bracket expression ([[]), not a backslash escape.CI
permissions: contents: read,persist-credentials: false, cancel superseded PR runs, and job timeouts.format:check, typecheck.{ubuntu, macos, windows} × Node {22, 24}running build, test andtest:package..gitattributes(* text=auto eol=lf) keeps fixtures LF on Windows, for CI and contributors alike.git add --renormalizechanged nothing.Test (22)/Test (24)becomeLintplusTest (<os>, Node <n>). Nothing requires checks today, so nothing breaks, but a future ruleset should use the new names.Lint and format
npm run lintis noweslint .. That coverssrc/,test/,scripts/and config files through a newtsconfig.eslint.json;test/fixtures,distandcoverageare ignored.max-lenis off in tests. ANSI stripping usesutil.stripVTControlCharactersinstead of control-character regexes, andno-unused-varsallows rest-sibling omission.format:checkscript..prettierignorenow also ignoresdist/,coverage/andtest/fixtures/.README.mdandSKILL.mdhad whitespace-only fixes.Tests
New
glob.test.ts: Windows conversion; non-Windows untouched; native paths; absolutepath.joinpatterns.Symlink tests:
snippet-resolutiontests resolve snippets through a symlinked or junctioned working directory, which fails onmain. With a snippet root outside that directory, the configured root is allowed and the same relative path under the link target is rejected. Both of those fail with the first version of this fix and pass onmain.Portability fixes:
\./etc/passwd.ENOTDIRcase and thechmodread-permission tests are skipped on Windows, where they can't apply.I have run
npm run buildto verify the project builds.Docs:
AGENTS.mddescribes the CI matrix and says Node 22+ (not 18+).CONTRIBUTING.mdlistsformat:check.I have run
npm run lintandnpm run format:check.I have run
npm testto ensure all tests pass. (324 passed)