{"record":{"id":"4461e94c31d68dc3","repo":"santifer/career-ops","slug":"garena-name-is-not-a-usable-url-segment-json-stringify-value","errorCode":null,"errorMessage":"garena: ${name} is not a usable URL segment: ${JSON.stringify(value)}","messagePattern":"garena: (.+?) is not a usable URL segment: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"providers/garena.mjs","lineNumber":95,"sourceCode":"/** @param {{ garena?: { office?: string } }} entry */\nfunction resolveOffice(entry) {\n  const office = entry && entry.garena && entry.garena.office;\n  return typeof office === 'string' && office.trim() ? office.trim() : DEFAULT_OFFICE;\n}\n\n/**\n * Escapes a config-derived value before it is interpolated into a Garena URL.\n * `office` is a `portals.yml` segment, so a malformed one is a config bug and\n * should fail loudly: separators (`/`, `?`, `#`, ...) are percent-escaped, and\n * `.`/`..` are rejected outright because escaping leaves them intact as\n * traversal segments.\n * @param {string} name - Field name, for the error message.\n * @param {string} value\n * @returns {string}\n */\nfunction urlSegment(name, value) {\n  if (value === '.' || value === '..') {\n    throw new Error(`garena: ${name} is not a usable URL segment: ${JSON.stringify(value)}`);\n  }\n  return encodeURIComponent(value);\n}\n\n/**\n * Escapes the per-posting `id` from the API response. Unlike `urlSegment`, a\n * bad value here returns `null` rather than throwing: `id` is host-controlled\n * and sits inside the parse loop, so a lone surrogate (URIError) or a `.`/`..`\n * traversal segment must drop only that posting, not unwind the whole page\n * (#3513).\n * @param {string} id\n * @returns {string | null}\n */\nfunction idUrlSegment(id) {\n  if (id === '.' || id === '..') return null;\n  return safeEncodeURIComponent(id);\n}\n","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/garena.mjs#L77-L113","documentation":"urlSegment in providers/garena.mjs builds URL path segments by encodeURIComponent-ing configured values (e.g. office names). Before encoding it rejects the values '.' and '..' outright, because percent-encoded dot segments can still be interpreted as relative path traversal by some servers. The error names the field (name) and shows the offending value as JSON.","triggerScenarios":"A portals.yml garena.office entry set to '.', '..', or a value that resolves to one of these via resolveOffice (e.g. empty/missing config falling through to a dot, or a stray '.' left in the config by hand).","commonSituations":"A config cleanup that replaced an office name with '.' meaning 'current directory' by habit; an empty office value coerced into a dot segment upstream; copy-pasting a filesystem-style relative path into the office field.","solutions":["Set the office field in the portals.yml garena entry to a real office slug (a non-empty name without dot segments).","Check resolveOffice's fallback: if the office key is missing, provide it explicitly instead of letting a degenerate value flow in.","Strip or validate config values before they reach the provider (reject '', '.', '..' at config-load time).","Re-run the scan after fixing the entry to confirm a usable URL is built."],"exampleFix":"// before\ngarena:\n  office: \".\"   # rejected by urlSegment\n// after\ngarena:\n  office: \"singapore\"","handlingStrategy":"validation","validationCode":"function isSafeSegment(v) {\n  return typeof v === 'string' && v.length > 0 && v !== '.' && v !== '..';\n}\nif (!isSafeSegment(entry?.garena?.office)) {\n  throw new Error(`config: garena.office must be a real slug for \"${entry.name}\"`);\n}","typeGuard":"const isSafeUrlSegment = (v) =>\n  typeof v === 'string' && v.length > 0 && v !== '.' && v !== '..';","tryCatchPattern":"try {\n  const jobs = parseGarenaResponse(json, entry);\n} catch (e) {\n  if (e.message.includes('is not a usable URL segment')) {\n    console.error(`Fix garena.office for \"${entry.name}\" — got a dot segment`);\n    return [];\n  }\n  throw e;\n}","preventionTips":["Never leave office values empty, '.', or '..' in portals.yml.","Validate slug-like config fields (^[^./][^/]*$ or similar) when the config is loaded.","Document which garena fields become URL path segments so editors know the constraints."],"tags":["config","url-validation","path-traversal","input-validation"],"backgroundTag":"invalid-argument-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"}