Skip to content
Merged
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
18 changes: 18 additions & 0 deletions packages/core/src/api/gist.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,24 @@ export default async (
};
}

const safePattern = /^[-\w/.,]+$/;
if (id && !safePattern.test(id)) {
return {
status: "error - permanent",
content: renderError({
message: "Something went wrong",
secondaryMessage: "Gist ID contains unsafe characters",
renderOptions: {
title_color,
text_color,
bg_color,
border_color,
theme,
},
}),
};
}

try {
const gistData = await fetchGist(id, pat);

Expand Down
24 changes: 20 additions & 4 deletions packages/core/src/api/top-langs.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,27 @@ export default async (
};
}

const safePattern = /^[-\w/.,]+$/;
if (username && !safePattern.test(username)) {
return {
status: "error - permanent",
content: renderError({
message: "Something went wrong",
secondaryMessage: "Username contains unsafe characters",
renderOptions: {
title_color,
text_color,
bg_color,
border_color,
theme,
},
}),
};
}

if (
layout !== undefined &&
(typeof layout !== "string" ||
!["compact", "normal", "donut", "donut-vertical", "pie"].includes(layout))
!["compact", "normal", "donut", "donut-vertical", "pie"].includes(layout)
) {
return {
status: "error - permanent",
Expand All @@ -78,8 +95,7 @@ export default async (

if (
stats_format !== undefined &&
(typeof stats_format !== "string" ||
!["bytes", "percentages"].includes(stats_format))
!["bytes", "percentages"].includes(stats_format)
) {
return {
status: "error - permanent",
Expand Down
18 changes: 18 additions & 0 deletions packages/core/src/api/wakatime.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,24 @@ export default async ({
};
}

const safePattern = /^[-\w/.,]+$/;
if (username && !safePattern.test(username)) {
return {
status: "error - permanent",
content: renderError({
message: "Something went wrong",
secondaryMessage: "Username contains unsafe characters",
renderOptions: {
title_color,
text_color,
bg_color,
border_color,
theme,
},
}),
};
}

try {
const stats = await fetchWakatimeStats({ username, api_domain });

Expand Down
30 changes: 30 additions & 0 deletions packages/core/tests/apiInputValidation.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { describe, expect, it } from "vitest";

import gistApi from "../src/api/gist.js";
import statsApi from "../src/api/index.js";
import pinApi from "../src/api/pin.js";
import topLangsApi from "../src/api/top-langs.js";
import wakatimeApi from "../src/api/wakatime.js";

// Values containing characters outside the safe set /^[-\w/.,]+$/. These must be
// rejected before any network request is made.
const unsafeValues = ["user name", "user@evil.com", "a<b", "a?b", "a:b", "a&b"];

describe("API input validation", () => {
describe.each([
["top-langs", "username", topLangsApi],
["wakatime", "username", wakatimeApi],
["gist", "id", gistApi],
["stats", "username", statsApi],
["stats", "repo", statsApi],
["stats", "owner", statsApi],
["pin", "username", pinApi],
["pin", "repo", pinApi],
])("%s: %s", (_endpoint, param, api) => {
it.each(unsafeValues)(`rejects unsafe ${param} %j`, async (value) => {
const result = await api({ [param]: value });
expect(result.status).toBe("error - permanent");
expect(result.content).toContain("unsafe characters");
});
});
});
Loading