{"record":{"id":"957bd538f0e96738","repo":"abhigyanpatwari/GitNexus","slug":"parameter-fieldname-must-be-a-single-string","errorCode":null,"errorMessage":"Parameter \"${fieldName}\" must be a single string, got an array","messagePattern":"Parameter \"(.+?)\" must be a single string, got an array","errorType":"exception","errorClass":"BadRequestError","httpStatus":400,"severity":"error","filePath":"gitnexus/src/server/validation.ts","lineNumber":60,"sourceCode":"  }\n}\n\n/**\n * Type guard for HTTP request parameters that must be a single string.\n *\n * Express's req.query and req.body parsers return `string | string[] | ParsedQs`\n * for any field, but route handlers commonly cast to `string` and operate on\n * `.length`. When the caller passes the same key twice (?x=a&x=b) the value\n * arrives as an array, and a `.length` check intended for the string ends up\n * counting array elements — bypassing length-based guards (CodeQL\n * js/type-confusion-through-parameter-tampering, alert at api.ts:1118).\n *\n * @throws BadRequestError when value is not a string (array, object, undefined, etc.)\n */\nexport function assertString(value: unknown, fieldName: string): string {\n  if (typeof value !== 'string') {\n    if (Array.isArray(value)) {\n      throw new BadRequestError(`Parameter \"${fieldName}\" must be a single string, got an array`);\n    }\n    throw new BadRequestError(`Parameter \"${fieldName}\" must be a string`);\n  }\n  return value;\n}\n\n/**\n * Resolve a user-supplied relative path against an allowed root and verify it\n * stays inside that root. Mirrors the existing guard at api.ts:1067-1077.\n *\n * Returns the absolute resolved path. Rejects empty paths, null bytes, and\n * paths that resolve outside the root (e.g., `../../../etc/passwd`).\n *\n * @throws BadRequestError when the path is empty or contains a null byte\n * @throws ForbiddenError when the resolved path escapes the root\n */\nexport function assertSafePath(rawPath: string, root: string): string {\n  if (rawPath.length === 0) {","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/aac7515d2a8c50a1f8f923c6fb77218b333560d6/gitnexus/src/server/validation.ts#L42-L78","documentation":"Express's qs parsers turn a repeated query/body key (?x=a&x=b) into an array, which silently defeats .length guards written for a value cast to string — the exact CodeQL js/type-confusion-through-parameter-tampering shape (alert at api.ts:1118). assertString is the hardening guard: when the value is specifically an array it throws BadRequestError (HTTP 400) naming the field, so callers fail fast instead of measuring array length as if it were string length.","triggerScenarios":"Calling any /api route that passes a query or body field through assertString with the same key present twice: GET /api/...?q=a&q=b, curl with repeated --data-urlencode flags, or a JSON body {\"q\": [\"a\",\"b\"]}.","commonSituations":"HTTP clients whose params serializer expands JS arrays into repeated keys (axios default paramsSerializer, superagent); hand-built query strings that append a default and then user input; intermediaries or proxies re-appending a parameter.","solutions":["Send the parameter exactly once per request","Fix the client's serializer so array-valued options use a distinct key or are joined into one value","If multi-value is genuinely intended, add an explicit array-accepting validator server-side instead of bypassing assertString"],"exampleFix":"// before — axios expands arrays into repeated keys → 400\naxios.get('/api/grep', { params: { q: ['a', 'b'] } });\n\n// after\naxios.get('/api/grep', { params: { q: 'a' } });\n// or join client-side into a single value\naxios.get('/api/grep', { params: { q: ['a', 'b'].join(',') } });","handlingStrategy":"type-guard","validationCode":"const params = new URLSearchParams(query);\nfor (const key of [...params.keys()]) {\n  if (params.getAll(key).length > 1) {\n    throw new Error(`Duplicate query key \"${key}\" — send a single value`);\n  }\n}","typeGuard":"function isSingleString(v: unknown): v is string {\n  return typeof v === 'string';\n}","tryCatchPattern":"try {\n  await apiCall(req);\n} catch (e) {\n  if (e instanceof BadRequestError && e.message.includes('single string')) {\n    // client bug: the same key was sent twice — fix the sender, do not retry\n  }\n  throw e;\n}","preventionTips":["Configure your HTTP client's params serializer to never expand arrays into repeated keys","Assert typeof value === 'string' before any .length-based check on query/body fields","Log the offending field name from the 400 body to locate the duplicate-key sender quickly"],"tags":["validation","express","query-parameters","parameter-tampering","type-confusion"],"backgroundTag":"parameter-validation-failed","analyzedSha":"aac7515d2a8c50a1f8f923c6fb77218b333560d6","analyzedAt":"2026-08-20T23:29:22.980Z","schemaVersion":2},"datasetVersion":"2026-08-22T20:17:22.307Z"}