santifer/career-ops · error · Error

local-parser: careers_url must be http(s)

Error message

local-parser: careers_url must be http(s): ${value}

What it means

safeCareersUrl() accepts only http: and https: protocols when interpolating {careers_url} into a local parser's argv. A parseable URL with any other scheme (ftp:, file:, javascript:, data:, etc.) is rejected to keep subprocess arguments safe.

Solutions

  1. Change careers_url in portals.yml to start with https:// (preferred) or http://.
  2. Remove the `{careers_url}` placeholder from parser.args if the parser does not actually need the URL.
  3. Confirm the field is a public web URL, not a local file path — use parser.script pointing at an in-repo file instead.
  4. Wrap fetch() calls in try-catch and surface a clear config-validation message before scan runs.

Example fix

// before (portals.yml)
careers_url: file:///home/user/jobs.html
// after
careers_url: https://acme.com/jobs.html
Defensive patterns

Strategy: validation

Validate before calling

const u = new URL(entry.careers_url);
if (u.protocol !== 'http:' && u.protocol !== 'https:') throw new Error(`${entry.name}: careers_url must be http(s), got ${u.protocol}`);

Try / catch

try {
  await localParser.fetch(entry);
} catch (e) {
  if (String(e.message).includes('careers_url must be http(s)')) {
    console.error(`${entry.name}: use a public web URL (https://), not a file/ftp path`);
    return [];
  }
  throw e;
}

Prevention

When it happens

Trigger: An entry's careers_url parses via `new URL()` but its protocol is not http/https — e.g. careers_url: file:///etc/passwd or ftp://acme.com/jobs — and a parser arg references `{careers_url}`.

Common situations: Local file paths pasted into careers_url (file:// or bare paths are caught here after URL-parsing quirks); internal ftp links in older configs; someone experimenting with javascript:/data: URLs.

Understand the failure class

Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.

Related errors


AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16). Data as JSON: /api/errors/a1e552d9b8b3d801. Report an issue: GitHub.

Appendix: source

Thrown at providers/local-parser.mjs:32

// `parser.command` / `parser.script` come from portals.yml, which on a shared or
// template config is not fully trusted. The command must be a known interpreter
// or a file inside this project — never an arbitrary binary like `rm` or `curl`.
const PROJECT_ROOT = realpathSync(resolve(fileURLToPath(new URL('..', import.meta.url))));
const ALLOWED_INTERPRETERS = new Set(['python3', 'python', 'node', 'deno', 'bun', 'sh', 'bash']);

// `{careers_url}` and `{company}` are interpolated into the parser's argv. Validate
// them so an interpolated value can never be read as a CLI flag (argument injection).
function safeCareersUrl(value) {
  if (!value) return '';
  let url;
  try {
    url = new URL(String(value));
  } catch {
    throw new Error(`local-parser: careers_url is not a valid URL: ${value}`);
  }
  if (url.protocol !== 'http:' && url.protocol !== 'https:') {
    throw new Error(`local-parser: careers_url must be http(s): ${value}`);
  }
  return url.href;
}

function safeCompany(value) {
  if (!value) return '';
  const name = String(value).trim();
  // execFile passes args verbatim (no shell), so the only injection risk is a
  // value that begins like a CLI flag.
  if (name.startsWith('-')) {
    throw new Error(`local-parser: company name cannot start with '-': ${value}`);
  }
  return name;
}

// Only validate a placeholder's value when the arg actually uses it — a fixed
// `parser.script` must not be rejected because some unrelated `{company}` value
// has punctuation it never sees.

View on GitHub (pinned to aac998c7ed)