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);
}