fix(server): SSRF guard on cross-instance invite preview fetch

This commit is contained in:
Jannis Braun
2026-04-29 22:19:10 +02:00
parent 4476c55963
commit 5481eb9e7e
2 changed files with 26 additions and 0 deletions
@@ -1,6 +1,13 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { fetchSpaceInviteSnapshot } from './spaceInviteSnapshot';
// Mock the ssrf module so tests don't need real DNS resolution.
// Default: validateExternalUrl resolves (allow). Individual tests override as needed.
vi.mock('./ssrf.js', () => ({
validateExternalUrl: vi.fn().mockResolvedValue(undefined),
isPrivateIp: vi.fn().mockReturnValue(false),
}));
describe('fetchSpaceInviteSnapshot', () => {
const originalFetch = global.fetch;
beforeEach(() => { global.fetch = vi.fn() as any; });
@@ -53,4 +60,17 @@ describe('fetchSpaceInviteSnapshot', () => {
const snap = await fetchSpaceInviteSnapshot('https://z.example', 'abc', 50);
expect(snap).toBeNull();
});
it('returns null when SSRF validator rejects the origin', async () => {
// Override the module-level mock to reject for this test only.
const ssrf = await import('./ssrf.js');
const spy = vi.spyOn(ssrf, 'validateExternalUrl').mockRejectedValueOnce(new Error('blocked'));
const fetchSpy = global.fetch as any;
const snap = await fetchSpaceInviteSnapshot('http://127.0.0.1:9200', 'abc');
expect(snap).toBeNull();
expect(fetchSpy).not.toHaveBeenCalled(); // CRITICAL — the fetch must NOT happen
spy.mockRestore();
});
});