Skip to content

Commit b6cd45a

Browse files
feat: Add Blob as a default endowment (#4063)
Add `Blob` as a default endowment for `instanceof` checks when using `fetch`. https://consensyssoftware.atlassian.net/browse/WPC-1126 <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Small expansion of the default sandbox API using the same hardened global pattern as other Web platform types; no auth or network policy changes. > > **Overview** > Snaps now receive a hardened global **`Blob`** by default, alongside existing fetch-related globals like **`Response`**. > > **`Blob`** is registered in the common endowment factory, included in **`DEFAULT_ENDOWMENTS`**, and covered by hardening and executor tests (including **`fetch` → `res.blob()` → `instanceof Blob`**). Simulation endowment snapshots are updated to list **`Blob`**. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 00347d2. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 8898f31 commit b6cd45a

5 files changed

Lines changed: 74 additions & 0 deletions

File tree

packages/snaps-execution-environments/src/common/BaseSnapExecutor.test.browser.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,6 +1113,69 @@ describe('BaseSnapExecutor', () => {
11131113
});
11141114
});
11151115

1116+
it('exposes a Blob global works with instanceof', async () => {
1117+
const CODE = `
1118+
module.exports.onRpcRequest = () => fetch('https://metamask.io').then(res => res.blob()).then(blob => blob instanceof Blob);
1119+
`;
1120+
1121+
const fetchSpy = spy(globalThis, 'fetch');
1122+
1123+
fetchSpy.mockImplementation(async () => {
1124+
return new Response('foo');
1125+
});
1126+
1127+
const executor = new TestSnapExecutor();
1128+
await executor.executeSnap(1, MOCK_SNAP_ID, CODE, ['fetch', 'Blob']);
1129+
1130+
expect(await executor.readCommand()).toStrictEqual({
1131+
jsonrpc: '2.0',
1132+
id: 1,
1133+
result: 'OK',
1134+
});
1135+
1136+
await executor.writeCommand({
1137+
jsonrpc: '2.0',
1138+
id: 2,
1139+
method: 'snapRpc',
1140+
params: {
1141+
snapId: MOCK_SNAP_ID,
1142+
handler: HandlerType.OnRpcRequest,
1143+
origin: MOCK_ORIGIN,
1144+
request: { jsonrpc: '2.0', method: '' },
1145+
},
1146+
});
1147+
1148+
expect(await executor.readCommand()).toStrictEqual({
1149+
jsonrpc: '2.0',
1150+
method: 'OutboundRequest',
1151+
params: { source: 'fetch' },
1152+
});
1153+
1154+
expect(await executor.readCommand()).toStrictEqual({
1155+
jsonrpc: '2.0',
1156+
method: 'OutboundResponse',
1157+
params: { source: 'fetch' },
1158+
});
1159+
1160+
expect(await executor.readCommand()).toStrictEqual({
1161+
jsonrpc: '2.0',
1162+
method: 'OutboundRequest',
1163+
params: { source: 'fetch' },
1164+
});
1165+
1166+
expect(await executor.readCommand()).toStrictEqual({
1167+
jsonrpc: '2.0',
1168+
method: 'OutboundResponse',
1169+
params: { source: 'fetch' },
1170+
});
1171+
1172+
expect(await executor.readCommand()).toStrictEqual({
1173+
id: 2,
1174+
jsonrpc: '2.0',
1175+
result: true,
1176+
});
1177+
});
1178+
11161179
it('notifies execution service of out of band errors via unhandledrejection', async () => {
11171180
const CODE = `
11181181
module.exports.onRpcRequest = async () => 'foo';

packages/snaps-execution-environments/src/common/endowments/commonEndowmentFactory.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ const commonEndowments: CommonEndowmentSpecification[] = [
8282
{ endowment: BigInt, name: 'BigInt' },
8383
{ endowment: BigInt64Array, name: 'BigInt64Array' },
8484
{ endowment: BigUint64Array, name: 'BigUint64Array' },
85+
{ endowment: Blob, name: 'Blob' },
8586
{ endowment: btoa, name: 'btoa', bind: true },
8687
{ endowment: DataView, name: 'DataView' },
8788
{ endowment: Float32Array, name: 'Float32Array' },

packages/snaps-execution-environments/src/common/endowments/endowments.test.browser.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,10 @@ describe('endowments', () => {
166166
endowments: { DateAttenuated },
167167
factory: () => new DateAttenuated(),
168168
},
169+
Blob: {
170+
endowments: { Blob },
171+
factory: () => new Blob([new ArrayBuffer(64)]),
172+
},
169173

170174
// Objects.
171175
consoleAttenuated: {
@@ -348,6 +352,10 @@ describe('endowments', () => {
348352
factory: expect.any(Function),
349353
names: ['BigUint64Array'],
350354
},
355+
{
356+
factory: expect.any(Function),
357+
names: ['Blob'],
358+
},
351359
{
352360
factory: expect.any(Function),
353361
names: ['btoa'],

packages/snaps-simulation/src/methods/specifications.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,7 @@ describe('getEndowments', () => {
427427
"ArrayBuffer",
428428
"AbortController",
429429
"AbortSignal",
430+
"Blob",
430431
"fetch",
431432
"Request",
432433
"Headers",

packages/snaps-utils/src/default-endowments.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,5 @@ export const DEFAULT_ENDOWMENTS: readonly string[] = Object.freeze([
3838
// https://github.com/MetaMask/snaps-monorepo/discussions/678
3939
'AbortController',
4040
'AbortSignal',
41+
'Blob',
4142
]);

0 commit comments

Comments
 (0)