santifer/career-ops · error · Error
local-parser: path escapes the project root: ${rawPath}
Error message
local-parser: path escapes the project root: ${rawPath} What it means
resolveInsideRoot resolves a configured path against PROJECT_ROOT via realpathSync and confirms the resolved path is either PROJECT_ROOT itself or begins with PROJECT_ROOT + path separator. If not, the path escapes the project tree and this error fires. It is the path-traversal guard for parser scripts/args — it stops '../' or symlinked paths from executing files outside the repo.
Source
Thrown at providers/local-parser.mjs:85
return scriptArg ? expandParserArg(scriptArg, entry) : null;
}
function buildParserArgs(entry) {
const parser = entry.parser || {};
const args = [];
if (parser.script) args.push(parser.script);
if (Array.isArray(parser.args)) args.push(...parser.args);
return args.map(arg => expandParserArg(arg, entry));
}
// Resolve a configured path and confirm it stays inside the project tree.
function resolveInsideRoot(rawPath) {
const resolved = realpathSync(resolve(PROJECT_ROOT, String(rawPath)));
if (resolved !== PROJECT_ROOT && !resolved.startsWith(PROJECT_ROOT + sep)) {
throw new Error(`local-parser: path escapes the project root: ${rawPath}`);
}
return resolved;
}
// The command is either a whitelisted interpreter (resolved via PATH) or a script
// that lives inside the repo. Anything else is rejected.
function resolveCommand(command) {
const value = String(command || '');
if (!value) throw new Error('local-parser: parser.command is required');
if (!value.includes('/') && ALLOWED_INTERPRETERS.has(value)) return value;
return resolveInsideRoot(value);
}
// Validate the whole invocation and return what to spawn. Throws on anything unsafe.
function resolveInvocation(entry) {
const rawCommand = String(entry.parser?.command || '');
const command = resolveCommand(rawCommand);
const args = buildParserArgs(entry);View on GitHub (pinned to 9b17a8ac97)
Solutions
- Move the parser script inside the project tree and reference it with a repo-relative path.
- If you need a system interpreter, set it as parser.command (a whitelisted interpreter like python3/node), not as a script path.
- Remove any '../' segments or symlinks that escape the repo.
- Run realpathSync on the path manually to see where it actually resolves.
Example fix
# before parser: command: python3 script: /opt/parsers/acme.py # after (script moved into repo) parser: command: python3 script: parsers/acme.py
Defensive patterns
Strategy: validation
Validate before calling
import { realpathSync } from 'node:fs';
import { resolve, sep } from 'node:path';
export function isInsideRoot(rawPath, root) {
try {
const resolved = realpathSync(resolve(root, String(rawPath)));
return resolved === root || resolved.startsWith(root + sep);
} catch { return false; }
} Type guard
import { realpathSync } from 'node:fs';
import { resolve, sep } from 'node:path';
/** @param {string} rawPath @param {string} root */
function staysInRoot(rawPath, root) {
try {
const r = realpathSync(resolve(root, rawPath));
return r === root || r.startsWith(root + sep);
} catch { return false; }
} Try / catch
try {
await provider.fetch(entry, ctx);
} catch (err) {
if (err.message.includes('escapes the project root')) console.error(`[security] ${err.message}`);
throw err;
} Prevention
- Keep parser scripts inside the repo; reference them with repo-relative paths.
- Use a whitelisted interpreter as parser.command and an in-repo script as parser.script.
- Never configure absolute system paths or '../'-laden relative paths as script paths.
- Treat a path-escape error as a security signal and audit how the path was configured.
When it happens
Trigger: A configured parser.script or parser path resolves outside PROJECT_ROOT — e.g. '../../etc/passwd', an absolute path like '/usr/bin/python3' passed as a script, or a symlink inside the repo that points outside. realpathSync resolves symlinks before the prefix check, so a symlinked escape is also caught.
Common situations: An absolute system path was given as a script; a relative path with '../' segments; a symlink under the repo pointing to an external binary; the repo was moved and PROJECT_ROOT no longer matches the script location.
Related errors
- refusing to hash symlink: ${childRel}
- apify: invalid actorId ${JSON.stringify(actorId)}. Expected
- local-parser: company name cannot start with '-': ${value}
- local-parser: interpreter command requires an in-repo parser
- local-parser: the parser script must be the interpreter's fi
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/00897c415f79e122.
Report an issue: GitHub.