abhigyanpatwari/GitNexus · error
${filename} resolves outside the repository root
Error message
${filename} resolves outside the repository root What it means
Thrown by readRepoControlFile in repo-control-file.ts when the requested filename, resolved against the repo root, escapes that root: path.relative() yields a path starting with '..' or an absolute path. Repo control files (like .gitnexusrc) are only read from inside the repository to prevent path traversal and reading files outside the project.
Source
Thrown at gitnexus/src/config/repo-control-file.ts:15
import fs from 'node:fs';
import * as path from 'node:path';
export const MAX_REPO_CONTROL_FILE_BYTES = 1024 * 1024;
/** Read a bounded, regular control file owned by the repository root. */
export async function readRepoControlFile(
repoRoot: string,
filename: string,
): Promise<string | null> {
const requestedRoot = path.resolve(repoRoot);
const requested = path.resolve(requestedRoot, filename);
const relative = path.relative(requestedRoot, requested);
if (relative.startsWith('..') || path.isAbsolute(relative)) {
throw new Error(`${filename} resolves outside the repository root`);
}
try {
const canonicalRoot = fs.realpathSync(requestedRoot);
const beforeOpen = fs.lstatSync(requested);
if (beforeOpen.isSymbolicLink()) throw new Error(`${filename} must not be a symbolic link`);
if (!beforeOpen.isFile()) throw new Error(`${filename} must be a regular file`);
if (beforeOpen.nlink !== 1) throw new Error(`${filename} must not be a hard link`);
if (beforeOpen.size > MAX_REPO_CONTROL_FILE_BYTES) {
throw new Error(`${filename} exceeds ${MAX_REPO_CONTROL_FILE_BYTES} bytes`);
}
return await new Promise<string>((resolve, reject) => {
const stream = fs.createReadStream(requested, {
flags: 'r',
start: 0,
end: MAX_REPO_CONTROL_FILE_BYTES,
autoClose: true,
});View on GitHub (pinned to 52924ef12c)
Solutions
- Place the control file inside the repository root and reference it by a root-relative name, e.g. '.gitnexusrc'.
- Remove any '../' or absolute-path components from the filename argument.
- Ensure the process runs with the correct repo root so path.resolve(repoRoot, filename) stays inside it.
- If you need config from another location, copy/symlink-check it into the repo (a plain in-repo file, since symlinks are rejected separately).
Example fix
// before
await readRepoControlFile(repoRoot, '../shared/.gitnexusrc');
// after
cp('../shared/.gitnexusrc', path.join(repoRoot, '.gitnexusrc'));
await readRepoControlFile(repoRoot, '.gitnexusrc'); Defensive patterns
Strategy: validation
Validate before calling
import path from 'node:path';
export function assertInsideRepo(repoRoot: string, filename: string): void {
const root = path.resolve(repoRoot);
const rel = path.relative(root, path.resolve(root, filename));
if (rel.startsWith('..') || path.isAbsolute(rel)) {
throw new Error(`${filename} resolves outside the repository root`);
}
} Try / catch
try {
const cfg = await readRepoControlFile(repoRoot, filename);
} catch (e) {
if (e.message.includes('resolves outside the repository root')) {
console.error(`Control file must live inside ${repoRoot}; got ${filename}`);
process.exitCode = 1;
} else throw e;
} Prevention
- Always pass root-relative filenames like '.gitnexusrc', never user-supplied absolute or ../ paths.
- Sanitize any user input that flows into the filename argument (strip leading '/' and '..' segments).
- Verify the process working directory / repoRoot detection so resolution stays in-repo.
When it happens
Trigger: Passing a filename such as '../other/repo/.gitnexusrc', '/etc/passwd', an absolute path outside the root, or a symlink-resolving name whose lexical resolution escapes the root to readRepoControlFile/loadAnalyzeConfigStrict.
Common situations: Building the config path with untrusted user input, misconfigured working directory so path.resolve lands outside the repo, test harnesses pointing at fixture files outside the repo root, or ../-relative path templates.
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
- path must include owner/repo without traversal
- Path must not contain null bytes
- ${source} entry "${trimmed}" must be an identifier or member
- ${filename} must be a regular file
- local_path must be normalized and must not contain traversal
AI-assisted analysis of abhigyanpatwari/GitNexus@52924ef12c (2026-09-01).
Data as JSON: /api/errors/ec6e552e3d277e24.
Report an issue: GitHub.