RocketChat/Rocket.Chat · critical · Error
Failed to persist keys as they are not strings.
Error message
Failed to persist keys as they are not strings.
What it means
E2EE key persistence (persistKeys) throws this error when the KeyPair passed in has a public_key or private_key that is not a string (typeof check fails) — i.e. the key material is undefined, an object, or otherwise malformed before it is ever encrypted and POSTed to /v1/e2e.setUserPublicAndPrivateKeys. It is an early integrity guard for the E2EE setup flow.
Source
Thrown at apps/meteor/client/lib/e2ee/rocketchat.e2e.ts:285
) {
// KeyID was changed, update instance with new keyID and put room in waiting keys status
this.instancesByRoomId[rid].onRoomKeyReset(room.e2eKeyId);
}
return this.instancesByRoomId[rid] ?? null;
}
removeInstanceByRoomId(rid: IRoom['_id']): void {
delete this.instancesByRoomId[rid];
}
private async persistKeys(
{ public_key, private_key }: KeyPair,
password: string,
{ force }: { force: boolean } = { force: false },
): Promise<void> {
if (typeof public_key !== 'string' || typeof private_key !== 'string') {
throw new Error('Failed to persist keys as they are not strings.');
}
const encodedPrivateKey = await this.keychain.encryptKey(private_key, password);
if (!encodedPrivateKey) {
throw new Error('Failed to encode private key with provided password.');
}
await sdk.rest.post('/v1/e2e.setUserPublicAndPrivateKeys', {
public_key,
private_key: JSON.stringify(encodedPrivateKey),
force,
});
}
async acceptSuggestedKey(rid: string): Promise<void> {
await sdk.rest.post('/v1/e2e.acceptSuggestedGroupKey', {
rid,View on GitHub (pinned to b2c16d5842)
Solutions
- Log/inspect the KeyPair right before persistKeys to see which field is not a string.
- Regenerate the key pair (the E2EE setup flow) instead of persisting the malformed one.
- If it originated from stored key recovery, reset E2EE state and re-create keys with force: true.
- Ensure the page runs in a secure context so key export succeeds.
Example fix
// before
await e2e.persistKeys(keyPair, password, { force });
// after
if (typeof keyPair.public_key !== 'string' || typeof keyPair.private_key !== 'string') {
keyPair = await e2e.generateKeys(); // regenerate the corrupted pair
}
await e2e.persistKeys(keyPair, password, { force }); Defensive patterns
Strategy: type-guard
Validate before calling
if (!isStringKeyPair(keyPair)) {
keyPair = await e2e.generateKeys(); // do not persist malformed material
}
await e2e.persistKeys(keyPair, password, { force }); Type guard
const isStringKeyPair = (kp: KeyPair): kp is KeyPair & { public_key: string; private_key: string } =>
typeof kp.public_key === 'string' &&
typeof kp.private_key === 'string' &&
kp.public_key.length > 0 &&
kp.private_key.length > 0; Try / catch
try {
await e2e.persistKeys(keyPair, password);
} catch (e) {
if (e instanceof Error && e.message.startsWith('Failed to persist keys')) {
// regenerate and retry once with force
await e2e.persistKeys(await e2e.generateKeys(), password, { force: true });
return;
}
throw e;
} Prevention
- Never construct KeyPair manually — always use the E2EE module's generation/decryption paths.
- Run in a secure context (https/localhost) so WebCrypto exports return strings.
- Treat non-string key material as corruption: regenerate rather than coerce.
When it happens
Trigger: Passing a KeyPair whose keys came back malformed from generation or decryption (a failed WebCrypto export returning undefined, a decode step that produced an object instead of a string), or custom code constructing KeyPair from raw crypto primitives without serializing them.
Common situations: Corrupted locally-stored E2EE key material being re-persisted; password reset / key recovery flows passing the wrong shape; refactors of the KeyPair type; browser WebCrypto unavailability (non-secure context) making key export return nothing.
Related errors
- Failed to encode private key with provided password.
- error-e2e-enabled
- error-keys-already-set
- error-invalid-keys
- error-invalid-user
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/648cfff8b6951be5.
Report an issue: GitHub.