{"record":{"id":"9c62c0d95979268b","repo":"nextlevelbuilder/ui-ux-pro-max-skill","slug":"refusing-to-modify-path-outside-repository-reso","errorCode":null,"errorMessage":"Refusing to modify path outside repository: ${resolvedPath}","messagePattern":"Refusing to modify path outside repository: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"cli/scripts/sync-assets.mjs","lineNumber":64,"sourceCode":"  /\\.(ttf|otf|woff2?|png|jpe?g|gif|ico|coverage|pyc)$/i.test(rel);\n\n// ponytail: all synced assets are text (csv/json/md/py); normalize CRLF->LF so\n// the byte hash and the on-disk copy don't drift with git autocrlf across platforms.\nconst toLF = (text) => text.replace(/\\r\\n/g, '\\n');\n\nasync function exists(path) {\n  try {\n    await access(path);\n    return true;\n  } catch {\n    return false;\n  }\n}\n\nfunction assertInsideRepo(path) {\n  const resolvedPath = resolve(path);\n  if (!resolvedPath.startsWith(repoRoot)) {\n    throw new Error(`Refusing to modify path outside repository: ${resolvedPath}`);\n  }\n  return resolvedPath;\n}\n\nasync function listFiles(root) {\n  const files = [];\n\n  async function walk(dir) {\n    for (const entry of await readdir(dir, { withFileTypes: true })) {\n      const fullPath = join(dir, entry.name);\n      if (entry.isDirectory()) {\n        await walk(fullPath);\n      } else if (entry.isFile()) {\n        files.push(relative(root, fullPath).replaceAll('\\\\', '/'));\n      }\n    }\n  }\n","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/nextlevelbuilder/ui-ux-pro-max-skill/blob/a38d04c3d5c298c851dbe5e6ee1965ee3de42cb5/cli/scripts/sync-assets.mjs#L46-L82","documentation":"sync-assets.mjs guards every write with assertInsideRepo(): each target path is resolved to an absolute path and must start with repoRoot (the repo checkout containing cli/scripts). Because syncDir() deletes the target directory before copying, this guard prevents an accidental absolute/relative escape from rm-ing arbitrary directories. It fires when a computed target resolves outside the repo — usually a wrong CWD or a relocated script.","triggerScenarios":"Running sync-assets.mjs after moving cli/ out of the repository (so __dirname/../.. no longer lands in the repo root while a target path does); invoking the script through a symlink that resolves elsewhere; someone editing the repoRoot/assetRoot constants to point outside the tree.","commonSituations":"Copying the cli folder into another project to reuse the script; running via a symlinked path; CI checking out into nested paths that break the '..','..' assumption.","solutions":["Run the script from its canonical location in the repo: `cd <repo>/cli && npm run sync:assets`.","Ensure the full repository (src/ui-ux-pro-max, cli/, .claude/) is checked out — the script assumes the standard layout.","If you intentionally relocated the script, update the repoRoot/assetRoot constants at the top of sync-assets.mjs to match the new layout.","Do not pass absolute external targets to syncDir(); the guard will (correctly) refuse."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// preflight: confirm the expected repo layout before syncing\nimport { access } from 'node:fs/promises';\n\nconst layoutOk = await Promise.all([\n  access('src/ui-ux-pro-max'),\n  access('cli/scripts/sync-assets.mjs'),\n  access('.claude/skills'),\n]).then(() => true, () => false);\nif (!layoutOk) throw new Error('Run sync-assets.mjs from a full checkout of the repo');","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always run `npm run sync:assets` from the repo's cli/ directory in a full clone.","Don't relocate cli/ independently of src/ and .claude/.","Treat this guard as a safety feature protecting rm -rf; fix the layout, never bypass the guard."],"tags":["build","sync","path-safety","node","developer-tooling"],"backgroundTag":null,"analyzedSha":"a38d04c3d5c298c851dbe5e6ee1965ee3de42cb5","analyzedAt":"2026-08-14T18:51:02.321Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}