{"record":{"id":"ae367b69027f6cc1","repo":"brianc/node-postgres","slug":"sasl-attribute-pairs-text-must-be-a-string","errorCode":null,"errorMessage":"SASL: attribute pairs text must be a string","messagePattern":"SASL: attribute pairs text must be a string","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/pg/lib/crypto/sasl.js","lineNumber":177,"sourceCode":"\n/**\n * base64-char     = ALPHA / DIGIT / \"/\" / \"+\"\n *\n * base64-4        = 4base64-char\n *\n * base64-3        = 3base64-char \"=\"\n *\n * base64-2        = 2base64-char \"==\"\n *\n * base64          = *base64-4 [base64-3 / base64-2]\n */\nfunction isBase64(text) {\n  return /^(?:[a-zA-Z0-9+/]{4})*(?:[a-zA-Z0-9+/]{2}==|[a-zA-Z0-9+/]{3}=)?$/.test(text)\n}\n\nfunction parseAttributePairs(text) {\n  if (typeof text !== 'string') {\n    throw new TypeError('SASL: attribute pairs text must be a string')\n  }\n\n  return new Map(\n    text.split(',').map((attrValue) => {\n      if (!/^.=/.test(attrValue)) {\n        throw new Error('SASL: Invalid attribute pair entry')\n      }\n      const name = attrValue[0]\n      const value = attrValue.substring(2)\n      return [name, value]\n    })\n  )\n}\n\nfunction parseServerFirstMessage(data) {\n  const attrPairs = parseAttributePairs(data)\n\n  const nonce = attrPairs.get('r')","sourceCodeStart":159,"sourceCodeEnd":195,"githubUrl":"https://github.com/brianc/node-postgres/blob/ff9d775abd12f29dd6df03945253b54eabbb29f2/packages/pg/lib/crypto/sasl.js#L159-L195","documentation":"A TypeError thrown by parseAttributePairs() when the input text is not a string. This function splits a SASL message into a Map of attribute key-value pairs and is called from parseServerFirstMessage() and parseServerFinalMessage(). Both callers receive data that was already string-validated upstream (continueSession at lines 72-73, finalizeSession at lines 133-134), making this a redundant belt-and-suspenders guard.","triggerScenarios":"parseAttributePairs() is called from parseServerFirstMessage (line 193) or parseServerFinalMessage (line 223). Both call sites are preceded by a typeof check that would throw a different error first (errors [20] and the SCRAM-SERVER-FIRST-MESSAGE serverData check). So this guard only fires if parseAttributePairs is called directly with a non-string, bypassing the upstream checks.","commonSituations":"Unreachable through normal pg connection flow. Would require a custom fork or a direct call to the internal parseAttributePairs function with a non-string argument.","solutions":["Reinstall node-postgres — this is an internal defensive guard not reachable in production.","Audit custom SASL handling code that might call internal functions directly."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Unreachable in normal operation — wrap connect() as a general safety net\ntry { await client.connect() } catch (e) { /* log and surface connection error */ }","preventionTips":["Use stock node-postgres without modifying internal SASL functions.","Reinstall pg if you suspect a corrupted or modified installation."],"tags":["authentication","sasl","scram","typeerror","internal-invariant"],"backgroundTag":null,"analyzedSha":"ff9d775abd12f29dd6df03945253b54eabbb29f2","analyzedAt":"2026-08-11T15:33:59.644Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}