From 66249df30b326bd51a37c9c1be50fcf307b4c213 Mon Sep 17 00:00:00 2001 From: mohitagw15856 <119053560+mohitagw15856@users.noreply.github.com> Date: Thu, 18 Jun 2026 20:43:45 +0100 Subject: [PATCH] fix: guard install path + robust frontmatter parsing (#47) (#54) Reapplies @MatrixNeoKozak's PR #47 onto current main (resolves the bin/cli.mjs conflict with the star-nudge changes): - resolve() the install target and refuse system-critical dirs (/, /usr, /etc, /root, ...) so a typo'd --target can't clobber the system - skillcheck frontmatter parser tolerates leading whitespace and CRLF/LF Claude-Session: https://claude.ai/code/session_016JWn5jRD5tcEFKrubjQ6Px Co-authored-by: Claude Co-authored-by: MatrixNeoKozak --- bin/cli.mjs | 12 ++++++++++-- scripts/skillcheck.mjs | 6 ++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/bin/cli.mjs b/bin/cli.mjs index fc28409..d96b09c 100755 --- a/bin/cli.mjs +++ b/bin/cli.mjs @@ -13,7 +13,7 @@ // --link symlink instead of copy (native agents; falls back to copy) // --dry-run print what would happen without writing import { readdirSync, existsSync, mkdirSync, rmSync, cpSync, symlinkSync, copyFileSync, statSync } from 'node:fs'; -import { join, dirname, basename } from 'node:path'; +import { join, dirname, basename, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { homedir } from 'node:os'; import { createRequire } from 'node:module'; @@ -79,7 +79,15 @@ function add(opts) { } const skillsDir = join(PKG_ROOT, 'skills'); if (!existsSync(skillsDir)) { console.error(`Error: bundled skills/ not found at ${skillsDir}.`); process.exit(1); } - const target = opts.target || defaultTarget(agent); + const target = resolve(opts.target || defaultTarget(agent)); + + // Guard against installing into system-critical directories (e.g. a typo'd --target). + const criticalPaths = ['/', '/usr', '/bin', '/etc', '/var', '/root', '/boot', '/proc', '/sys', '/dev']; + if (criticalPaths.includes(target)) { + console.error(`Error: Cannot install into a system-critical directory: ${target}`); + process.exit(1); + } + let count = 0; console.log(`${opts.dryRun ? '[dry-run] ' : ''}Installing for '${agent}' into ${target}`); diff --git a/scripts/skillcheck.mjs b/scripts/skillcheck.mjs index 7d0994c..d9531af 100644 --- a/scripts/skillcheck.mjs +++ b/scripts/skillcheck.mjs @@ -22,10 +22,12 @@ const strict = args.includes('--strict'); const asJson = args.includes('--json'); function parseFrontmatter(text) { - const m = text.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/); + // Tolerate optional leading whitespace and CRLF/LF line endings so authored-on-Windows + // files don't produce false negatives. + const m = text.match(/^\s*---\r?\n([\s\S]*?)\r?\n\s*---\r?\n?([\s\S]*)$/); if (!m) return { meta: null, body: text }; const meta = {}; - for (const line of m[1].split('\n')) { + for (const line of m[1].split(/\r?\n/)) { const kv = line.match(/^(\w[\w-]*):\s*(.*)$/); if (kv) { let v = kv[2].trim();