{"record":{"id":"83fcc3f1d7601e9d","repo":"santifer/career-ops","slug":"wttj-api-env-payload-is-not-valid-json","errorCode":null,"errorMessage":"wttj: /api/env payload is not valid JSON","messagePattern":"wttj: /api/env payload is not valid JSON","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/wttj.mjs","lineNumber":61,"sourceCode":"  }\n  return url;\n}\n\n/**\n * Parse the `window.env = {...}` payload served by /api/env and extract the\n * 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 *","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/wttj.mjs#L43-L79","documentation":"A {...} span was found in /api/env but JSON.parse rejected it. The brace-matching is greedy (first { to last }), so any stray brace elsewhere on the page (inline JS, analytics) can make the span invalid JSON.","triggerScenarios":"The page has additional {/} outside the env object so the greedy slice captures invalid JSON; WTTJ added inline scripts with brace characters; the env object itself now contains unquoted keys or trailing commas that strict JSON rejects; the response was truncated mid-object.","commonSituations":"WTTJ added inline JS with braces; a partial response truncating the object; a CDN injecting a script tag with braces.","solutions":["Inspect the raw /api/env body and locate the real window.env = {...} block.","Tighten extraction to the window.env assignment instead of greedy first-brace-to-last-brace.","If WTTJ now emits a JS object literal (unquoted keys, trailing commas), switch to a tolerant parser or a brace-balanced extractor."],"exampleFix":"// before — greedy slice can capture stray braces\nconst start = text.indexOf(\"{\");\nconst end = text.lastIndexOf(\"}\");\nenv = JSON.parse(text.slice(start, end + 1));\n// after — scope to window.env and balance braces\nconst assign = text.indexOf(\"window.env\");\nconst firstBrace = text.indexOf(\"{\", assign);\nconst obj = balanceBraces(text, firstBrace); // returns the matched {...}\nenv = JSON.parse(obj);","handlingStrategy":"try-catch","validationCode":"const slice = envText.slice(envText.indexOf(\"{\"), envText.lastIndexOf(\"}\") + 1);\ntry { JSON.parse(slice); }\ncatch { console.warn(\"wttj env slice is not strict JSON — extraction may need updating\"); }","typeGuard":null,"tryCatchPattern":"try { parseEnvPayload(envText); }\ncatch (err) {\n  if (/not valid JSON/.test(err.message)) { logUpstreamChange(\"wttj\", err.message); }\n  throw err;\n}","preventionTips":["Prefer a scoped window.env = {...} extraction over greedy brace matching.","Alert on this error as a likely WTTJ markup change.","Smoke-test parseEnvPayload against a captured real payload in CI."],"tags":["api-contract","wttj","parsing","json","algolia"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}