mastra-ai/mastra · error

Google service account private key signing failed (${(err as

Error message

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.

What it means

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).

Source

Thrown at auth/google/src/rbac-provider.ts:220

    const header = { alg: 'RS256', typ: 'JWT', ...(account.privateKeyId ? { kid: account.privateKeyId } : {}) };
    const claim = {
      iss: account.clientEmail,
      scope: (account.scopes ?? DEFAULT_DIRECTORY_SCOPES).join(' '),
      aud: OAUTH_TOKEN_URL,
      exp: now + 3600,
      iat: now,
      ...(account.subject ? { sub: account.subject } : {}),
    };
    const unsigned = `${this.base64Url(JSON.stringify(header))}.${this.base64Url(JSON.stringify(claim))}`;
    const privateKey = this.normalizePrivateKey(account.privateKey);

    let signature: string;
    try {
      signature = createSign('RSA-SHA256').update(unsigned).sign(privateKey, 'base64url');
    } catch (err) {
      const hasBegin = privateKey.includes('-----BEGIN');
      const hasEnd = privateKey.includes('-----END');
      throw new Error(
        `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.`,
      );
    }

    const response = await fetch(OAUTH_TOKEN_URL, {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
        assertion: `${unsigned}.${signature}`,
      }),
      signal: AbortSignal.timeout(DEFAULT_FETCH_TIMEOUT_MS),
    });

    if (!response.ok) {
      throw new Error(`Google service account token request failed (${response.status}): ${await response.text()}`);

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Convert literal '\n' sequences to real newlines before use: privateKey.replace(/\\n/g, '\n')
  2. Remove any surrounding single/double quotes and trailing commas from the env value
  3. Store the key as a file and read it with fs.readFileSync(keyPath, 'utf8') instead of an env var
  4. Regenerate/download the service account key and verify it starts with '-----BEGIN PRIVATE KEY-----'

Example fix

// before
const privateKey = process.env.GOOGLE_PRIVATE_KEY; // contains literal \n
sign({ privateKey });
// after
const privateKey = (process.env.GOOGLE_PRIVATE_KEY ?? '').replace(/\\n/g, '\n');
sign({ privateKey });
Defensive patterns

Strategy: validation

Validate before calling

const raw = process.env.GOOGLE_PRIVATE_KEY ?? '';
const privateKey = raw.includes('\\n') ? raw.replace(/\\n/g, '\n') : raw;
if (!privateKey.startsWith('-----BEGIN PRIVATE KEY-----') || !privateKey.trimEnd().endsWith('-----END PRIVATE KEY-----')) {
  throw new Error('GOOGLE_PRIVATE_KEY is not valid PEM; check quoting and \\n escapes');
}

Type guard

function isValidPemKey(key: string): boolean {
  return typeof key === 'string'
    && key.startsWith('-----BEGIN')
    && key.includes('-----END')
    && !key.includes('\\n');
}

Try / catch

try {
  await provider.getToken();
} catch (err) {
  if (err instanceof Error && err.message.includes('private key signing failed')) {
    console.error('Service account key is malformed:', err.message);
  }
  throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/ec534876f978aab7. Report an issue: GitHub.