{"record":{"id":"b2d8e222179d0c80","repo":"santifer/career-ops","slug":"wttj-unexpected-algolia-app-id-appid","errorCode":null,"errorMessage":"wttj: unexpected Algolia app id \"${appId}\"","messagePattern":"wttj: unexpected Algolia app id \"(.+?)\"","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/wttj.mjs","lineNumber":92,"sourceCode":" * Algolia application id + client search key.\n * @param {string} text\n * @returns {{ appId: string, apiKey: string }}\n */\nexport function parseEnvPayload(text) {\n  const start = text.indexOf('{');\n  const end = text.lastIndexOf('}');\n  if (start === -1 || end <= start) throw new Error('wttj: /api/env payload has no JSON object');\n  let env;\n  try {\n    env = JSON.parse(text.slice(start, end + 1));\n  } catch {\n    throw new Error('wttj: /api/env payload is not valid JSON');\n  }\n  const appId = typeof env.PUBLIC_ALGOLIA_APPLICATION_ID === 'string' ? env.PUBLIC_ALGOLIA_APPLICATION_ID.trim() : '';\n  const apiKey = typeof env.PUBLIC_ALGOLIA_API_KEY_CLIENT === 'string' ? env.PUBLIC_ALGOLIA_API_KEY_CLIENT.trim() : '';\n  // App ids are short alphanumerics; validating keeps the derived Algolia\n  // hostname from being attacker-shaped if the env payload ever changes.\n  if (!/^[A-Z0-9]{6,16}$/i.test(appId)) throw new Error(`wttj: unexpected Algolia app id \"${appId}\"`);\n  // The key is only ever sent as a request header (never used to build a\n  // host), so don't over-constrain its format — WTTJ may rotate to a longer\n  // or non-hex (e.g. secured/base64) client key. Length bounds only.\n  if (!apiKey || apiKey.length < 16 || apiKey.length > 500) {\n    throw new Error('wttj: unexpected Algolia api key shape');\n  }\n  return { appId, apiKey };\n}\n\n/**\n * Normalize a single Algolia hit. Exported for tests.\n *\n * Field mapping → normalized Job shape:\n *   - title:    `name`\n *   - url:      /en/companies/{organization.slug}/jobs/{slug} on the WTTJ site\n *   - company:  `organization.name`\n *   - location: offices[0] city+country, with \", Remote\" appended when the\n *               posting allows fulltime remote","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/wttj.mjs#L74-L110","documentation":"parseEnvPayload extracts the Algolia application id from WTTJ's /api/env payload and validates it against /^[A-Z0-9]{6,16}$/i before using it to derive the Algolia hostname. If PUBLIC_ALGOLIA_APPLICATION_ID is missing, empty, or shaped differently than expected, this error is thrown to prevent building an attacker-shaped hostname from an untrusted value. The app id is the interpolation in the message, so it shows exactly what was received.","triggerScenarios":"The fetched /api/env payload has no PUBLIC_ALGOLIA_APPLICATION_ID field (property renamed or removed), the field is not a string, it is empty/whitespace after trim, or its value is not 6-16 alphanumeric characters (e.g. truncated, HTML error page captured, or WTTJ rotated to a different id format).","commonSituations":"WTTJ changes their public env payload field names; a proxy or anti-bot page returns HTML instead of the JSON env payload; the scraper hits a regional/changed endpoint; network middleware rewrites the response.","solutions":["Fetch https://www.welcometothejungle.com and inspect the /api/env response to confirm PUBLIC_ALGOLIA_APPLICATION_ID exists and note its exact value","If the field was renamed, update the property name in parseEnvPayload to match the new payload key","Relax or update the /^[A-Z0-9]{6,16}$/i regex only if the new id format is verified legitimate, keeping the length bound to avoid attacker-shaped hosts","Clear any caching layer (CDN, HTTP cache) that may have stored a stale or error response for /api/env"],"exampleFix":"// before: WTTJ renamed the field\nconst appId = typeof env.PUBLIC_ALGOLIA_APPLICATION_ID === 'string' ? env.PUBLIC_ALGOLIA_APPLICATION_ID.trim() : '';\n// after\nconst appId = typeof (env.PUBLIC_ALGOLIA_APPLICATION_ID ?? env.PUBLIC_ALGOLIA_APP_ID) === 'string'\n  ? (env.PUBLIC_ALGOLIA_APPLICATION_ID ?? env.PUBLIC_ALGOLIA_APP_ID).trim() : '';","handlingStrategy":"validation","validationCode":"// before calling the provider, sanity-check the env payload yourself\nconst env = JSON.parse(envText);\nconst appId = env?.PUBLIC_ALGOLIA_APPLICATION_ID;\nif (typeof appId !== 'string' || !/^[A-Z0-9]{6,16}$/i.test(appId.trim())) {\n  throw new Error('env payload lacks a usable Algolia app id — WTTJ page changed?');\n}","typeGuard":"function hasAlgoliaAppId(env) {\n  return typeof env?.PUBLIC_ALGOLIA_APPLICATION_ID === 'string' &&\n    /^[A-Z0-9]{6,16}$/.test(env.PUBLIC_ALGOLIA_APPLICATION_ID.trim());\n}","tryCatchPattern":"try {\n  await scanWttj(entry);\n} catch (e) {\n  if (e.message.startsWith('wttj: unexpected Algolia app id')) {\n    console.warn('WTTJ env payload changed or was intercepted — inspect /api/env manually.');\n  } else throw e;\n}","preventionTips":["Check the /api/env payload shape after any WTTJ scraping failure before changing code","Never widen the appId regex beyond alphanumerics — the id is used to build a hostname","Cache the env payload briefly but revalidate on repeated failures","Pin tests that parse a recorded env payload fixture to catch field renames early"],"tags":["validation","scraper","algolia","wttj"],"backgroundTag":"invalid-env-var-value","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}