From c608b23f0cb25082df23d06288444d73679f4221 Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Tue, 17 Feb 2026 04:32:52 +0000 Subject: [PATCH] fix: use strict hostname check for GitHub URLs in OSS sponsorship router Replace loose .includes('github.com') check with exact hostname comparison to prevent lookalike domains like github.com.evil.com. Add isStrictGitHubUrl validator and apply it as a Zod refinement on the CSV row schema. --- src/routers/admin/oss-sponsorship-router.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/routers/admin/oss-sponsorship-router.ts b/src/routers/admin/oss-sponsorship-router.ts index 439ccce44..56c0f915d 100644 --- a/src/routers/admin/oss-sponsorship-router.ts +++ b/src/routers/admin/oss-sponsorship-router.ts @@ -23,8 +23,23 @@ import { TRPCError } from '@trpc/server'; import { getIntegrationForOrganization } from '@/lib/integrations/db/platform-integrations'; import { getAgentConfig } from '@/lib/agent-config/db/agent-configs'; +/** + * Validate that a URL is strictly a GitHub.com URL. + * Rejects lookalike hostnames like github.com.evil.com. + */ +function isStrictGitHubUrl(url: string): boolean { + try { + const parsed = new URL(url); + return parsed.hostname === 'github.com' || parsed.hostname === 'www.github.com'; + } catch { + return false; + } +} + const OssCsvRowSchema = z.object({ - githubUrl: z.string().url(), + githubUrl: z.string().url().refine(isStrictGitHubUrl, { + message: 'URL must be a github.com URL', + }), email: z.string().email(), creditsDollars: z.number().nonnegative(), tier: z.union([z.literal(1), z.literal(2), z.literal(3)]), @@ -47,7 +62,7 @@ function escapeIlikePattern(str: string): string { function extractRepoNameFromUrl(githubUrl: string): string | null { try { const parsed = new URL(githubUrl); - if (!parsed.hostname.includes('github.com')) { + if (parsed.hostname !== 'github.com' && parsed.hostname !== 'www.github.com') { return null; } const pathParts = parsed.pathname.split('/').filter(Boolean);