From d523ff377ebfddcfd0c5e8d941688cf93944bffd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=AE=B6=E5=90=8D?= Date: Tue, 28 Apr 2026 15:10:43 +0800 Subject: [PATCH] fix: validate repository URL paths --- .../billing/src/__tests__/org-billing.test.ts | 15 +++++++++++++++ packages/billing/src/org-billing.ts | 16 ++++++++++++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/packages/billing/src/__tests__/org-billing.test.ts b/packages/billing/src/__tests__/org-billing.test.ts index 6f3dfa16ee..7a4262b70c 100644 --- a/packages/billing/src/__tests__/org-billing.test.ts +++ b/packages/billing/src/__tests__/org-billing.test.ts @@ -190,6 +190,15 @@ describe('Organization Billing', () => { expect(result.error).toBeUndefined() }) + it('should validate and normalize SSH URLs', () => { + const result = validateAndNormalizeRepositoryUrl( + 'git@github.com:user/repo.git', + ) + expect(result.isValid).toBe(true) + expect(result.normalizedUrl).toBe('https://github.com/user/repo') + expect(result.error).toBeUndefined() + }) + it('should reject invalid domains', () => { const result = validateAndNormalizeRepositoryUrl( 'https://example.com/user/repo', @@ -204,6 +213,12 @@ describe('Organization Billing', () => { expect(result.error).toBe('Repository domain not allowed') }) + it('should reject URLs without owner and repo path segments', () => { + const result = validateAndNormalizeRepositoryUrl('https://github.com') + expect(result.isValid).toBe(false) + expect(result.error).toBe('Repository path must include owner and repo') + }) + it('should accept allowed domains', () => { const domains = ['github.com', 'gitlab.com', 'bitbucket.org'] diff --git a/packages/billing/src/org-billing.ts b/packages/billing/src/org-billing.ts index 6740b9410b..8035da13ee 100644 --- a/packages/billing/src/org-billing.ts +++ b/packages/billing/src/org-billing.ts @@ -498,8 +498,8 @@ export function validateAndNormalizeRepositoryUrl(url: string): { error?: string } { try { - // Basic URL validation - const urlObj = new URL(url.startsWith('http') ? url : `https://${url}`) + const normalized = normalizeRepositoryUrl(url) + const urlObj = new URL(normalized) // Whitelist allowed domains const allowedDomains = ['github.com', 'gitlab.com', 'bitbucket.org'] @@ -507,8 +507,16 @@ export function validateAndNormalizeRepositoryUrl(url: string): { return { isValid: false, error: 'Repository domain not allowed' } } - // Normalize URL format - const normalized = normalizeRepositoryUrl(url) + const pathSegments = urlObj.pathname + .split('/') + .filter((segment) => segment.length > 0) + + if (pathSegments.length < 2) { + return { + isValid: false, + error: 'Repository path must include owner and repo', + } + } return { isValid: true, normalizedUrl: normalized } } catch (error) {