Skip to content

Commit f588ad3

Browse files
Merge pull request #634 from rogerSuperBuilderAlpha/develop
security: close 8 CodeQL alerts + postcss bump
2 parents a3c9195 + ba7a99f commit f588ad3

8 files changed

Lines changed: 93 additions & 142 deletions

app/summer-cohort/_components/WeekSubmissionsCollapsible.tsx

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -71,20 +71,19 @@ function buildExampleJson(
7171
photoUrl: string | null
7272
): string {
7373
const handle = githubHandle ?? "your-handle";
74-
const name = (displayName ?? "Your Name").replace(/"/g, '\\"');
75-
const photo = photoUrl ?? "https://example.com/your-photo.jpg";
76-
const liveLine = week.liveUrlRequired
77-
? `\n "liveUrl": "https://yourthing.example.com",`
78-
: "";
79-
return `{
80-
"githubHandle": "${handle}",
81-
"name": "${name}",
82-
"photoUrl": "${photo}",
83-
"repoUrl": "https://github.com/${handle}/your-week-${week.week}-build",${liveLine}
84-
"loomUrl": "https://www.loom.com/share/...",
85-
"pitch": "One sentence on why you should win this week.",
86-
"competeForWin": true
87-
}`;
74+
const obj: Record<string, unknown> = {
75+
githubHandle: handle,
76+
name: displayName ?? "Your Name",
77+
photoUrl: photoUrl ?? "https://example.com/your-photo.jpg",
78+
repoUrl: `https://github.com/${handle}/your-week-${week.week}-build`,
79+
...(week.liveUrlRequired
80+
? { liveUrl: "https://yourthing.example.com" }
81+
: {}),
82+
loomUrl: "https://www.loom.com/share/...",
83+
pitch: "One sentence on why you should win this week.",
84+
competeForWin: true,
85+
};
86+
return JSON.stringify(obj, null, 2);
8887
}
8988

9089
function buildSubmissionPath(

package-lock.json

Lines changed: 3 additions & 31 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,8 @@
168168
"react": "$react",
169169
"react-dom": "$react-dom"
170170
},
171-
"http-proxy-agent": "^7.0.0"
171+
"http-proxy-agent": "^7.0.0",
172+
"postcss": "^8.5.12"
172173
},
173174
"lint-staged": {
174175
"**/*.{ts,tsx}": [

scripts/_lib/parse-github-login.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* Copyright (C) 2026 Cursor Boston
3+
* This file is part of Cursor Boston, licensed under GPL-3.0.
4+
* See LICENSE file for details.
5+
*/
6+
7+
/**
8+
* Shared helper for the script tools that parse a GitHub login from a free-form
9+
* CSV cell — accepts a bare login (`octocat`, `@octocat`), a profile URL
10+
* (`https://github.com/octocat`), or an unprefixed form (`github.com/octocat`).
11+
*
12+
* Strict hostname check (`github.com` / `www.github.com` only) — the previous
13+
* `hostname.includes("github.com")` and `lower.includes("github.com")` patterns
14+
* matched `evilgithub.com` and `something.com/github.com/foo` respectively
15+
* (CodeQL js/incomplete-url-substring-sanitization).
16+
*/
17+
18+
const INVALID_LOGIN_TOKENS = new Set([
19+
"",
20+
"n",
21+
"no",
22+
"none",
23+
"na",
24+
"n/a",
25+
"-",
26+
".",
27+
"unknown",
28+
]);
29+
30+
function isGithubHostname(hostname: string): boolean {
31+
const h = hostname.toLowerCase();
32+
return h === "github.com" || h === "www.github.com";
33+
}
34+
35+
function loginFromUrl(candidate: string): string | null {
36+
try {
37+
const u = new URL(candidate);
38+
if (!isGithubHostname(u.hostname)) return null;
39+
const parts = u.pathname.split("/").filter(Boolean);
40+
return parts[0] ?? null;
41+
} catch {
42+
return null;
43+
}
44+
}
45+
46+
export function parseGithubLogin(raw: string | undefined | null): string | null {
47+
if (!raw || typeof raw !== "string") return null;
48+
const trimmed = raw.trim();
49+
if (!trimmed) return null;
50+
const lower = trimmed.toLowerCase();
51+
52+
let login: string | null = null;
53+
if (lower.startsWith("http://") || lower.startsWith("https://")) {
54+
login = loginFromUrl(trimmed);
55+
} else if (/^(www\.)?github\.com[/:@]/i.test(trimmed)) {
56+
// Unprefixed form like "github.com/foo" — prepend a scheme so the URL
57+
// parser can do strict hostname validation.
58+
login = loginFromUrl(`https://${trimmed.replace(/^\/+/, "")}`);
59+
} else {
60+
// Treat as bare login.
61+
login = trimmed;
62+
}
63+
64+
if (!login) return null;
65+
const cleaned = login.replace(/^@+/, "");
66+
if (INVALID_LOGIN_TOKENS.has(cleaned.toLowerCase()) || cleaned.length < 2) {
67+
return null;
68+
}
69+
return cleaned;
70+
}

scripts/ai-evaluate-submissions.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ function githubHeaders(): Record<string, string> {
8181
async function fetchRepoReadme(repoUrl: string): Promise<string | null> {
8282
try {
8383
const url = new URL(repoUrl);
84-
if (!url.hostname.includes("github.com")) return null;
84+
const host = url.hostname.toLowerCase();
85+
if (host !== "github.com" && host !== "www.github.com") return null;
8586
const parts = url.pathname.split("/").filter(Boolean);
8687
if (parts.length < 2) return null;
8788
const [owner, repo] = parts;

scripts/build-hack-a-sprint-ranking.ts

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { readFileSync, writeFileSync } from "fs";
1818
import { homedir } from "os";
1919
import { join, resolve } from "path";
2020
import { DECLINED_EMAILS, JUDGE_EMAILS } from "../lib/hackathon-event-signup";
21+
import { parseGithubLogin } from "./_lib/parse-github-login";
2122

2223
const REPO_OWNER = "rogerSuperBuilderAlpha";
2324
const REPO_NAME = "cursor-boston";
@@ -38,10 +39,6 @@ const GITHUB_LOGIN_CORRECTIONS: Record<string, string> = {
3839
dannygarciadev: "DannyGarciaDEV",
3940
};
4041

41-
const INVALID_LOGIN_TOKENS = new Set([
42-
"", "n", "no", "none", "na", "n/a", "-", ".", "unknown",
43-
]);
44-
4542
type CsvRow = Record<string, string>;
4643

4744
function parseCsv(content: string): CsvRow[] {
@@ -73,30 +70,6 @@ function parseCsv(content: string): CsvRow[] {
7370
});
7471
}
7572

76-
function parseGithubLogin(raw: string): string | null {
77-
if (!raw) return null;
78-
let s = raw.trim();
79-
const lower = s.toLowerCase();
80-
if (lower.startsWith("http://") || lower.startsWith("https://")) {
81-
try {
82-
const u = new URL(s);
83-
if (!u.hostname.includes("github.com")) return null;
84-
const parts = u.pathname.split("/").filter(Boolean);
85-
if (parts.length === 0) return null;
86-
s = parts[0]!;
87-
} catch { return null; }
88-
} else if (lower.includes("github.com")) {
89-
const idx = lower.indexOf("github.com");
90-
const rest = s.slice(idx + "github.com".length).replace(/^[/:]+/, "");
91-
const parts = rest.split("/").filter(Boolean);
92-
if (parts.length === 0) return null;
93-
s = parts[0]!;
94-
}
95-
s = s.replace(/^@+/, "");
96-
if (INVALID_LOGIN_TOKENS.has(s.toLowerCase()) || s.length < 2) return null;
97-
return s;
98-
}
99-
10073
function resolveGithubLogin(lumaLogin: string): string {
10174
const corrected = GITHUB_LOGIN_CORRECTIONS[lumaLogin.toLowerCase()];
10275
return corrected ?? lumaLogin;

scripts/seed-luma-registrants.ts

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,10 @@ import {
2929
isHackathonEventSignupId,
3030
} from "../lib/hackathon-event-signup";
3131
import { getAdminDb } from "../lib/firebase-admin";
32+
import { parseGithubLogin } from "./_lib/parse-github-login";
3233

3334
const GITHUB_COL_KEY = "What is your GitHub username?";
3435

35-
const INVALID_LOGIN_TOKENS = new Set([
36-
"", "n", "no", "none", "na", "n/a", "-", ".", "unknown",
37-
]);
38-
3936
type CsvRow = Record<string, string>;
4037

4138
function parseCsv(content: string): CsvRow[] {
@@ -69,31 +66,6 @@ function parseCsv(content: string): CsvRow[] {
6966
return out;
7067
}
7168

72-
function parseGithubLogin(raw: string | undefined): string | null {
73-
if (!raw || typeof raw !== "string") return null;
74-
let s = raw.trim();
75-
if (!s) return null;
76-
const lower = s.toLowerCase();
77-
if (lower.startsWith("http://") || lower.startsWith("https://")) {
78-
try {
79-
const u = new URL(s.startsWith("http") ? s : `https://${s}`);
80-
if (!u.hostname.includes("github.com")) return null;
81-
const parts = u.pathname.split("/").filter(Boolean);
82-
if (parts.length === 0) return null;
83-
s = parts[0]!;
84-
} catch { return null; }
85-
} else if (lower.includes("github.com")) {
86-
const idx = lower.indexOf("github.com");
87-
const rest = s.slice(idx + "github.com".length).replace(/^[/:]+/, "");
88-
const parts = rest.split("/").filter(Boolean);
89-
if (parts.length === 0) return null;
90-
s = parts[0]!;
91-
}
92-
s = s.replace(/^@+/, "");
93-
if (INVALID_LOGIN_TOKENS.has(s.toLowerCase()) || s.length < 2) return null;
94-
return s;
95-
}
96-
9769
function parseArgs(argv: string[]) {
9870
const dryRun = argv.includes("--dry-run");
9971
const apply = argv.includes("--apply");

scripts/send-hack-a-sprint-emails.ts

Lines changed: 1 addition & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ import {
6868
import { getGithubRepoPair, getGithubRepoWebBaseUrl } from "../lib/github-recent-merged-prs";
6969
import { getAdminAuth, getAdminDb } from "../lib/firebase-admin";
7070
import { sendEmail } from "../lib/mailgun";
71+
import { parseGithubLogin } from "./_lib/parse-github-login";
7172

7273
const SITE_ORIGIN = process.env.NEXT_PUBLIC_APP_URL || "https://cursorboston.com";
7374
const SIGNUP_PATH = "/hackathons/hack-a-sprint-2026/signup";
@@ -95,17 +96,6 @@ const USER_ID_IN_CHUNK = 10;
9596
/** Update before send if the open PR queue size changed (see GitHub pulls tab). */
9697
const DAYOF_OPEN_PR_QUEUE_COUNT = 16;
9798

98-
const INVALID_LOGIN_TOKENS = new Set([
99-
"",
100-
"n",
101-
"no",
102-
"none",
103-
"na",
104-
"n/a",
105-
"-",
106-
".",
107-
"unknown",
108-
]);
10999

110100
type RegistrantTier =
111101
| "DECLINED"
@@ -172,33 +162,6 @@ function parseCsv(content: string): CsvRow[] {
172162
}
173163

174164
/** Extract GitHub login from URL or bare login; returns null if unusable. */
175-
function parseGithubLogin(raw: string | undefined): string | null {
176-
if (!raw || typeof raw !== "string") return null;
177-
let s = raw.trim();
178-
if (!s) return null;
179-
const lower = s.toLowerCase();
180-
if (lower.startsWith("http://") || lower.startsWith("https://")) {
181-
try {
182-
const u = new URL(s.startsWith("http") ? s : `https://${s}`);
183-
if (!u.hostname.includes("github.com")) return null;
184-
const parts = u.pathname.split("/").filter(Boolean);
185-
if (parts.length === 0) return null;
186-
s = parts[0]!;
187-
} catch {
188-
return null;
189-
}
190-
} else if (lower.includes("github.com")) {
191-
const idx = lower.indexOf("github.com");
192-
const rest = s.slice(idx + "github.com".length).replace(/^[/:]+/, "");
193-
const parts = rest.split("/").filter(Boolean);
194-
if (parts.length === 0) return null;
195-
s = parts[0]!;
196-
}
197-
s = s.replace(/^@+/, "");
198-
if (INVALID_LOGIN_TOKENS.has(s.toLowerCase()) || s.length < 2) return null;
199-
return s;
200-
}
201-
202165
function signedUpAtToMs(value: unknown): number {
203166
if (
204167
value &&

0 commit comments

Comments
 (0)