diff --git a/package-lock.json b/package-lock.json index ceefd287..59f987aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "plotlink", - "version": "0.1.51", + "version": "0.1.52", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "plotlink", - "version": "0.1.51", + "version": "0.1.52", "workspaces": [ "packages/*" ], diff --git a/package.json b/package.json index ebd8a71e..4820407f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "plotlink", - "version": "0.1.51", + "version": "0.1.52", "private": true, "workspaces": [ "packages/*" diff --git a/src/app/api/cron/backfill/route.ts b/src/app/api/cron/backfill/route.ts index 2658ff77..7cacbfc6 100644 --- a/src/app/api/cron/backfill/route.ts +++ b/src/app/api/cron/backfill/route.ts @@ -13,6 +13,7 @@ import type { Database } from "../../../../../lib/supabase"; const IPFS_GATEWAY = "https://ipfs.filebase.io/ipfs/"; const IPFS_TIMEOUT_MS = 10_000; +const IPFS_MAX_BYTES = 1_000_000; /** * How many blocks to scan per cron run (~5 min on Base = ~150 blocks at 2s/block). @@ -36,7 +37,11 @@ async function fetchIPFSContent(cid: string): Promise { signal: AbortSignal.timeout(IPFS_TIMEOUT_MS), }); if (!res.ok) return null; - return await res.text(); + const cl = res.headers.get("content-length"); + if (cl && parseInt(cl) > IPFS_MAX_BYTES) return null; + const text = await res.text(); + if (new TextEncoder().encode(text).byteLength > IPFS_MAX_BYTES) return null; + return text; } catch { return null; } diff --git a/src/app/api/index/plot/route.ts b/src/app/api/index/plot/route.ts index a5fca822..5e5415cd 100644 --- a/src/app/api/index/plot/route.ts +++ b/src/app/api/index/plot/route.ts @@ -14,6 +14,7 @@ import type { Database } from "../../../../../lib/supabase"; const IPFS_GATEWAY = "https://ipfs.filebase.io/ipfs/"; const IPFS_TIMEOUT_MS = 10_000; +const IPFS_MAX_BYTES = 1_000_000; /** PlotChained event topic0 (keccak256 of the event signature) */ const PLOT_CHAINED_TOPIC = encodeEventTopics({ @@ -93,7 +94,10 @@ export async function POST(req: Request) { signal: AbortSignal.timeout(IPFS_TIMEOUT_MS), }); if (!ipfsRes.ok) throw new Error(`IPFS status ${ipfsRes.status}`); + const cl = ipfsRes.headers.get("content-length"); + if (cl && parseInt(cl) > IPFS_MAX_BYTES) throw new Error("IPFS content too large"); const ipfsContent = await ipfsRes.text(); + if (new TextEncoder().encode(ipfsContent).byteLength > IPFS_MAX_BYTES) throw new Error("IPFS content too large"); // Verify IPFS content hash matches on-chain hash if (hashContent(ipfsContent) === contentHash) { content = ipfsContent; diff --git a/src/app/api/index/storyline/route.ts b/src/app/api/index/storyline/route.ts index 9cccbee2..bd336bf4 100644 --- a/src/app/api/index/storyline/route.ts +++ b/src/app/api/index/storyline/route.ts @@ -17,6 +17,7 @@ import { awardWritePoints } from "../../../../../lib/airdrop/award"; const IPFS_GATEWAY = "https://ipfs.filebase.io/ipfs/"; const IPFS_TIMEOUT_MS = 10_000; +const IPFS_MAX_BYTES = 1_000_000; /** StorylineCreated event topic0 */ const STORYLINE_CREATED_TOPIC = encodeEventTopics({ @@ -121,7 +122,10 @@ export async function POST(req: Request) { signal: AbortSignal.timeout(IPFS_TIMEOUT_MS), }); if (!ipfsRes.ok) throw new Error(`IPFS status ${ipfsRes.status}`); + const cl = ipfsRes.headers.get("content-length"); + if (cl && parseInt(cl) > IPFS_MAX_BYTES) throw new Error("IPFS content too large"); const ipfsContent = await ipfsRes.text(); + if (new TextEncoder().encode(ipfsContent).byteLength > IPFS_MAX_BYTES) throw new Error("IPFS content too large"); // Verify IPFS content hash matches on-chain hash if (hashContent(ipfsContent) === openingHash) { genesisContent = ipfsContent;