Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/parser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ export class YouMdParserImpl implements YouMdParser {
const response = await fetch(url, {
headers,
signal: controller.signal,
redirect: "error",
});

if (!response.ok) {
Expand Down Expand Up @@ -414,6 +415,20 @@ export class YouMdParserImpl implements YouMdParser {
};
}

if (err instanceof Error && /redirect/i.test(err.message)) {
return {
profile: createEmptyProfile(),
success: false,
errors: [
{
code: "NETWORK_ERROR",
message: "Redirects are not allowed for remote profile loading",
},
],
warnings: [],
};
}

return {
profile: createEmptyProfile(),
success: false,
Expand Down
40 changes: 40 additions & 0 deletions test/parser/loadFromUrl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,46 @@ describe("loadFromUrl hardening", () => {
expect(result.errors.some((e) => e.code === "TIMEOUT")).toBe(true);
});

it("disables redirects when fetching remote profiles", async () => {
const parser = createParser();

const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue({
ok: true,
status: 200,
statusText: "OK",
headers: { get: () => null },
body: null,
text: vi.fn(async () => "---\nschema_version: \"1.1\"\n---\n# Me"),
} as unknown as Response);

await parser.loadFromUrl("https://example.com/profile.md");

expect(fetchSpy).toHaveBeenCalledWith(
"https://example.com/profile.md",
expect.objectContaining({ redirect: "error" })
);
});

it("returns NETWORK_ERROR when upstream responds with a redirect", async () => {
const parser = createParser();

vi.spyOn(globalThis, "fetch").mockRejectedValue(
new Error("redirect mode is set to error")
);

const result = await parser.loadFromUrl("https://example.com/profile.md");

expect(result.success).toBe(false);
expect(result.errors).toEqual(
expect.arrayContaining([
expect.objectContaining({
code: "NETWORK_ERROR",
message: "Redirects are not allowed for remote profile loading",
}),
])
);
});

it("rejects oversized streamed responses when content-length is missing", async () => {
const parser = createParser();
const encoder = new TextEncoder();
Expand Down
Loading