{"record":{"id":"45dba1f1495445e1","repo":"santifer/career-ops","slug":"careerviet-url-must-use-https-url","errorCode":null,"errorMessage":"careerviet: URL must use HTTPS: ${url}","messagePattern":"careerviet: URL must use HTTPS: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/careerviet.mjs","lineNumber":108,"sourceCode":" * apart; both wrap their <time> the same way.\n */\nconst UPDATED_DATE_RE = /Cập nhật(?:<!--[\\s\\S]*?-->)?\\s*:?\\s*(?:<\\/span>)?\\s*<time>([\\d/-]+)<\\/time>/i;\n\n/** @param {any} ctx @param {number} ms */\nfunction sleep(ctx, ms) {\n  if (typeof ctx?.sleep === 'function') return ctx.sleep(ms);\n  return new Promise((r) => setTimeout(r, ms));\n}\n\n/** @param {string} url */\nfunction assertCareerVietUrl(url) {\n  let parsed;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`careerviet: invalid URL: ${url}`);\n  }\n  if (parsed.protocol !== 'https:') throw new Error(`careerviet: URL must use HTTPS: ${url}`);\n  if (parsed.hostname !== TRUSTED_HOST) {\n    throw new Error(`careerviet: untrusted hostname \"${parsed.hostname}\" — must be ${TRUSTED_HOST}`);\n  }\n  return url;\n}\n\n/**\n * Collapse a markup fragment to its visible text.\n * @param {string} fragment\n * @returns {string}\n */\nexport function visibleText(fragment) {\n  return decodeEntities(\n    String(fragment ?? '')\n      .replace(/<!--[\\s\\S]*?-->/g, ' ')\n      .replace(/<[^>]+>/g, ' '),\n  )\n    .replace(/\\s+/g, ' ')","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/santifer/career-ops/blob/1696bec4d021768e7359f9aad6b329cba883da20/providers/careerviet.mjs#L90-L126","documentation":"The CareerViet provider enforces HTTPS on every request URL. When assertCareerVietUrl parses the URL but parsed.protocol is not 'https:', it throws this error. The provider scrapes server-rendered search pages over plain HTTPS; allowing http would silently downgrade transport and contradict the fixed-host security posture (only https://careerviet.vn is ever requested).","triggerScenarios":"An entry or direct call supplying http://careerviet.vn/...; code that builds the URL from an http base constant; a proxy or test harness rewriting the scheme; calling assertCareerVietUrl on a constructed page URL where the base was http.","commonSituations":"Legacy config predating an HTTPS-only policy; developer testing against a local http mock of the board; URL copied from an insecure mirror or cached http link.","solutions":["Change the configured URL scheme to https://","If the URL is built dynamically, use an https base: new URL(path, 'https://careerviet.vn')","Remove any middleware/proxy that downgrades https to http for these requests","Pre-validate with new URL(u).protocol === 'https:' before passing URLs into the provider"],"exampleFix":"// before\nconst url = `http://careerviet.vn/viec-lam?page=${page}`;\n// after\nconst url = `https://careerviet.vn/viec-lam?page=${page}`;","handlingStrategy":"validation","validationCode":"function isHttpsUrl(value) {\n  if (typeof value !== 'string') return false;\n  try { return new URL(value).protocol === 'https:'; } catch { return false; }\n}\nif (!isHttpsUrl(entry.careers_url)) entry.careers_url = entry.careers_url.replace(/^http:/, 'https:');","typeGuard":"function isHttpsCareervietUrl(value) {\n  if (typeof value !== 'string') return false;\n  try {\n    const p = new URL(value);\n    return p.protocol === 'https:' && p.hostname === 'careerviet.vn';\n  } catch { return false; }\n}","tryCatchPattern":"try {\n  await provider.fetch(entry, ctx);\n} catch (err) {\n  if (String(err.message).includes('must use HTTPS')) {\n    console.error(`${entry.name}: careerviet URLs must start with https://`);\n  } else throw err;\n}","preventionTips":["Always configure https:// URLs for this provider — http is rejected by design","Use an https base constant when constructing page URLs","Avoid proxies that downgrade TLS for board scraping","Lint provider entries for non-https schemes at startup"],"tags":["url-validation","https","security","provider"],"backgroundTag":"insecure-scheme-rejected","analyzedSha":"1696bec4d021768e7359f9aad6b329cba883da20","analyzedAt":"2026-09-01T19:19:23.111Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}