abhigyanpatwari/GitNexus · critical · InvalidStoragePathError

Storage path escaped its parent directory.

Error message

Storage path escaped its parent directory.

What it means

validateConfiguredStoragePath computes path.relative(parent, inspected) and throws if the result starts with '..' or is absolute — the storage path resolved outside its parent directory. This is a path-traversal guard ensuring the configured path cannot climb out (via '..' segments or symlink-style tricks) of the directory it is supposed to be contained in.

Solutions

  1. Normalize/verify before the call: ensure path.resolve(value) stays within the intended base via path.relative(base, path.resolve(value)).startsWith('..') === false.
  2. Reject user-supplied '..' segments at the boundary instead of resolving them.
  3. Use storagePathFromRoot(root, repoPath) to derive slots from a fixed root rather than accepting free-form paths.

Example fix

// before
configuredStoragePath(path.join(base, userInput)); // userInput = '../other'
// after
const candidate = path.resolve(base, userInput);
if (path.relative(base, candidate).startsWith('..') || path.isAbsolute(path.relative(base, candidate))) {
  throw new Error('storage path escapes base directory');
}
configuredStoragePath(candidate);
Defensive patterns

Strategy: validation

Validate before calling

const candidate = path.resolve(value);
const rel = path.relative(intendedBase, candidate);
if (rel.startsWith('..') || path.isAbsolute(rel)) {
  throw new Error(`storage path escapes base directory: ${value}`);
}

Type guard

const staysWithin = (base: string, target: string): boolean => {
  const rel = path.relative(path.resolve(base), path.resolve(target));
  return rel !== '' && !rel.startsWith('..') && !path.isAbsolute(rel);
};

Try / catch

try {
  const p = registeredStoragePath(repoPath);
} catch (e) {
  if (e instanceof InvalidStoragePathError && e.message.includes('escaped')) {
    throw new Error(`traversal attempt in registered storage path for ${repoPath}; audit registry/config input`);
  }
  throw e;
}

Prevention

When it happens

Trigger: Passing a storage path containing '..' segments that escape the parent (e.g. /data/repos/../elsewhere) to configuredStoragePath, registeredStoragePath, resolved/resolvedStoragePath, or readOwnershipMetadata.

Common situations: Untrusted storage paths from HTTP/CLI concatenated without normalization; config values assembled from user input; overly clever templating inserting '..' segments.

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@ac9a4e9abd (2026-09-15). Data as JSON: /api/errors/d1f8939577def11e. Report an issue: GitHub.

Appendix: source

Thrown at gitnexus/src/storage/storage-resolver.ts:262

};

export const defaultStoragePath = (repoPath: string): string =>
  path.join(resolveRepoPath(repoPath), GITNEXUS_DIR);

export const validateConfiguredStoragePath = (value: string): string => {
  const resolved = validateAbsolutePath(value, 'Storage path');
  const parent = path.dirname(resolved);
  const base = path.basename(resolved);
  if (base.length === 0) {
    throw new InvalidStoragePathError('Storage path must not be a filesystem root.');
  }
  // Rebuild through parent + basename and apply the path.relative idiom
  // CodeQL's js/path-injection sanitizer recognizes. The reconstructed path
  // is what callers pass to filesystem APIs.
  const inspected = path.resolve(parent, base);
  const rel = path.relative(parent, inspected);
  if (rel.startsWith('..') || path.isAbsolute(rel)) {
    throw new InvalidStoragePathError('Storage path escaped its parent directory.');
  }
  return inspected;
};

/** Resolve one repository's isolated slot under an external storage root. */
export const storagePathFromRoot = (rootPath: string, repoPath: string): string => {
  const root = validateAbsolutePath(rootPath, STORAGE_ROOT_ENV);
  const storagePath = path.resolve(root, storageSlotName(repoPath));
  const rel = path.relative(root, storagePath);
  if (
    rel === '' ||
    rel.startsWith('..') ||
    path.isAbsolute(rel) ||
    !samePath(path.dirname(storagePath), root)
  ) {
    throw new InvalidStoragePathError(
      `Resolved storage path must remain directly inside ${STORAGE_ROOT_ENV}.`,
    );

View on GitHub (pinned to ac9a4e9abd)