mongodb/node-mongodb-native · error · MongoRuntimeError

Server returned an invalid signature

Error message

Server returned an invalid signature

What it means

Thrown by continueScramConversation (scram.ts:189) when the server's final SASL signature (parsedResponse.v) does not match the signature the client computed from its salted password. SCRAM uses this server signature to authenticate the server to the client; a mismatch means the party you are talking to does not hold the Server Key derived from your password. Raised as MongoRuntimeError.

Solutions

  1. Enable TLS/SSL on the connection (tls=true) to prevent tampering
  2. Verify the hostname and server certificate against the deployment you expect
  3. Remove proxies/load balancers that may alter the SASL payload
  4. Confirm the cluster endpoint is the legitimate MongoDB deployment

Example fix

// before
const uri = 'mongodb://user:pass@cluster/?tls=false';

// after
const uri = 'mongodb://user:pass@cluster/?tls=true&tlsCAFile=/etc/ssl/mongo-ca.pem';
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await client.connect();
} catch (err) {
  if (err instanceof MongoRuntimeError && /invalid signature/.test(err.message)) {
    // potential MITM or spoofed server; do not fall back to insecure transport
    alertSecurityTeam(err);
  }
  throw err;
}

Prevention

When it happens

Trigger: The client computed Server Key via HMAC and PBKDF2, then compared the server's returned 'v' value with a constant-time compareDigest; the two differed. This happens when the server is not the legitimate password-holding server, the exchange was tampered with, or (less commonly) the wire payload was corrupted.

Common situations: A man-in-the-middle intercepting the SCRAM exchange without knowing the password. A buggy proxy/gateway corrupting the final SASL payload. Connecting to a spoofed/misconfigured server. TLS disabled on an untrusted network allowing active tampering.

Related errors


AI-assisted analysis of mongodb/node-mongodb-native@dce7939f86 (2026-08-11). Data as JSON: /api/errors/1cfea67daf0769d6. Report an issue: GitHub.

Appendix: source

Thrown at src/cmap/auth/scram.ts:189

  const payloadString = ByteUtils.toUTF8(payload.buffer, 0, payload.position, false);
  const authMessage = [firstMessage, payloadString, withoutProof].join(',');

  const clientSignature = await HMAC(cryptoMethod, storedKey, authMessage);
  const clientProof = `p=${xor(clientKey, clientSignature)}`;
  const clientFinal = [withoutProof, clientProof].join(',');

  const serverSignature = await HMAC(cryptoMethod, serverKey, authMessage);
  const saslContinueCmd = {
    saslContinue: 1,
    conversationId: response.conversationId,
    payload: new Binary(ByteUtils.fromUTF8(clientFinal))
  };

  const r = await connection.command(ns(`${db}.$cmd`), saslContinueCmd, undefined);
  const parsedResponse = parsePayload(r.payload);

  if (!compareDigest(ByteUtils.fromBase64(parsedResponse.v), serverSignature)) {
    throw new MongoRuntimeError('Server returned an invalid signature');
  }

  if (r.done !== false) {
    // If the server sends r.done === true we can save one RTT
    return;
  }

  const retrySaslContinueCmd = {
    saslContinue: 1,
    conversationId: r.conversationId,
    payload: ByteUtils.allocate(0)
  };

  await connection.command(ns(`${db}.$cmd`), retrySaslContinueCmd, undefined);
}

function parsePayload(payload: Binary) {
  const payloadStr = ByteUtils.toUTF8(payload.buffer, 0, payload.position, false);

View on GitHub (pinned to dce7939f86)