abhigyanpatwari/GitNexus · error · InvalidStoragePathError
Resolved storage path must remain directly inside
Error message
Resolved storage path must remain directly inside ${STORAGE_ROOT_ENV}. What it means
storagePathFromRoot derives one repository's isolated slot under an external storage root and verifies the result is a single-level child: the relative path must be non-empty, not climb with '..', not absolute, and the slot's dirname must equal the root. Any violation throws InvalidStoragePathError 'Resolved storage path must remain directly inside GITNEXUS_STORAGE_ROOT'. This prevents a hostile repoPath from constructing slot paths deeper than or outside the configured root.
Solutions
- Ensure rootPath is itself validated (run validateConfiguredStoragePath/root on it) before deriving slots.
- Check the repoPath input: canonicalize it (path.resolve + realpath) so the slot name derivation is stable and single-level.
- Log the computed storagePath and root on failure; if the encoding scheme produces separators, pre-hash the repo path (e.g. slug + hash) before deriving the slot.
Example fix
// before const slot = storagePathFromRoot(rawEnvRoot, remoteRepoUrl); // after const slot = storagePathFromRoot(validateConfiguredStoragePath(rawEnvRoot), path.resolve(realpathSync(repoDir)));
Defensive patterns
Strategy: validation
Validate before calling
const root = validateConfiguredStoragePath(rootPath);
const repoCanon = path.resolve(fs.realpathSync(repoPath));
const slot = storagePathFromRoot(root, repoCanon);
if (path.dirname(slot) !== root) throw new Error('slot must be a direct child of root'); Type guard
const isDirectChild = (root: string, slot: string): boolean => path.dirname(path.resolve(slot)) === path.resolve(root);
Try / catch
try {
return storagePathFromRoot(root, repoPath);
} catch (e) {
if (e instanceof InvalidStoragePathError && e.message.includes('directly inside')) {
throw new Error(`slot derivation left storage root — canonicalize repoPath and re-validate root`);
}
throw e;
} Prevention
- Always pass an already-validated storage root into storagePathFromRoot.
- Canonicalize (realpath) repo paths before slot derivation.
- Test slot derivation with adversarial repo names (slashes, '..', unicode).
When it happens
Trigger: Calling storagePathFromRoot(rootPath, repoPath) where the derived slot name (from the repo path) yields '' , '..' segments, or a nested path so that path.dirname(slot) !== root — e.g. a repoPath that encodes separators/slashes into the slot name.
Common situations: Unusual repository paths (URLs, remote refs, names containing slashes) hashed/encoded into slot names incorrectly; passing a root with trailing separator or a non-resolved root; upgrading between versions that changed slot-name encoding.
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
- resolves outside the repository root
- Invalid upload path
- path must include owner/repo without traversal
- Path must not contain null bytes
- Upload path must not contain traversal segments
AI-assisted analysis of abhigyanpatwari/GitNexus@ac9a4e9abd (2026-09-15).
Data as JSON: /api/errors/97770f43d5500f2c.
Report an issue: GitHub.
Appendix: source
Thrown at gitnexus/src/storage/storage-resolver.ts:278
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}.`,
);
}
return storagePath;
};
const configuredStoragePath = (): string | undefined => {
const value = process.env[STORAGE_PATH_ENV];
return value === undefined ? undefined : validateConfiguredStoragePath(value);
};
const configuredStorageRoot = (repoPath: string): string | undefined => {
const value = process.env[STORAGE_ROOT_ENV];
return value === undefined ? undefined : storagePathFromRoot(value, repoPath);
};
const registeredStoragePath = (repoPath: string): string | undefined => {
let entries: unknown[];View on GitHub (pinned to ac9a4e9abd)