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

  1. Move the parser script inside the project tree and reference it with a repo-relative path.
  2. If you need a system interpreter, set it as parser.command (a whitelisted interpreter like python3/node), not as a script path.
  3. Remove any '../' segments or symlinks that escape the repo.
  4. 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

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


AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13). Data as JSON: /api/errors/00897c415f79e122. Report an issue: GitHub.