diff --git a/app/src/lib/safe-uri.test.ts b/app/src/lib/safe-uri.test.ts index 549ec53..2c1a01a 100644 --- a/app/src/lib/safe-uri.test.ts +++ b/app/src/lib/safe-uri.test.ts @@ -56,6 +56,17 @@ describe('safeImageUri ( sink)', () => { expect(safeImageUri('https://cdn.example/icon.png')).toBe('https://cdn.example/icon.png'); }); + it('allows same-origin gateway paths (bridge-sdk metadata pipeline)', () => { + expect(safeImageUri('/ipfs/QmTest')).toBe('/ipfs/QmTest'); + expect(safeImageUri(' /ipfs/bafyabc/icon.png ')).toBe('/ipfs/bafyabc/icon.png'); + }); + + it('rejects protocol-relative and backslash path forms', () => { + // '//' would be a cross-origin URL; backslashes can smuggle a host. + expect(safeImageUri('//evil.example/icon.png')).toBeNull(); + expect(safeImageUri('/\\evil.example')).toBeNull(); + }); + it('maps ipfs:// URIs through the gateway', () => { expect(safeImageUri('ipfs://QmTest/icon.png')).toBe('https://ipfs.io/ipfs/QmTest/icon.png'); }); diff --git a/app/src/lib/safe-uri.ts b/app/src/lib/safe-uri.ts index cc46641..770ad55 100644 --- a/app/src/lib/safe-uri.ts +++ b/app/src/lib/safe-uri.ts @@ -64,5 +64,12 @@ export function safeImageUri(raw: string): string | null { // data:image/... payloads never touch the network; scripts inside an // context do not execute. Anything but a strict image data URI is rejected. if (/^data:image\/[a-z0-9.!#$&^+\-_.]+[;,]/i.test(trimmed)) return trimmed; + // Same-origin gateway paths ('/ipfs/…' from the bridge-sdk metadata + // pipeline) only ever hit our own proxy, so they carry no third-party + // beacon risk. Protocol-relative '//' and backslash forms are rejected: + // they can smuggle a foreign host past the same-origin check. + if (trimmed.startsWith('/') && !trimmed.startsWith('//') && !trimmed.includes('\\')) { + return trimmed; + } return safeExternalUri(trimmed); }