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
- Normalize/verify before the call: ensure path.resolve(value) stays within the intended base via path.relative(base, path.resolve(value)).startsWith('..') === false.
- Reject user-supplied '..' segments at the boundary instead of resolving them.
- 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
- Normalize user input with path.resolve before joining into storage paths.
- Reject '..' segments from untrusted sources instead of resolving them.
- Prefer storagePathFromRoot over free-form path construction for slots.
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
- Refusing to adopt branch metadata: branch storage target…
- Clone target must be a subdirectory of
- resolves outside the repository root
- local_path must be normalized and must not contain…
- path must include owner/repo without traversal
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)