{"record":{"id":"9a3901fa884548fb","repo":"mastra-ai/mastra","slug":"invalid-encrypted-session-data-9a3901","errorCode":null,"errorMessage":"Invalid encrypted session data","messagePattern":"Invalid encrypted session data","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"auth/google/src/auth-provider.ts","lineNumber":130,"sourceCode":"}\n\nasync function encryptSession(data: unknown, password: string): Promise<string> {\n  const encoder = new TextEncoder();\n  const salt = crypto.getRandomValues(new Uint8Array(SALT_LENGTH));\n  const key = await deriveKey(password, salt, 'encrypt');\n  const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH));\n  const encrypted = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, encoder.encode(JSON.stringify(data)));\n  const combined = new Uint8Array(salt.length + iv.length + new Uint8Array(encrypted).length);\n  combined.set(salt);\n  combined.set(iv, salt.length);\n  combined.set(new Uint8Array(encrypted), salt.length + iv.length);\n  return btoa(String.fromCharCode(...combined));\n}\n\nasync function decryptSession(encrypted: string, password: string): Promise<unknown> {\n  const combined = Uint8Array.from(atob(encrypted), c => c.charCodeAt(0));\n  if (combined.length < SALT_LENGTH + IV_LENGTH + 1) {\n    throw new Error('Invalid encrypted session data');\n  }\n  const salt = combined.slice(0, SALT_LENGTH);\n  const iv = combined.slice(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);\n  const data = combined.slice(SALT_LENGTH + IV_LENGTH);\n  const key = await deriveKey(password, salt, 'decrypt');\n  const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, data);\n  return JSON.parse(new TextDecoder().decode(decrypted));\n}\n\nasync function hmacSign(data: string, secret: string): Promise<string> {\n  const encoder = new TextEncoder();\n  const cryptoKey = await crypto.subtle.importKey(\n    'raw',\n    encoder.encode(secret),\n    { name: 'HMAC', hash: 'SHA-256' },\n    false,\n    ['sign'],\n  );","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/auth/google/src/auth-provider.ts#L112-L148","documentation":"Google SSO session data is encrypted as base64(AES-GCM(payload)) with a leading salt (SALT_LENGTH bytes) and IV (IV_LENGTH bytes). decryptSession base64-decodes the blob and throws if the decoded buffer is shorter than salt+IV+1 bytes, because no valid ciphertext could exist — the input is truncated or not actually an encrypted session produced by this library.","triggerScenarios":"Calling decryptSession (or accessing the `sessionData` getter backed by it) with a string that isn't base64 of a salted+IV-prefixed AES-GCM blob — e.g. an empty string, a plaintext token, a JWT, or a value truncated by storage limits.","commonSituations":"Cookie truncated by the browser's ~4KB limit or by a CDN/proxy; storing plaintext session JSON instead of the encrypted form; decoding the base64 twice or passing a base64url variant with padding stripped; old sessions encrypted with a different format after a library upgrade.","solutions":["Delete/re-issue the session: redirect the user to re-authenticate so fresh session data is encrypted and stored.","Verify the stored value is the exact output of encryptSession (base64, salt+IV prefixed), not plaintext or a JWT.","Check cookie size limits; if the blob is near 4KB, store session server-side and keep only a small reference in the cookie.","Confirm the same cookiePassword/encryption format is used across versions and instances (mismatched formats produce garbage or short blobs)."],"exampleFix":"// before\nconst session = await decryptSession(cookies.get('session') ?? '', password);\n// after\nconst raw = cookies.get('session');\nif (!raw || raw.length < 44) { // ~min salt+IV+ciphertext base64\n  return redirectToLogin(); // no/invalid session, re-auth instead of throwing\n}\nconst session = await decryptSession(raw, password);","handlingStrategy":"validation","validationCode":"function looksLikeEncryptedSession(v: unknown): v is string {\n  if (typeof v !== 'string' || v.length === 0) return false;\n  try {\n    const bytes = Uint8Array.from(atob(v), c => c.charCodeAt(0));\n    return bytes.length >= 16 + 12 + 1; // SALT_LENGTH + IV_LENGTH + ciphertext\n  } catch {\n    return false;\n  }\n}\nif (!looksLikeEncryptedSession(cookieValue)) return reauthenticate();","typeGuard":"function isEncryptedSession(v: unknown): v is string {\n  return typeof v === 'string' && v.length >= 40 && /^[A-Za-z0-9+/]+=*$/.test(v);\n}","tryCatchPattern":null,"preventionTips":["Treat an undecryptable/short session as 'no session' and re-authenticate instead of throwing.","Watch cookie size limits (4KB) — store large sessions server-side.","Don't store plaintext tokens or JWTs where the encrypted session blob is expected.","Keep the cookiePassword and encryption format stable across versions/instances."],"tags":["auth","google","encryption","session","aes-gcm"],"backgroundTag":"invalid-encrypted-payload","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}