Skip to content

Commit 7752320

Browse files
committed
chore: parse app status link
No actual exploit here as far as I can tell, but doing a string check without parsing was flagged by a scanner.
1 parent 7849794 commit 7752320

File tree

3 files changed

+139
-46
lines changed

3 files changed

+139
-46
lines changed

site/src/pages/TaskPage/TaskSidebar.tsx

Lines changed: 2 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import GitHub from "@mui/icons-material/GitHub";
21
import { Button } from "components/Button/Button";
32
import {
43
DropdownMenu,
@@ -14,21 +13,15 @@ import {
1413
TooltipProvider,
1514
TooltipTrigger,
1615
} from "components/Tooltip/Tooltip";
17-
import {
18-
ArrowLeftIcon,
19-
BugIcon,
20-
EllipsisVerticalIcon,
21-
ExternalLinkIcon,
22-
GitPullRequestArrowIcon,
23-
} from "lucide-react";
16+
import { ArrowLeftIcon, EllipsisVerticalIcon } from "lucide-react";
2417
import { AppStatusStateIcon } from "modules/apps/AppStatusStateIcon";
2518
import type { Task } from "modules/tasks/tasks";
2619
import type { FC } from "react";
2720
import { Link as RouterLink } from "react-router-dom";
2821
import { cn } from "utils/cn";
2922
import { timeFrom } from "utils/time";
30-
import { truncateURI } from "utils/uri";
3123
import { TaskAppIFrame } from "./TaskAppIframe";
24+
import { TaskStatusLink } from "./TaskStatusLink";
3225
import { AI_APP_CHAT_SLUG, AI_APP_CHAT_URL_PATHNAME } from "./constants";
3326

3427
type TaskSidebarProps = {
@@ -201,40 +194,3 @@ const TaskStatuses: FC<TaskStatusesProps> = ({ task }) => {
201194
<Spinner loading />
202195
);
203196
};
204-
205-
type TaskStatusLinkProps = {
206-
uri: string;
207-
};
208-
209-
const TaskStatusLink: FC<TaskStatusLinkProps> = ({ uri }) => {
210-
let icon = <ExternalLinkIcon />;
211-
let label = truncateURI(uri);
212-
213-
if (uri.startsWith("https://github.com")) {
214-
const issueNumber = uri.split("/").pop();
215-
const [org, repo] = uri.split("/").slice(3, 5);
216-
const prefix = `${org}/${repo}`;
217-
218-
if (uri.includes("pull/")) {
219-
icon = <GitPullRequestArrowIcon />;
220-
label = issueNumber
221-
? `${prefix}#${issueNumber}`
222-
: `${prefix} Pull Request`;
223-
} else if (uri.includes("issues/")) {
224-
icon = <BugIcon />;
225-
label = issueNumber ? `${prefix}#${issueNumber}` : `${prefix} Issue`;
226-
} else {
227-
icon = <GitHub />;
228-
label = `${org}/${repo}`;
229-
}
230-
}
231-
232-
return (
233-
<Button asChild variant="outline" size="sm" className="min-w-0">
234-
<a href={uri} target="_blank" rel="noreferrer">
235-
{icon}
236-
{label}
237-
</a>
238-
</Button>
239-
);
240-
};
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { TaskStatusLink } from "./TaskStatusLink";
3+
4+
const meta: Meta<typeof TaskStatusLink> = {
5+
title: "pages/TaskPage/TaskStatusLink",
6+
component: TaskStatusLink,
7+
// Add a wrapper to test truncation.
8+
decorators: [
9+
(Story) => (
10+
<div style={{ display: "flex", width: "200px" }}>
11+
<Story />
12+
</div>
13+
),
14+
],
15+
};
16+
17+
export default meta;
18+
type Story = StoryObj<typeof TaskStatusLink>;
19+
20+
export const GithubPRNumber: Story = {
21+
args: {
22+
uri: "https://github.com/org/repo/pull/1234",
23+
},
24+
};
25+
26+
export const GitHubPRNoNumber: Story = {
27+
args: {
28+
uri: "https://github.com/org/repo/pull",
29+
},
30+
};
31+
32+
export const GithubIssueNumber: Story = {
33+
args: {
34+
uri: "https://github.com/org/repo/issues/4321",
35+
},
36+
};
37+
38+
export const GithubIssueNoNumber: Story = {
39+
args: {
40+
uri: "https://github.com/org/repo/issues",
41+
},
42+
};
43+
44+
export const GithubOrgRepo: Story = {
45+
args: {
46+
uri: "https://github.com/org/repo",
47+
},
48+
};
49+
50+
export const GithubOrg: Story = {
51+
args: {
52+
uri: "https://github.com/org",
53+
},
54+
};
55+
56+
export const Github: Story = {
57+
args: {
58+
uri: "https://github.com",
59+
},
60+
};
61+
62+
export const File: Story = {
63+
args: {
64+
uri: "file:///path/to/file",
65+
},
66+
};
67+
68+
export const Long: Story = {
69+
args: {
70+
uri: "https://dev.coder.com/this-is-a/long-url/to-test/how-the-truncation/looks",
71+
},
72+
};
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import GitHub from "@mui/icons-material/GitHub";
2+
import { Button } from "components/Button/Button";
3+
import {
4+
BugIcon,
5+
ExternalLinkIcon,
6+
GitPullRequestArrowIcon,
7+
} from "lucide-react";
8+
import type { FC } from "react";
9+
10+
type TaskStatusLinkProps = {
11+
uri: string;
12+
};
13+
14+
export const TaskStatusLink: FC<TaskStatusLinkProps> = ({ uri }) => {
15+
let icon = <ExternalLinkIcon />;
16+
let label = uri;
17+
18+
try {
19+
const parsed = new URL(uri);
20+
switch (parsed.protocol) {
21+
// For file URIs, strip off the `file://`.
22+
case "file:":
23+
label = uri.replace(/^file:\/\//, "");
24+
break;
25+
case "http:":
26+
case "https:":
27+
// For GitHub URIs, use a short representation.
28+
if (parsed.host === "github.com") {
29+
const [_, org, repo, type, number] = parsed.pathname.split("/");
30+
switch (type) {
31+
case "pull":
32+
icon = <GitPullRequestArrowIcon />;
33+
label = number
34+
? `${org}/${repo}#${number}`
35+
: `${org}/${repo} pull request`;
36+
break;
37+
case "issues":
38+
icon = <BugIcon />;
39+
label = number
40+
? `${org}/${repo}#${number}`
41+
: `${org}/${repo} issue`;
42+
break;
43+
default:
44+
icon = <GitHub />;
45+
if (org && repo) {
46+
label = `${org}/${repo}`;
47+
}
48+
break;
49+
}
50+
}
51+
break;
52+
}
53+
} catch (error) {
54+
// Invalid URL, probably.
55+
}
56+
57+
return (
58+
<Button asChild variant="outline" size="sm" className="min-w-0">
59+
<a href={uri} target="_blank" rel="noreferrer">
60+
{icon}
61+
<span className="truncate">{label}</span>
62+
</a>
63+
</Button>
64+
);
65+
};

0 commit comments

Comments
 (0)