Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions src/app/crypto/engineCrypto/EngineCrypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
import { PerSessionBackupDownloader } from './perSessionBackupDownload';
import type { CryptoEventHandlerMap } from 'matrix-js-sdk/lib/crypto-api/CryptoEventHandlerMap';
import { createDebugLogger } from '$utils/debugLogger';
import { traceVerification, warnVerification } from '$utils/verificationTrace';
import { traceVerification, warnToDevice, warnVerification } from '$utils/verificationTrace';
import { EngineVerificationRequest } from '../verification/request';
import {
EnginePhase,
Expand Down Expand Up @@ -133,7 +133,7 @@

const entries = Object.entries(value as Record<string, unknown>)
.filter(([, item]) => item !== undefined)
.sort(([a], [b]) => (a < b ? -1 : 1));

Check warning on line 136 in src/app/crypto/engineCrypto/EngineCrypto.ts

View workflow job for this annotation

GitHub Actions / Lint

unicorn(no-array-sort)

src/app/crypto/engineCrypto/EngineCrypto.ts:136:6: Use `Array#toSorted()` instead of `Array#sort()`.
return `{${entries.map(([key, item]) => `${JSON.stringify(key)}:${canonicalJson(item)}`).join(',')}}`;
};

Expand Down Expand Up @@ -921,8 +921,15 @@
});
} else if (event.type === ProcessedToDeviceEventType.PlainText) {
received.push({ message, encryptionInfo: null });
} else {
// Dropped like js-sdk's backend does, but an unreadable one carries no type, so a
// verification request lost here is invisible everywhere else.
warnToDevice('Dropped a to-device event the engine could not read', {
sender: message.sender ?? 'unknown',
type: message.type ?? 'unknown',
processed: event.type,
});
}
// Undecryptable and invalid events are dropped, as js-sdk's own backend does.
}

return received;
Expand Down
33 changes: 32 additions & 1 deletion src/app/crypto/engineCrypto/incomingVerificationRequest.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { CryptoEvent, EventType, type MatrixClient } from '$types/matrix-sdk';
import { engineInvoke } from '../olmMachine/engineInvoke';
import { traceVerification, warnVerification } from '$utils/verificationTrace';
import { traceVerification, warnToDevice, warnVerification } from '$utils/verificationTrace';
import { EngineCrypto } from './EngineCrypto';

vi.mock('$utils/verificationTrace', () => ({
traceVerification: vi.fn<(message: string, data?: unknown) => void>(),
warnToDevice: vi.fn<(message: string, data?: unknown) => void>(),
warnVerification: vi.fn<(message: string, data?: unknown) => void>(),
}));

Expand Down Expand Up @@ -33,6 +34,36 @@ const requestState = {
isSelfVerification: true,
};

describe('unreadable to-device events', () => {
beforeEach(() => {
mockInvoke.mockReset();
vi.mocked(warnToDevice).mockClear();
});

it('reports one the engine could not decrypt instead of dropping it silently', async () => {
const { mx } = clientSpy();
mockInvoke.mockImplementation(async (_identity, method) => {
if (method === 'receiveSyncChanges') {
return [
{
type: 1,
rawEvent: JSON.stringify({ type: 'm.room.encrypted', sender: '@me:e.org' }),
},
];
}
return [];
});

const crypto = new EngineCrypto(mx, { userId: '@me:e.org', deviceId: 'D' });
await crypto.preprocessToDeviceMessages([REQUEST_EVENT as never]);

expect(warnToDevice).toHaveBeenCalledWith(
'Dropped a to-device event the engine could not read',
expect.objectContaining({ sender: '@me:e.org', type: 'm.room.encrypted' })
);
});
});

describe('sending a verification request', () => {
beforeEach(() => {
mockInvoke.mockReset();
Expand Down
44 changes: 44 additions & 0 deletions src/app/utils/consolePasteScamWarning.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { installConsolePasteScamWarning } from './consolePasteScamWarning';

const isMobileTauri = vi.hoisted(() => vi.fn<() => boolean>());

vi.mock('./platform', () => ({ isMobileTauri }));

describe('installConsolePasteScamWarning', () => {
beforeEach(() => {
vi.useFakeTimers();
vi.clearAllMocks();
window.innerWidth = 800;
window.outerWidth = 800;
window.innerHeight = 400;
// A soft keyboard shrinks the viewport by far more than the 160px threshold.
window.outerHeight = 900;
});

afterEach(() => {
vi.useRealTimers();
});

it('stays silent on a phone, where the keyboard looks like docked devtools', () => {
isMobileTauri.mockReturnValue(true);
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined);

installConsolePasteScamWarning();
vi.advanceTimersByTime(2000);

expect(warn).not.toHaveBeenCalled();
warn.mockRestore();
});

it('still warns on desktop when devtools look docked', () => {
isMobileTauri.mockReturnValue(false);
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined);

installConsolePasteScamWarning();
vi.advanceTimersByTime(2000);

expect(warn).toHaveBeenCalled();
warn.mockRestore();
});
});
6 changes: 6 additions & 0 deletions src/app/utils/consolePasteScamWarning.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { isMobileTauri } from './platform';

// This is probably not very accurate, but doesn't really matter I suppose
function isDockedDevtoolsLikely(): boolean {
const gapW = window.outerWidth - window.innerWidth;
Expand All @@ -7,6 +9,10 @@ function isDockedDevtoolsLikely(): boolean {
}

export function installConsolePasteScamWarning(): void {
// A phone has no docked devtools, and the soft keyboard moves the viewport far enough
// to look exactly like one opening.
if (isMobileTauri()) return;

const BANNER_STYLE =
'font-size:56px;font-weight:900;color:#ff0033;background:#1a0006;padding:16px 24px;border:6px solid #ff0033;line-height:1.1;';
const BODY_STYLE =
Expand Down
10 changes: 10 additions & 0 deletions src/app/utils/verificationTrace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ export const traceVerification = (message: string, data: TraceData = {}): void =
Sentry.logger.info(`[crypto:verification] ${message}`, attributes(data));
};

export const warnToDevice = (message: string, data: TraceData = {}): void => {
Sentry.addBreadcrumb({
category: 'crypto.to-device',
message,
level: 'warning',
data: attributes(data),
});
Sentry.logger.warn(`[crypto:to-device] ${message}`, attributes(data));
};

export const warnVerification = (message: string, data: TraceData = {}): void => {
Sentry.addBreadcrumb({
category: 'crypto.verification',
Expand Down
Loading