abhigyanpatwari/GitNexus · error · BadRequestError
Parameter "${fieldName}" must be a single string, got an arr
Error message
Parameter "${fieldName}" must be a single string, got an array What it means
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.
Source
Thrown at gitnexus/src/server/validation.ts:60
}
}
/**
* Type guard for HTTP request parameters that must be a single string.
*
* Express's req.query and req.body parsers return `string | string[] | ParsedQs`
* for any field, but route handlers commonly cast to `string` and operate on
* `.length`. When the caller passes the same key twice (?x=a&x=b) the value
* arrives as an array, and a `.length` check intended for the string ends up
* counting array elements — bypassing length-based guards (CodeQL
* js/type-confusion-through-parameter-tampering, alert at api.ts:1118).
*
* @throws BadRequestError when value is not a string (array, object, undefined, etc.)
*/
export function assertString(value: unknown, fieldName: string): string {
if (typeof value !== 'string') {
if (Array.isArray(value)) {
throw new BadRequestError(`Parameter "${fieldName}" must be a single string, got an array`);
}
throw new BadRequestError(`Parameter "${fieldName}" must be a string`);
}
return value;
}
/**
* Resolve a user-supplied relative path against an allowed root and verify it
* stays inside that root. Mirrors the existing guard at api.ts:1067-1077.
*
* Returns the absolute resolved path. Rejects empty paths, null bytes, and
* paths that resolve outside the root (e.g., `../../../etc/passwd`).
*
* @throws BadRequestError when the path is empty or contains a null byte
* @throws ForbiddenError when the resolved path escapes the root
*/
export function assertSafePath(rawPath: string, root: string): string {
if (rawPath.length === 0) {View on GitHub (pinned to aac7515d2a)
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
Example fix
// before — axios expands arrays into repeated keys → 400
axios.get('/api/grep', { params: { q: ['a', 'b'] } });
// after
axios.get('/api/grep', { params: { q: 'a' } });
// or join client-side into a single value
axios.get('/api/grep', { params: { q: ['a', 'b'].join(',') } }); Defensive patterns
Strategy: type-guard
Validate before calling
const params = new URLSearchParams(query);
for (const key of [...params.keys()]) {
if (params.getAll(key).length > 1) {
throw new Error(`Duplicate query key "${key}" — send a single value`);
}
} Type guard
function isSingleString(v: unknown): v is string {
return typeof v === 'string';
} Try / catch
try {
await apiCall(req);
} catch (e) {
if (e instanceof BadRequestError && e.message.includes('single string')) {
// client bug: the same key was sent twice — fix the sender, do not retry
}
throw e;
} Prevention
- 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
When it happens
Trigger: 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"]}.
Common situations: 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.
Related errors
- Parameter "${fieldName}" must be a string
- "url" must be a string
- ${source} entries must all be strings.
- ${source} entries must not be empty.
- ${source} entry "${trimmed}" must be an identifier or member
AI-assisted analysis of abhigyanpatwari/GitNexus@aac7515d2a (2026-08-20).
Data as JSON: /api/errors/957bd538f0e96738.
Report an issue: GitHub.