From 57388dfe1ec132c94dc90fca4cae76a196cd2b01 Mon Sep 17 00:00:00 2001 From: James Pepper Date: Wed, 27 May 2026 12:10:13 +0100 Subject: [PATCH 1/2] Potential fix for code scanning alert no. 3: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- src/lib/blockchain-api.ts | 12 ++++++++++++ src/lib/mempool.ts | 11 +++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/lib/blockchain-api.ts b/src/lib/blockchain-api.ts index f6296f51..d7e32107 100644 --- a/src/lib/blockchain-api.ts +++ b/src/lib/blockchain-api.ts @@ -14,6 +14,18 @@ function sleep(ms: number): Promise { } export async function fetchJson(url: string, options?: RequestInit, revalidate?: number): Promise { + let parsedUrl: URL; + try { + parsedUrl = new URL(url); + } catch { + throw new Error('Invalid provider URL.'); + } + + const allowedHosts = new Set(['blockstream.info', 'mempool.space', 'api.coingecko.com']); + if (parsedUrl.protocol !== 'https:' || !allowedHosts.has(parsedUrl.hostname)) { + throw new Error('Disallowed provider URL.'); + } + const headers: Record = { 'Accept': 'application/json', 'User-Agent': 'BitSleuth/1.0', diff --git a/src/lib/mempool.ts b/src/lib/mempool.ts index b38a209f..844d52b0 100644 --- a/src/lib/mempool.ts +++ b/src/lib/mempool.ts @@ -51,8 +51,15 @@ export async function getMempoolData(): Promise<{ data: MempoolData | null; erro export async function getBlockDetails(hash: string, startIndex: number = 0): Promise<{ data: BlockDetails | null; error: string | null; }> { try { - const blockUrl = `https://mempool.space/api/block/${hash}`; - const txsUrl = `https://mempool.space/api/block/${hash}/txs/${startIndex}`; + const normalizedHash = hash.trim(); + if (!/^[a-fA-F0-9]{64}$/.test(normalizedHash)) { + return { data: null, error: 'The block hash you entered is not valid.' }; + } + + const safeStartIndex = Number.isInteger(startIndex) && startIndex >= 0 ? startIndex : 0; + const encodedHash = encodeURIComponent(normalizedHash); + const blockUrl = `https://mempool.space/api/block/${encodedHash}`; + const txsUrl = `https://mempool.space/api/block/${encodedHash}/txs/${safeStartIndex}`; // Block data is immutable, so we can cache it for a long time. const [blockData, blockTxsData] = await Promise.all([ From b82090cc28437cf0cbc06925dbe5a3c3469ae7f0 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 27 May 2026 11:24:45 +0000 Subject: [PATCH 2/2] Fix incomplete SSRF allowlist to prevent breaking blockchain.info and api.alternative.me The Copilot Autofix allowlist only included three hosts, but fetchJson() is also called with blockchain.info (BTC ticker) and api.alternative.me (Fear & Greed Index). Without these, address pages, wallet dashboard, and market page would throw "Disallowed provider URL" errors. Also moved the Set to module scope to avoid re-creating it on every call. https://claude.ai/code/session_01SKn49yBtsK4JtGvpyfauqB --- src/lib/blockchain-api.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/lib/blockchain-api.ts b/src/lib/blockchain-api.ts index d7e32107..b668e7f7 100644 --- a/src/lib/blockchain-api.ts +++ b/src/lib/blockchain-api.ts @@ -9,6 +9,14 @@ const MEMPOOL_SPACE_API_BASE = 'https://mempool.space/api'; const ESPLORA_BASES = [BLOCKSTREAM_API_BASE, MEMPOOL_SPACE_API_BASE]; +const ALLOWED_HOSTS = new Set([ + 'blockstream.info', + 'mempool.space', + 'api.coingecko.com', + 'blockchain.info', + 'api.alternative.me', +]); + function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } @@ -21,8 +29,7 @@ export async function fetchJson(url: string, options?: RequestInit, revalidate?: throw new Error('Invalid provider URL.'); } - const allowedHosts = new Set(['blockstream.info', 'mempool.space', 'api.coingecko.com']); - if (parsedUrl.protocol !== 'https:' || !allowedHosts.has(parsedUrl.hostname)) { + if (parsedUrl.protocol !== 'https:' || !ALLOWED_HOSTS.has(parsedUrl.hostname)) { throw new Error('Disallowed provider URL.'); }