{"record":{"id":"a1e552d9b8b3d801","repo":"santifer/career-ops","slug":"local-parser-careers-url-must-be-http-s-value","errorCode":null,"errorMessage":"local-parser: careers_url must be http(s): ${value}","messagePattern":"local-parser: careers_url must be http\\(s\\): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/local-parser.mjs","lineNumber":32,"sourceCode":"\n// `parser.command` / `parser.script` come from portals.yml, which on a shared or\n// template config is not fully trusted. The command must be a known interpreter\n// or a file inside this project — never an arbitrary binary like `rm` or `curl`.\nconst PROJECT_ROOT = realpathSync(resolve(fileURLToPath(new URL('..', import.meta.url))));\nconst ALLOWED_INTERPRETERS = new Set(['python3', 'python', 'node', 'deno', 'bun', 'sh', 'bash']);\n\n// `{careers_url}` and `{company}` are interpolated into the parser's argv. Validate\n// them so an interpolated value can never be read as a CLI flag (argument injection).\nfunction safeCareersUrl(value) {\n  if (!value) return '';\n  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.","sourceCodeStart":14,"sourceCodeEnd":50,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/local-parser.mjs#L14-L50","documentation":"After safeCareersUrl parses the URL, it requires the protocol to be http: or https:. Any other scheme (file:, ftp:, data:, javascript:) is rejected. Unlike the cloud providers, http: is allowed here because an internal/intranet parser target may legitimately be plain HTTP.","triggerScenarios":"entry.careers_url parses but uses a non-http(s) scheme — e.g. file:///path/to/feed, ftp://..., or a data: URL — and the parser template uses {careers_url}.","commonSituations":"A local file:// path was configured as careers_url; an internal tool URL was mis-typed with the wrong scheme; a data: URL was used for a test fixture.","solutions":["Use http:// or https:// for the careers_url value.","If you intended to pass a local file path to the parser, do so via the parser script itself, not the {careers_url} placeholder.","For intranet targets over plain HTTP, http:// is acceptable — just ensure the scheme is present."],"exampleFix":"# before\ncareers_url: file:///opt/feeds/acme.xml\n\n# after\ncareers_url: http://intranet.acme.local/careers","handlingStrategy":"validation","validationCode":"import { URL } from 'node:url';\nexport function isHttpOrHttps(value) {\n  try { const p = new URL(String(value)).protocol; return p === 'http:' || p === 'https:'; } catch { return false; }\n}","typeGuard":"/** @param {string} url */\nfunction isHttpScheme(url) {\n  try { const p = new URL(url).protocol; return p === 'http:' || p === 'https:'; } catch { return false; }\n}","tryCatchPattern":"try {\n  await provider.fetch(entry, ctx);\n} catch (err) {\n  if (err.message.includes('must be http(s)')) console.warn(`fix scheme for ${entry.name}: ${err.message}`);\n  throw err;\n}","preventionTips":["Use http:// or https:// for any careers_url fed to {careers_url}.","Never use file:// or other schemes as careers_url — pass local files via the parser script instead.","Add a config lint rule that rejects non-http(s) schemes on local-parser careers_url values."],"tags":["url-validation","scheme-allowlist","local-parser","argument-injection"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}