{"record":{"id":"52c5811786ef102f","repo":"abhigyanpatwari/GitNexus","slug":"path-must-include-owner-repo-without-traversal","errorCode":null,"errorMessage":"path must include owner/repo without traversal","messagePattern":"path must include owner/repo without traversal","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"gitnexus/src/core/auto-sync/config.ts","lineNumber":311,"sourceCode":"  const repoPath = match[2];\n  if (!ALLOWED_REMOTE_HOSTS.has(host)) {\n    throw new Error('host must be one of github.com, gitlab.com, or gitee.com');\n  }\n  const pathParts = repoPath.split('/');\n  // Every segment becomes a directory component: the namespace segments build\n  // the clone path and the last one names the repo. So each is held to the same\n  // charset, which is what keeps a separator out of a segment — on Windows\n  // `..\\..\\outside` is traversal even though the segment is not literally `..`,\n  // and testing the raw string for `..` instead would reject an ordinary\n  // `foo..bar`. Traversal is a whole segment; a separator is a character.\n  const namespaceParts = pathParts.slice(0, -1);\n  if (\n    repoPath.startsWith('/') ||\n    pathParts.length < 2 ||\n    pathParts.some((part) => !part || part === '.' || part === '..') ||\n    namespaceParts.some((part) => !REMOTE_PATH_SEGMENT_PATTERN.test(part))\n  ) {\n    throw new Error('path must include owner/repo without traversal');\n  }\n  // The final segment becomes the on-disk clone directory via `extractRepoName`,\n  // whose name rules are stricter than the path check above: a backslash — or\n  // anything outside `[A-Za-z0-9._-]` — passes here and then throws once per\n  // tick inside the sync loop instead of at config load. These rules are a\n  // strict superset, so anything accepted here is accepted there.\n  const lastSegment = pathParts[pathParts.length - 1];\n  const repoName = /\\.git$/i.test(lastSegment) ? lastSegment.slice(0, -4) : lastSegment;\n  if (\n    !repoName ||\n    repoName === '.' ||\n    repoName === '..' ||\n    repoName === 'unknown' ||\n    repoName.startsWith('-') ||\n    !REMOTE_REPO_NAME_PATTERN.test(repoName)\n  ) {\n    throw new Error(\n      'repository name must use only letters, digits, \".\", \"_\", or \"-\" and must not be \"unknown\"',","sourceCodeStart":293,"sourceCodeEnd":329,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/0d1aed942f0e8b5d3bac27519fff441aceea722d/gitnexus/src/core/auto-sync/config.ts#L293-L329","documentation":"This error is thrown when the repo path portion of an SSH remote URL is structurally invalid: it must contain at least two segments (owner and repo), must not start with '/', and no segment may be empty, '.', '..', or (for namespace segments) fail the remote path segment charset pattern. The rules exist because every segment becomes an on-disk directory component of the clone path — '..' or separator-bearing segments would enable path traversal outside the sync root.","triggerScenarios":"remote_url = git@github.com:repo.git (no owner), git@github.com:/repo.git (leading slash), git@github.com:acme/../etc.git (traversal), git@github.com:acme//repo.git (empty segment), or a namespace segment containing characters outside the allowed segment pattern (e.g. spaces, backslashes like acme\\\\..\\\\outside).","commonSituations":"Hand-edited config where the owner was deleted; path pasted from a filesystem (Windows backslashes); intentionally deep-nested paths on a forge that supports subgroups with an extra or missing slash; malicious or corrupted config files.","solutions":["Use the full owner/repo path exactly as the forge shows it: git@github.com:owner/repo.git.","Remove any leading slash after the colon and any empty ('//') segments.","Replace '.' or '..' segments with the real directory names — traversal segments are always rejected.","If the repo lives in nested groups/subgroups, keep each group as its own segment: git@gitlab.com:group/subgroup/repo.git.","Convert backslashes to forward slashes if the path was pasted from Windows."],"exampleFix":"// before\nremote_url = git@github.com:acme/../outside/repo.git\n// after\nremote_url = git@github.com:acme/repo.git","handlingStrategy":"validation","validationCode":"function pathLooksSafe(url) {\n  const m = /^git@[^:\\s/]+:(\\S+)$/.exec((url ?? '').trim());\n  if (!m) return false;\n  const parts = m[1].split('/');\n  if (m[1].startsWith('/') || parts.length < 2) return false;\n  return parts.every((p) => p && p !== '.' && p !== '..' && /^[A-Za-z0-9._-]+$/.test(p));\n}\nif (!pathLooksSafe(cfg.remote_url)) throw new Error('remote path must be owner/repo, no traversal');","typeGuard":null,"tryCatchPattern":"try {\n  const cfg = parseAutoSyncConfig(raw);\n} catch (e) {\n  if (String(e.message).includes('path must include owner/repo')) {\n    log.error('remote_url path must be owner/repo with no empty, \".\", or \"..\" segments');\n  }\n  throw e;\n}","preventionTips":["Always include owner and repo segments in the URL path.","Never build remote URLs by string-concatenating filesystem paths.","Reject '..' segments when generating config from user input.","Use forward slashes only; backslashes will not survive segment validation."],"tags":["config","git","path-traversal","security","validation"],"backgroundTag":"path-traversal-blocked","analyzedSha":"0d1aed942f0e8b5d3bac27519fff441aceea722d","analyzedAt":"2026-09-08T00:40:44.970Z","contentChangedAt":"2026-09-08T00:40:44.970Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}