diff --git a/apps/fee-payer/src/index.ts b/apps/fee-payer/src/index.ts index d11db5fe..ed72b164 100644 --- a/apps/fee-payer/src/index.ts +++ b/apps/fee-payer/src/index.ts @@ -15,7 +15,6 @@ import { captureEvent, getRequestContext, } from './lib/posthog.js' -import { blockSignTransactionMiddleware } from './lib/block-sign-transaction.js' import { rateLimitMiddleware } from './lib/rate-limit.js' import { getUsage } from './lib/usage.js' @@ -88,7 +87,7 @@ app.get( }, ) -app.all('*', blockSignTransactionMiddleware, rateLimitMiddleware, async (c) => { +app.all('*', rateLimitMiddleware, async (c) => { const requestContext = getRequestContext(c.req.raw) const handler = Handler.feePayer({ diff --git a/apps/fee-payer/src/lib/block-sign-transaction.ts b/apps/fee-payer/src/lib/block-sign-transaction.ts deleted file mode 100644 index c580f913..00000000 --- a/apps/fee-payer/src/lib/block-sign-transaction.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { Context, Next } from 'hono' -import { cloneRawRequest } from 'hono/request' -import { RpcRequest } from 'ox' - -/** - * Middleware that blocks `eth_signTransaction` requests. - * - * `eth_signTransaction` uses the sponsor's key as the transaction sender, - * which allows an attacker to execute arbitrary transactions on behalf of the - * sponsor. Only `eth_signRawTransaction` and `eth_sendRawTransaction` are safe - * because they preserve the original sender's signature and only add a - * fee-payer co-signature. - */ -export async function blockSignTransactionMiddleware(c: Context, next: Next) { - try { - const clonedRequest = await cloneRawRequest(c.req) - const request = RpcRequest.from((await clonedRequest.json()) as never) - - if (request.method === 'eth_signTransaction') { - return c.json( - { - jsonrpc: '2.0', - id: request.id ?? null, - error: { code: -32601, message: 'Method not supported' }, - }, - 403, - ) - } - } catch { - // Let unparseable requests through - handler will return appropriate error - } - - await next() -} diff --git a/apps/fee-payer/src/lib/rate-limit.ts b/apps/fee-payer/src/lib/rate-limit.ts index bd920857..cc8769d6 100644 --- a/apps/fee-payer/src/lib/rate-limit.ts +++ b/apps/fee-payer/src/lib/rate-limit.ts @@ -22,15 +22,21 @@ export async function rateLimitMiddleware(c: Context, next: Next) { try { const clonedRequest = await cloneRawRequest(c.req) const request = RpcRequest.from((await clonedRequest.json()) as never) - const serialized = request.params?.[0] as `0x76${string}` + const serialized = request.params?.[0] - if (serialized?.startsWith('0x76')) { - const transaction = Transaction.deserialize(serialized) + if ( + typeof serialized === 'string' && + (serialized.startsWith('0x76') || serialized.startsWith('0x78')) + ) { + const transaction = Transaction.deserialize(serialized as `0x76${string}`) // biome-ignore lint/suspicious/noExplicitAny: _ const from = (transaction as any).from if (!from) { - return c.json({ error: 'Unable to determine sender for rate limiting' }, 400) + return c.json( + { error: 'Unable to determine sender for rate limiting' }, + 400, + ) } const { success } = await env.AddressRateLimiter.limit({ key: from }) diff --git a/apps/fee-payer/test/e2e.test.ts b/apps/fee-payer/test/e2e.test.ts index 069e8a7d..690e039f 100644 --- a/apps/fee-payer/test/e2e.test.ts +++ b/apps/fee-payer/test/e2e.test.ts @@ -137,13 +137,11 @@ describe('fee-payer integration', () => { }), }) - expect(response.status).toBe(403) + expect(response.status).toBe(200) const data = (await response.json()) as { - error?: { code: number; message: string } + error?: { code: number; name: string } } expect(data.error).toBeDefined() - expect(data.error?.code).toBe(-32601) - expect(data.error?.message).toBe('Method not supported') }) it('handles CORS preflight requests', async () => { diff --git a/apps/fee-payer/test/rate-limit.test.ts b/apps/fee-payer/test/rate-limit.test.ts index a2175af8..0a20455e 100644 --- a/apps/fee-payer/test/rate-limit.test.ts +++ b/apps/fee-payer/test/rate-limit.test.ts @@ -46,23 +46,4 @@ describe('rate-limit middleware', () => { expect(response.status).toBe(200) }) - it('blocks eth_signTransaction before rate limiting runs', async () => { - const response = await SELF.fetch('https://fee-payer.test/', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - jsonrpc: '2.0', - id: 1, - method: 'eth_signTransaction', - params: [{ to: '0x0000000000000000000000000000000000000000' }], - }), - }) - - expect(response.status).toBe(403) - const data = (await response.json()) as { - error?: { code: number; message: string } - } - expect(data.error?.code).toBe(-32601) - expect(data.error?.message).toBe('Method not supported') - }) }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 77a51948..83a95c11 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -193,8 +193,8 @@ catalogs: specifier: ^4.2.1 version: 4.2.1 tempo.ts: - specifier: ^0.14.0 - version: 0.14.0 + specifier: ^0.14.2 + version: 0.14.2 testcontainers: specifier: ^11.11.0 version: 11.12.0 @@ -316,7 +316,7 @@ importers: version: 7.7.4 tempo.ts: specifier: 'catalog:' - version: 0.14.0(@remix-run/headers@0.17.2)(@remix-run/route-pattern@0.15.3)(@remix-run/session@0.4.1)(typescript@5.9.3)(viem@2.47.1(typescript@5.9.3)(zod@4.3.6))(zod@4.3.6) + version: 0.14.2(typescript@5.9.3)(viem@2.47.1(typescript@5.9.3)(zod@4.3.6))(zod@4.3.6) viem: specifier: 'catalog:' version: 2.47.1(typescript@5.9.3)(zod@4.3.6) @@ -588,7 +588,7 @@ importers: version: 0.14.0(typescript@5.9.3)(zod@4.3.6) tempo.ts: specifier: 'catalog:' - version: 0.14.0(@remix-run/headers@0.17.2)(@remix-run/route-pattern@0.15.3)(@remix-run/session@0.4.1)(typescript@5.9.3)(viem@2.47.1(typescript@5.9.3)(zod@4.3.6))(zod@4.3.6) + version: 0.14.2(typescript@5.9.3)(viem@2.47.1(typescript@5.9.3)(zod@4.3.6))(zod@4.3.6) viem: specifier: 'catalog:' version: 2.47.1(typescript@5.9.3)(zod@4.3.6) @@ -637,7 +637,7 @@ importers: version: 19.2.4(react@19.2.4) tempo.ts: specifier: 'catalog:' - version: 0.14.0(@remix-run/headers@0.17.2)(@remix-run/route-pattern@0.15.3)(@remix-run/session@0.4.1)(typescript@5.9.3)(viem@2.47.1(typescript@5.9.3)(zod@4.3.6))(zod@4.3.6) + version: 0.14.2(typescript@5.9.3)(viem@2.47.1(typescript@5.9.3)(zod@4.3.6))(zod@4.3.6) viem: specifier: 'catalog:' version: 2.47.1(typescript@5.9.3)(zod@4.3.6) @@ -671,7 +671,7 @@ importers: dependencies: tempo.ts: specifier: 'catalog:' - version: 0.14.0(@remix-run/headers@0.17.2)(@remix-run/route-pattern@0.15.3)(@remix-run/session@0.4.1)(typescript@5.9.3)(viem@2.47.1(typescript@5.9.3)(zod@4.3.6))(zod@4.3.6) + version: 0.14.2(typescript@5.9.3)(viem@2.47.1(typescript@5.9.3)(zod@4.3.6))(zod@4.3.6) devDependencies: '@cloudflare/workers-types': specifier: 'catalog:' @@ -705,7 +705,7 @@ importers: version: 0.14.0(typescript@5.9.3)(zod@4.3.6) tempo.ts: specifier: 'catalog:' - version: 0.14.0(@remix-run/headers@0.17.2)(@remix-run/route-pattern@0.15.3)(@remix-run/session@0.4.1)(typescript@5.9.3)(viem@2.47.1(typescript@5.9.3)(zod@4.3.6))(zod@4.3.6) + version: 0.14.2(typescript@5.9.3)(viem@2.47.1(typescript@5.9.3)(zod@4.3.6))(zod@4.3.6) viem: specifier: 'catalog:' version: 2.47.1(typescript@5.9.3)(zod@4.3.6) @@ -736,7 +736,7 @@ importers: version: 4.0.1 tempo.ts: specifier: 'catalog:' - version: 0.14.0(@remix-run/headers@0.17.2)(@remix-run/route-pattern@0.15.3)(@remix-run/session@0.4.1)(typescript@5.9.3)(viem@2.47.1(typescript@5.9.3)(zod@4.3.6))(zod@4.3.6) + version: 0.14.2(typescript@5.9.3)(viem@2.47.1(typescript@5.9.3)(zod@4.3.6))(zod@4.3.6) viem: specifier: 'catalog:' version: 2.47.1(typescript@5.9.3)(zod@4.3.6) @@ -2124,18 +2124,11 @@ packages: engines: {node: '>=18'} hasBin: true - '@remix-run/fetch-router@0.12.0': - resolution: {integrity: sha512-BDG/VepZg2ZJ7wav3HDrB9ZJLpzZONHi9ItOkFMcKsrbm5g7jjrxW5Vdijbbebz12pbJQu6VKTwLVXp/LgFusA==} - peerDependencies: - '@remix-run/headers': ^0.17.2 - '@remix-run/route-pattern': ^0.15.3 - '@remix-run/session': ^0.4.0 - - '@remix-run/headers@0.17.2': - resolution: {integrity: sha512-IfHVCftsRKfk7kIQUxP9WDCe0OXj9X0lDRfFxk3CPcXJenBUEsYEPeBoW/YCZlKhdRWZjQlrofdk63lMSJmy8w==} + '@remix-run/fetch-router@0.17.0': + resolution: {integrity: sha512-3FeJGrTqrKKCvZdQWijbCXTEHKcdttkLFbI2ogfpZ+iDYSNZ9036wgDXuuoZqg6d+D0E8Unhk5ZwrLKDCd/hOw==} - '@remix-run/route-pattern@0.15.3': - resolution: {integrity: sha512-7s4Oy9q6Oz9Vfwg0iZscpmYVASNG9fLqbCa+YY0+SWKksDpvCRiW46xp3S3zEvT7zEP7G55FKA+JdrqqK2AOXw==} + '@remix-run/route-pattern@0.19.0': + resolution: {integrity: sha512-RXKaIJ2Lx01uyZc0iw+yLzowFCa1/NuB8jN7QTo4QUe2CaUGtvPGdhgrTUp75lyNNCSJIrM9SaAJ6c1pjZdmoA==} '@remix-run/session@0.4.1': resolution: {integrity: sha512-Bm6aKYgutb/raHZ3laloz8g/Qu7f3CeK3o4gUVDMxtEiAdWCzJamwHoTpGOc5+g1Kuy7z85v4M6nGrF06MFDSg==} @@ -5443,14 +5436,6 @@ packages: resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} engines: {node: '>=20'} - ox@0.11.3: - resolution: {integrity: sha512-1bWYGk/xZel3xro3l8WGg6eq4YEKlaqvyMtVhfMFpbJzK2F6rj4EDRtqDCWVEJMkzcmEi9uW2QxsqELokOlarw==} - peerDependencies: - typescript: '>=5.4.0' - peerDependenciesMeta: - typescript: - optional: true - ox@0.14.0: resolution: {integrity: sha512-WLOB7IKnmI3Ol6RAqY7CJdZKl8QaI44LN91OGF1061YIeN6bL5IsFcdp7+oQShRyamE/8fW/CBRWhJAOzI35Dw==} peerDependencies: @@ -6089,8 +6074,8 @@ packages: teex@1.0.1: resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} - tempo.ts@0.14.0: - resolution: {integrity: sha512-tyNg6pomYGqXpiRm0PDLwzOcifd//C9J+B+4rvbIHIwvwqxE1jres1YuaVSayo0JE0hzmXi/HZjJOsbSRdu+kg==} + tempo.ts@0.14.2: + resolution: {integrity: sha512-N4UkP2X/KDLmYUEIEWUDAk1m/USbKMzTjjUz1m0LwrIEVfoDlcSbBRc9jp14gLZcJVDlnq+fWHFVcH+GdrySgQ==} peerDependencies: viem: '>=2.43.3' peerDependenciesMeta: @@ -7846,15 +7831,12 @@ snapshots: - react-native-b4a - supports-color - '@remix-run/fetch-router@0.12.0(@remix-run/headers@0.17.2)(@remix-run/route-pattern@0.15.3)(@remix-run/session@0.4.1)': + '@remix-run/fetch-router@0.17.0': dependencies: - '@remix-run/headers': 0.17.2 - '@remix-run/route-pattern': 0.15.3 + '@remix-run/route-pattern': 0.19.0 '@remix-run/session': 0.4.1 - '@remix-run/headers@0.17.2': {} - - '@remix-run/route-pattern@0.15.3': {} + '@remix-run/route-pattern@0.19.0': {} '@remix-run/session@0.4.1': {} @@ -11491,21 +11473,6 @@ snapshots: powershell-utils: 0.1.0 wsl-utils: 0.3.1 - ox@0.11.3(typescript@5.9.3)(zod@4.3.6): - dependencies: - '@adraffy/ens-normalize': 1.11.1 - '@noble/ciphers': 1.3.0 - '@noble/curves': 1.9.1 - '@noble/hashes': 1.8.0 - '@scure/bip32': 1.7.0 - '@scure/bip39': 1.6.0 - abitype: 1.2.3(typescript@5.9.3)(zod@4.3.6) - eventemitter3: 5.0.1 - optionalDependencies: - typescript: 5.9.3 - transitivePeerDependencies: - - zod - ox@0.14.0(typescript@5.9.3)(zod@4.3.6): dependencies: '@adraffy/ens-normalize': 1.11.1 @@ -12292,16 +12259,13 @@ snapshots: - bare-abort-controller - react-native-b4a - tempo.ts@0.14.0(@remix-run/headers@0.17.2)(@remix-run/route-pattern@0.15.3)(@remix-run/session@0.4.1)(typescript@5.9.3)(viem@2.47.1(typescript@5.9.3)(zod@4.3.6))(zod@4.3.6): + tempo.ts@0.14.2(typescript@5.9.3)(viem@2.47.1(typescript@5.9.3)(zod@4.3.6))(zod@4.3.6): dependencies: - '@remix-run/fetch-router': 0.12.0(@remix-run/headers@0.17.2)(@remix-run/route-pattern@0.15.3)(@remix-run/session@0.4.1) - ox: 0.11.3(typescript@5.9.3)(zod@4.3.6) + '@remix-run/fetch-router': 0.17.0 + ox: 0.14.0(typescript@5.9.3)(zod@4.3.6) optionalDependencies: viem: 2.47.1(typescript@5.9.3)(zod@4.3.6) transitivePeerDependencies: - - '@remix-run/headers' - - '@remix-run/route-pattern' - - '@remix-run/session' - typescript - zod diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index af75522d..6eaf542c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -66,7 +66,7 @@ catalog: sonda: ^0.11.1 tailwind-merge: ^3.5.0 tailwindcss: ^4.2.1 - tempo.ts: ^0.14.0 + tempo.ts: ^0.14.2 testcontainers: ^11.11.0 tidx.ts: ^0.1.0 tw-animate-css: ^1.4.0