Skip to content

Commit 155982c

Browse files
AztecBotalexghr
andcommitted
fix: reject non-json RPC requests
Fix #16489 Co-authored-by: Alex Gherghisan <[email protected]>
1 parent 6689a2f commit 155982c

File tree

2 files changed

+35
-10
lines changed

2 files changed

+35
-10
lines changed

yarn-project/foundation/src/json-rpc/server/safe_json_rpc_server.test.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
makeHandler,
1010
} from './safe_json_rpc_server.js';
1111

12+
const jsonrpc = '2.0';
13+
1214
describe('SafeJsonRpcServer', () => {
1315
let testState: TestState;
1416
let testNotes: TestNote[];
@@ -19,7 +21,8 @@ describe('SafeJsonRpcServer', () => {
1921
testState = new TestState(testNotes);
2022
});
2123

22-
const send = (body: any) => request(server.getApp().callback()).post('/').send(body);
24+
const send = (body: any, contentType = 'application/json') =>
25+
request(server.getApp().callback()).post('/').send(body).set({ 'content-type': contentType });
2326
const sendBatch = (...body: any[]) => request(server.getApp().callback()).post('/').send(body);
2427

2528
const expectError = (response: request.Response, httpCode: number, message: string) => {
@@ -32,9 +35,22 @@ describe('SafeJsonRpcServer', () => {
3235
server = createSafeJsonRpcServer<TestStateApi>(testState, TestStateSchema);
3336
});
3437

38+
it.each([
39+
[JSON.stringify({ method: 'getNote', params: [1] }), ''],
40+
[JSON.stringify({ method: 'getNote', params: [1] }), 'text/plain'],
41+
[JSON.stringify({ method: 'getNote', params: [1] }), 'text/javascript'],
42+
[new URLSearchParams({ method: 'count' }).toString(), 'application/x-www-formurlencoded'],
43+
['foo', 'text/plain'],
44+
[Buffer.from([0x42]), 'application/octet-stream'],
45+
])('rejects non json request bodies', async (body, contentType) => {
46+
const response = await send(body, contentType);
47+
expect(response.text).toContain('Invalid request');
48+
expect(response.status).toBe(400);
49+
});
50+
3551
it('calls an RPC function with a primitive parameter', async () => {
3652
const response = await send({ method: 'getNote', params: [1] });
37-
expect(response.text).toEqual(JSON.stringify({ result: { data: 'b' } }));
53+
expect(response.text).toEqual(JSON.stringify({ jsonrpc, result: { data: 'b' } }));
3854
expect(response.status).toBe(200);
3955
});
4056

@@ -45,29 +61,29 @@ describe('SafeJsonRpcServer', () => {
4561

4662
it('calls an RPC function with a primitive return type', async () => {
4763
const response = await send({ method: 'count', params: [] });
48-
expect(response.text).toEqual(JSON.stringify({ result: 2 }));
64+
expect(response.text).toEqual(JSON.stringify({ jsonrpc, result: 2 }));
4965
expect(response.status).toBe(200);
5066
});
5167

5268
it('calls an RPC function with an array of classes', async () => {
5369
const response = await send({ method: 'addNotes', params: [[{ data: 'c' }, { data: 'd' }]] });
5470
expect(response.status).toBe(200);
55-
expect(response.text).toBe(JSON.stringify({ result: ['a', 'b', 'c', 'd'].map(data => ({ data })) }));
71+
expect(response.text).toBe(JSON.stringify({ jsonrpc, result: ['a', 'b', 'c', 'd'].map(data => ({ data })) }));
5672
expect(testState.notes).toEqual([new TestNote('a'), new TestNote('b'), new TestNote('c'), new TestNote('d')]);
5773
expect(testState.notes.every(note => note instanceof TestNote)).toBe(true);
5874
});
5975

6076
it('calls an RPC function with no inputs nor outputs', async () => {
6177
const response = await send({ method: 'clear', params: [] });
6278
expect(response.status).toBe(200);
63-
expect(response.text).toEqual(JSON.stringify({}));
79+
expect(response.text).toEqual(JSON.stringify({ jsonrpc }));
6480
expect(testState.notes).toEqual([]);
6581
});
6682

6783
it('calls an RPC function that returns a primitive object and a bigint', async () => {
6884
const response = await send({ method: 'getStatus', params: [] });
6985
expect(response.status).toBe(200);
70-
expect(response.text).toEqual(JSON.stringify({ result: { status: 'ok', count: '2' } }));
86+
expect(response.text).toEqual(JSON.stringify({ jsonrpc, result: { status: 'ok', count: '2' } }));
7187
});
7288

7389
it('calls an RPC function that throws an error', async () => {
@@ -170,11 +186,11 @@ describe('SafeJsonRpcServer', () => {
170186
it('routes to the correct namespace', async () => {
171187
const response = await send({ method: 'letters_getNote', params: [1] });
172188
expect(response.status).toBe(200);
173-
expect(response.text).toEqual(JSON.stringify({ result: { data: 'b' } }));
189+
expect(response.text).toEqual(JSON.stringify({ jsonrpc, result: { data: 'b' } }));
174190

175191
const response2 = await send({ method: 'numbers_getNote', params: [1] });
176192
expect(response2.status).toBe(200);
177-
expect(response2.text).toEqual(JSON.stringify({ result: { data: '2' } }));
193+
expect(response2.text).toEqual(JSON.stringify({ jsonrpc, result: { data: '2' } }));
178194
});
179195

180196
it('fails if namespace is not found', async () => {

yarn-project/foundation/src/json-rpc/server/safe_json_rpc_server.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,12 @@ export class SafeJsonRpcServer {
120120
app.use(middleware);
121121
}
122122
app.use(exceptionHandler);
123-
app.use(bodyParser({ jsonLimit: this.config.maxBodySizeBytes, enableTypes: ['json'], detectJSON: () => true }));
123+
app.use(
124+
bodyParser({
125+
jsonLimit: this.config.maxBodySizeBytes,
126+
enableTypes: ['json'],
127+
}),
128+
);
124129
app.use(cors());
125130
app.use(router.routes());
126131
app.use(router.allowedMethods());
@@ -191,7 +196,11 @@ export class SafeJsonRpcServer {
191196
return { jsonrpc: '2.0', error: { code: -32600, message: 'Invalid Request' }, id: null };
192197
}
193198

194-
const { params = [], jsonrpc, id, method } = request;
199+
const { params = [], jsonrpc = '2.0', id, method } = request;
200+
if (typeof method !== 'string' || !method) {
201+
return { jsonrpc: '2.0', id, error: { code: -32600, message: `Invalid request` } };
202+
}
203+
195204
// Fail if not a registered function in the proxy
196205
if (typeof method !== 'string' || method === 'constructor' || !this.proxy.hasMethod(method)) {
197206
return { jsonrpc, id, error: { code: -32601, message: `Method not found: ${method}` } };

0 commit comments

Comments
 (0)