{"record":{"id":"ec534876f978aab7","repo":"mastra-ai/mastra","slug":"google-service-account-private-key-signing-failed","errorCode":null,"errorMessage":"Google service account private key signing failed (${(err as Error).message}). Key has BEGIN marker: ${hasBegin}, END marker: ${hasEnd}. Ensure your .env value contains the raw PEM with \\n for newlines, without extra surrounding quotes or commas.","messagePattern":"Google service account private key signing failed \\((.+?)\\)\\. Key has BEGIN marker: (.+?), END marker: (.+?)\\. Ensure your \\.env value contains the raw PEM with \\\\n for newlines, without extra surrounding quotes or commas\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"auth/google/src/rbac-provider.ts","lineNumber":220,"sourceCode":"    const header = { alg: 'RS256', typ: 'JWT', ...(account.privateKeyId ? { kid: account.privateKeyId } : {}) };\n    const claim = {\n      iss: account.clientEmail,\n      scope: (account.scopes ?? DEFAULT_DIRECTORY_SCOPES).join(' '),\n      aud: OAUTH_TOKEN_URL,\n      exp: now + 3600,\n      iat: now,\n      ...(account.subject ? { sub: account.subject } : {}),\n    };\n    const unsigned = `${this.base64Url(JSON.stringify(header))}.${this.base64Url(JSON.stringify(claim))}`;\n    const privateKey = this.normalizePrivateKey(account.privateKey);\n\n    let signature: string;\n    try {\n      signature = createSign('RSA-SHA256').update(unsigned).sign(privateKey, 'base64url');\n    } catch (err) {\n      const hasBegin = privateKey.includes('-----BEGIN');\n      const hasEnd = privateKey.includes('-----END');\n      throw new Error(\n        `Google service account private key signing failed (${(err as Error).message}). ` +\n          `Key has BEGIN marker: ${hasBegin}, END marker: ${hasEnd}. ` +\n          `Ensure your .env value contains the raw PEM with \\\\n for newlines, without extra surrounding quotes or commas.`,\n      );\n    }\n\n    const response = await fetch(OAUTH_TOKEN_URL, {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n      body: new URLSearchParams({\n        grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',\n        assertion: `${unsigned}.${signature}`,\n      }),\n      signal: AbortSignal.timeout(DEFAULT_FETCH_TIMEOUT_MS),\n    });\n\n    if (!response.ok) {\n      throw new Error(`Google service account token request failed (${response.status}): ${await response.text()}`);","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/auth/google/src/rbac-provider.ts#L202-L238","documentation":"This error is thrown when Node's crypto createSign('RSA-SHA256') fails to sign the JWT assertion with the Google service account private key. The library wraps the underlying crypto error and inspects the key for PEM BEGIN/END markers to diagnose the most common cause: a malformed key string. It almost always indicates the private key was mangled by environment-variable loading (unescaped newlines, extra quotes, or a JSON-embedded key with '\\n' literals not converted).","triggerScenarios":"Calling getToken() -> getServiceAccountToken() when the GOOGLE_SERVICE_ACCOUNT key (from options or env) is not valid PEM: newlines are literal '\\n' instead of real newlines, the value is wrapped in extra quotes, a trailing comma was copied from a JSON key file, or the key material itself is corrupt/truncated.","commonSituations":"Deploying to environments (Docker, serverless, CI) where .env values are not multiline-safe; pasting the private_key field straight from a downloaded service-account JSON into a single-line env var; dotenv versions that strip or mis-handle quoted multiline values.","solutions":["Convert literal '\\n' sequences to real newlines before use: privateKey.replace(/\\\\n/g, '\\n')","Remove any surrounding single/double quotes and trailing commas from the env value","Store the key as a file and read it with fs.readFileSync(keyPath, 'utf8') instead of an env var","Regenerate/download the service account key and verify it starts with '-----BEGIN PRIVATE KEY-----'"],"exampleFix":"// before\nconst privateKey = process.env.GOOGLE_PRIVATE_KEY; // contains literal \\n\nsign({ privateKey });\n// after\nconst privateKey = (process.env.GOOGLE_PRIVATE_KEY ?? '').replace(/\\\\n/g, '\\n');\nsign({ privateKey });","handlingStrategy":"validation","validationCode":"const raw = process.env.GOOGLE_PRIVATE_KEY ?? '';\nconst privateKey = raw.includes('\\\\n') ? raw.replace(/\\\\n/g, '\\n') : raw;\nif (!privateKey.startsWith('-----BEGIN PRIVATE KEY-----') || !privateKey.trimEnd().endsWith('-----END PRIVATE KEY-----')) {\n  throw new Error('GOOGLE_PRIVATE_KEY is not valid PEM; check quoting and \\\\n escapes');\n}","typeGuard":"function isValidPemKey(key: string): boolean {\n  return typeof key === 'string'\n    && key.startsWith('-----BEGIN')\n    && key.includes('-----END')\n    && !key.includes('\\\\n');\n}","tryCatchPattern":"try {\n  await provider.getToken();\n} catch (err) {\n  if (err instanceof Error && err.message.includes('private key signing failed')) {\n    console.error('Service account key is malformed:', err.message);\n  }\n  throw err;\n}","preventionTips":["Store the key in a file and read it via fs.readFileSync instead of env vars when possible","Use secret managers (GCP Secret Manager, AWS Secrets Manager) that preserve newlines","Normalize with .replace(/\\\\n/g, '\\n') at config-load time in one place","Never wrap the env value in extra quotes or copy the trailing comma from JSON"],"tags":["auth","google","crypto","configuration","env"],"backgroundTag":"private-key-parse-failed","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}