{"record":{"id":"1b64dc73f83d1914","repo":"santifer/career-ops","slug":"local-parser-company-name-cannot-start-with","errorCode":null,"errorMessage":"local-parser: company name cannot start with '-': ${value}","messagePattern":"local-parser: company name cannot start with '-': (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/local-parser.mjs","lineNumber":43,"sourceCode":"  let url;\n  try {\n    url = new URL(String(value));\n  } catch {\n    throw new Error(`local-parser: careers_url is not a valid URL: ${value}`);\n  }\n  if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n    throw new Error(`local-parser: careers_url must be http(s): ${value}`);\n  }\n  return url.href;\n}\n\nfunction safeCompany(value) {\n  if (!value) return '';\n  const name = String(value).trim();\n  // execFile passes args verbatim (no shell), so the only injection risk is a\n  // value that begins like a CLI flag.\n  if (name.startsWith('-')) {\n    throw new Error(`local-parser: company name cannot start with '-': ${value}`);\n  }\n  return name;\n}\n\n// Only validate a placeholder's value when the arg actually uses it — a fixed\n// `parser.script` must not be rejected because some unrelated `{company}` value\n// has punctuation it never sees.\nfunction expandParserArg(value, entry) {\n  let out = String(value);\n  if (out.includes('{careers_url}')) out = out.replaceAll('{careers_url}', safeCareersUrl(entry.careers_url));\n  if (out.includes('{company}')) out = out.replaceAll('{company}', safeCompany(entry.name));\n  return out;\n}\n\nfunction getParserScriptPath(entry) {\n  const parser = entry.parser || {};\n  if (parser.script) return expandParserArg(parser.script, entry);\n","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/local-parser.mjs#L25-L61","documentation":"safeCompany rejects an entry.name that, after trimming, starts with '-'. Because the company name is interpolated into the parser's argv and execFile passes args verbatim (no shell), the only remaining injection vector is a value that looks like a CLI flag (e.g. --eval, -c). This guard closes it.","triggerScenarios":"entry.name (or any value substituted into {company}) begins with '-' after trimming — e.g. '-Acme', '--help', '-c import os'. Only checked when the parser arg template actually contains {company}.","commonSituations":"A test/placeholder entry name like '-company'; a malformed YAML value where a flag-like token leaked into the name field; an adversarial or copy-pasted name beginning with a dash.","solutions":["Rename the entry so the company name does not start with '-' (e.g. 'Acme' instead of '-Acme').","If the dash is intentional (e.g. '-8' brand), prefix with a non-dash character or wrap the value differently in the parser script rather than via argv.","Remove the {company} placeholder from parser.args if the parser does not need it."],"exampleFix":"# before\n- name: '-Acme'\n  parser: { command: python3, script: parsers/acme.py, args: ['{company}'] }\n\n# after\n- name: 'Acme'\n  parser: { command: python3, script: parsers/acme.py, args: ['{company}'] }","handlingStrategy":"validation","validationCode":"export function isSafeCompanyName(value) {\n  return typeof value === 'string' && value.trim().length > 0 && !value.trim().startsWith('-');\n}\n// if (entry.parser?.args?.some(a => String(a).includes('{company}')) && !isSafeCompanyName(entry.name)) failConfig(...);","typeGuard":"/** @param {unknown} v */\nfunction isFlagSafeName(v) {\n  return typeof v === 'string' && v.trim().length > 0 && !v.trim().startsWith('-');\n}","tryCatchPattern":"try {\n  await provider.fetch(entry, ctx);\n} catch (err) {\n  if (err.message.includes(\"cannot start with '-'\")) console.warn(`rename entry ${entry.name}: ${err.message}`);\n  throw err;\n}","preventionTips":["Reject company names starting with '-' at config load time.","Treat the {company} placeholder as carrying attacker-controlled risk — sanitize any external value before interpolation.","Only use {company} in parser.args when the parser genuinely needs it."],"tags":["argument-injection","security","local-parser","config"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}