Skip to content

Commit

Permalink
Increase max UI size limit (#2342)
Browse files Browse the repository at this point in the history
Increase the max UI size limit to 10 MB. This lets Snaps use images of
larger size without running into this issue.

Also modifies the implementation slightly to improve performance,
instead of validating that the JSON blob is valid JSON, we strictly
calculate the size of the blob.

Fixes #2341
  • Loading branch information
FrederikBolding committed Apr 18, 2024
1 parent f65435b commit deae905
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { SnapId } from '@metamask/snaps-sdk';
import { form, input, panel, text } from '@metamask/snaps-sdk';
import { form, image, input, panel, text } from '@metamask/snaps-sdk';
import { MOCK_SNAP_ID } from '@metamask/snaps-utils/test-utils';

import {
Expand Down Expand Up @@ -117,15 +117,16 @@ describe('SnapInterfaceController', () => {
messenger: controllerMessenger,
});

const components = panel([text('[foo](https://foo.bar)'.repeat(100000))]);
const images = new Array(800_000).fill(image(`<svg />`));
const components = panel(images);

await expect(
rootMessenger.call(
'SnapInterfaceController:createInterface',
MOCK_SNAP_ID,
components,
),
).rejects.toThrow('A Snap UI may not be larger than 250 kB.');
).rejects.toThrow('A Snap UI may not be larger than 10 MB.');
});

it('throws if text content is too large', async () => {
Expand Down Expand Up @@ -353,7 +354,8 @@ describe('SnapInterfaceController', () => {
children: [input({ name: 'bar' })],
});

const newContent = panel([text('[foo](https://foo.bar)'.repeat(100000))]);
const images = new Array(800_000).fill(image(`<svg />`));
const newContent = panel(images);

const id = await rootMessenger.call(
'SnapInterfaceController:createInterface',
Expand All @@ -368,7 +370,7 @@ describe('SnapInterfaceController', () => {
id,
newContent,
),
).rejects.toThrow('A Snap UI may not be larger than 250 kB.');
).rejects.toThrow('A Snap UI may not be larger than 10 MB.');
});

it('throws if text content is too large', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@ import type {
} from '@metamask/phishing-controller';
import type { Component, InterfaceState, SnapId } from '@metamask/snaps-sdk';
import {
getJsonSizeUnsafe,
getTotalTextLength,
validateComponentLinks,
} from '@metamask/snaps-utils';
import { assert, getJsonSize } from '@metamask/utils';
import { assert } from '@metamask/utils';
import { nanoid } from 'nanoid';

import { constructState } from './utils';

const MAX_UI_CONTENT_SIZE = 250_000; // 250 kb
const MAX_UI_CONTENT_SIZE = 10_000_000; // 10 mb
const MAX_TEXT_LENGTH = 50_000; // 50 kb

const controllerName = 'SnapInterfaceController';
Expand Down Expand Up @@ -253,16 +254,16 @@ export class SnapInterfaceController extends BaseController<
* Utility function to validate the components of an interface.
* Throws if something is invalid.
*
* Right now this only checks links against the phighing list.
*
* @param content - The components to verify.
*/
async #validateContent(content: Component) {
const size = getJsonSize(content);
// We assume the validity of this JSON to be validated by the caller.
// E.g. in the RPC method implementation.
const size = getJsonSizeUnsafe(content);

assert(
size <= MAX_UI_CONTENT_SIZE,
`A Snap UI may not be larger than ${MAX_UI_CONTENT_SIZE / 1000} kB.`,
`A Snap UI may not be larger than ${MAX_UI_CONTENT_SIZE / 1000000} MB.`,
);

const textSize = getTotalTextLength(content);
Expand Down
4 changes: 2 additions & 2 deletions packages/snaps-utils/coverage.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"branches": 96.48,
"functions": 98.64,
"functions": 98.65,
"lines": 98.75,
"statements": 94.53
"statements": 94.54
}
9 changes: 8 additions & 1 deletion packages/snaps-utils/src/json.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { parseJson } from './json';
import { getJsonSizeUnsafe, parseJson } from './json';

describe('parseJson', () => {
it('strips __proto__ and constructor', () => {
Expand All @@ -7,3 +7,10 @@ describe('parseJson', () => {
expect(parseJson(input)).toStrictEqual({ test: {}, test2: {} });
});
});

describe('getJsonSizeUnsafe', () => {
it('calculates the size of the JSON input', () => {
const input = { foo: 'bar' };
expect(getJsonSizeUnsafe(input)).toBe(13);
});
});
13 changes: 13 additions & 0 deletions packages/snaps-utils/src/json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,16 @@ import { getSafeJson } from '@metamask/utils';
export function parseJson<Type extends Json = Json>(json: string) {
return getSafeJson<Type>(JSON.parse(json));
}

/**
* Get the size of a JSON blob without validating that is valid JSON.
*
* This may sometimes be preferred over `getJsonSize` for performance reasons.
*
* @param value - The JSON value to get the size of.
* @returns The size of the JSON value in bytes.
*/
export function getJsonSizeUnsafe(value: Json): number {
const json = JSON.stringify(value);
return new TextEncoder().encode(json).byteLength;
}

0 comments on commit deae905

Please sign in to comment.