describe("resolveAndValidateIP", () => {
it("accepts a hostname resolving to a public IP", async () => { const ip = await resolveAndValidateIP("teams.sharepoint.com", publicResolve);
expect(ip).toBe("13.107.136.10");
});
it("rejects a hostname resolving to 10.x.x.x", async () => {
await expect(resolveAndValidateIP("evil.test", privateResolve("10.0.0.1"))).rejects.toThrow( "private/reserved IP",
);
});
it("rejects a hostname resolving to 169.254.169.254", async () => {
await expect(
resolveAndValidateIP("evil.test", privateResolve("169.254.169.254")),
).rejects.toThrow("private/reserved IP");
});
it("rejects a hostname resolving to loopback", async () => {
await expect(resolveAndValidateIP("evil.test", privateResolve("127.0.0.1"))).rejects.toThrow( "private/reserved IP",
);
});
it("rejects a hostname resolving to IPv6 loopback", async () => {
await expect(resolveAndValidateIP("evil.test", privateResolve("::1"))).rejects.toThrow( "private/reserved IP",
);
});
it("throws on DNS resolution failure", async () => {
await expect(resolveAndValidateIP("nonexistent.test", failingResolve)).rejects.toThrow( "DNS resolution failed",
);
});
});
describe("safeFetch", () => {
it("fetches a URL directly when no redirect occurs", async () => { const fetchMock = vi.fn(async (_url: string, _init?: RequestInit) => { returnnew Response("ok", { status: 200 });
});
await expectSafeFetchStatus({
fetchMock,
url: "https://teams.sharepoint.com/file.pdf",
allowHosts: ["sharepoint.com"],
expectedStatus: 200,
});
expect(fetchMock).toHaveBeenCalledOnce(); // Should have used redirect: "manual"
expect(fetchMock.mock.calls[0][1]).toHaveProperty("redirect", "manual");
});
it("follows a redirect to an allowlisted host with public IP", async () => { const fetchMock = mockFetchWithRedirect({ "https://teams.sharepoint.com/file.pdf": "https://cdn.sharepoint.com/storage/file.pdf",
});
await expectSafeFetchStatus({
fetchMock,
url: "https://teams.sharepoint.com/file.pdf",
allowHosts: ["sharepoint.com"],
expectedStatus: 200,
});
expect(fetchMock).toHaveBeenCalledTimes(2);
});
it("returns the redirect response when dispatcher is provided by an outer guard", async () => { const redirectedTo = "https://cdn.sharepoint.com/storage/file.pdf"; const fetchMock = mockFetchWithRedirect({ "https://teams.sharepoint.com/file.pdf": redirectedTo,
}); const res = await safeFetch({
url: "https://teams.sharepoint.com/file.pdf",
allowHosts: ["sharepoint.com"],
fetchFn: fetchMock as unknown as typeof fetch,
requestInit: { dispatcher: {} } as RequestInit,
resolveFn: publicResolve,
});
expect(res.status).toBe(302);
expect(res.headers.get("location")).toBe(redirectedTo);
expect(fetchMock).toHaveBeenCalledOnce();
});
it("still enforces allowlist checks before returning dispatcher-mode redirects", async () => { const fetchMock = mockFetchWithRedirect({ "https://teams.sharepoint.com/file.pdf": "https://evil.example.com/steal",
});
await expect(
safeFetch({
url: "https://teams.sharepoint.com/file.pdf",
allowHosts: ["sharepoint.com"],
fetchFn: fetchMock as unknown as typeof fetch,
requestInit: { dispatcher: {} } as RequestInit,
resolveFn: publicResolve,
}),
).rejects.toThrow("blocked by allowlist");
expect(fetchMock).toHaveBeenCalledOnce();
});
it("blocks a redirect to a non-allowlisted host", async () => { const fetchMock = mockFetchWithRedirect({ "https://teams.sharepoint.com/file.pdf": "https://evil.example.com/steal",
});
await expect(
safeFetch({
url: "https://teams.sharepoint.com/file.pdf",
allowHosts: ["sharepoint.com"],
fetchFn: fetchMock as unknown as typeof fetch,
resolveFn: publicResolve,
}),
).rejects.toThrow("blocked by allowlist"); // Should not have fetched the evil URL
expect(fetchMock).toHaveBeenCalledTimes(1);
});
it("blocks a redirect to an allowlisted host that resolves to a private IP (DNS rebinding)", async () => {
let callCount = 0; const rebindingResolve = async () => {
callCount++; // First call (initial URL) resolves to public IP if (callCount === 1) { return { address: "13.107.136.10" };
} // Second call (redirect target) resolves to private IP return { address: "169.254.169.254" };
};
it("encodeGraphShareId uses u! + base64url without padding", () => { // Graph docs example: encoding "https://onedrive.live.com/redir?resid=..." // should yield u!aHR0cHM6... (base64url, no '+', '/', or trailing '='). const url = "https://contoso.sharepoint.com/sites/a/Shared Documents/file.pdf"; const shareId = encodeGraphShareId(url);
expect(shareId.startsWith("u!")).toBe(true); const encoded = shareId.slice(2); // base64url alphabet is A-Z, a-z, 0-9, '-', '_' (no padding).
expect(encoded).toMatch(/^[A-Za-z0-9_-]+$/); // Round-trip check: decoding yields the original URL. const decoded = Buffer.from(encoded, "base64url").toString("utf8");
expect(decoded).toBe(url);
});
it("encodeGraphShareId swaps '+' and '/' for '-' and '_'", () => { // A URL whose standard base64 contains '+' and '/' chars. // Choose an input that base64 encodes with those characters. const url = "https://host.sharepoint.com/sites/path?x=???"; const shareId = encodeGraphShareId(url); const encoded = shareId.slice(2);
expect(encoded).not.toContain("+");
expect(encoded).not.toContain("/");
expect(encoded).not.toContain("=");
});
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.