abhigyanpatwari/GitNexus · error · Error

path must include owner/repo without traversal

Error message

path must include owner/repo without traversal

What it means

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.

Source

Thrown at gitnexus/src/core/auto-sync/config.ts:311

  const repoPath = match[2];
  if (!ALLOWED_REMOTE_HOSTS.has(host)) {
    throw new Error('host must be one of github.com, gitlab.com, or gitee.com');
  }
  const pathParts = repoPath.split('/');
  // Every segment becomes a directory component: the namespace segments build
  // the clone path and the last one names the repo. So each is held to the same
  // charset, which is what keeps a separator out of a segment — on Windows
  // `..\..\outside` is traversal even though the segment is not literally `..`,
  // and testing the raw string for `..` instead would reject an ordinary
  // `foo..bar`. Traversal is a whole segment; a separator is a character.
  const namespaceParts = pathParts.slice(0, -1);
  if (
    repoPath.startsWith('/') ||
    pathParts.length < 2 ||
    pathParts.some((part) => !part || part === '.' || part === '..') ||
    namespaceParts.some((part) => !REMOTE_PATH_SEGMENT_PATTERN.test(part))
  ) {
    throw new Error('path must include owner/repo without traversal');
  }
  // The final segment becomes the on-disk clone directory via `extractRepoName`,
  // whose name rules are stricter than the path check above: a backslash — or
  // anything outside `[A-Za-z0-9._-]` — passes here and then throws once per
  // tick inside the sync loop instead of at config load. These rules are a
  // strict superset, so anything accepted here is accepted there.
  const lastSegment = pathParts[pathParts.length - 1];
  const repoName = /\.git$/i.test(lastSegment) ? lastSegment.slice(0, -4) : lastSegment;
  if (
    !repoName ||
    repoName === '.' ||
    repoName === '..' ||
    repoName === 'unknown' ||
    repoName.startsWith('-') ||
    !REMOTE_REPO_NAME_PATTERN.test(repoName)
  ) {
    throw new Error(
      'repository name must use only letters, digits, ".", "_", or "-" and must not be "unknown"',

View on GitHub (pinned to 0d1aed942f)

Solutions

  1. Use the full owner/repo path exactly as the forge shows it: git@github.com:owner/repo.git.
  2. Remove any leading slash after the colon and any empty ('//') segments.
  3. Replace '.' or '..' segments with the real directory names — traversal segments are always rejected.
  4. If the repo lives in nested groups/subgroups, keep each group as its own segment: git@gitlab.com:group/subgroup/repo.git.
  5. Convert backslashes to forward slashes if the path was pasted from Windows.

Example fix

// before
remote_url = git@github.com:acme/../outside/repo.git
// after
remote_url = git@github.com:acme/repo.git
Defensive patterns

Strategy: validation

Validate before calling

function pathLooksSafe(url) {
  const m = /^git@[^:\s/]+:(\S+)$/.exec((url ?? '').trim());
  if (!m) return false;
  const parts = m[1].split('/');
  if (m[1].startsWith('/') || parts.length < 2) return false;
  return parts.every((p) => p && p !== '.' && p !== '..' && /^[A-Za-z0-9._-]+$/.test(p));
}
if (!pathLooksSafe(cfg.remote_url)) throw new Error('remote path must be owner/repo, no traversal');

Try / catch

try {
  const cfg = parseAutoSyncConfig(raw);
} catch (e) {
  if (String(e.message).includes('path must include owner/repo')) {
    log.error('remote_url path must be owner/repo with no empty, ".", or ".." segments');
  }
  throw e;
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@0d1aed942f (2026-09-08). Data as JSON: /api/errors/52c5811786ef102f. Report an issue: GitHub.