Skip to content

Commit d07981b

Browse files
committed
refactor: replace regex with URL parser in convertGitHubWebUrl
Remove the regular expression used to parse GitHub blob URLs in favour of the built-in URL constructor and string operations. This eliminates the potential ReDoS security hotspot flagged by SonarCloud while preserving full support for slash-based branch names (e.g. 'feature/new-branch'). Fixes SonarCloud security hotspot on validation.service.ts:76.
1 parent a982d23 commit d07981b

1 file changed

Lines changed: 37 additions & 17 deletions

File tree

src/domains/services/validation.service.ts

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -62,26 +62,46 @@ const isValidGitHubBlobUrl = (url: string): boolean => {
6262
};
6363

6464
/**
65-
* Convert GitHub web URL to API URL
65+
* Convert GitHub web URL to API URL.
66+
* Supports slash-based branch names (e.g. 'feature/new-validation').
67+
* Uses URL parsing instead of regex to avoid potential ReDoS.
68+
* Fixes: https://github.com/asyncapi/cli/issues/1940
6669
*/
6770
const convertGitHubWebUrl = (url: string): string => {
68-
// Remove fragment from URL before processing
69-
const urlWithoutFragment = url.split('#')[0];
70-
71-
// Handle GitHub web URLs like: https://github.com/owner/repo/blob/branch/path
72-
// eslint-disable-next-line no-useless-escape
73-
// Support slash-based branch names (e.g. 'feature/new-validation') by not restricting
74-
// the branch segment to non-slash characters. The branch ends at the last /blob/ marker.
75-
// Fixes: https://github.com/asyncapi/cli/issues/1940
76-
const githubWebPattern = /^https:\/\/github\.com\/([^\/]+)\/([^\/]+)\/blob\/(.+?)\/([^?#]+(?:\?[^#]*)?)$/;
77-
const match = urlWithoutFragment.match(githubWebPattern);
78-
79-
if (match) {
80-
const [, owner, repo, branch, filePath] = match;
81-
return `https://api.github.com/repos/${owner}/${repo}/contents/${filePath}?ref=${branch}`;
82-
}
71+
try {
72+
// Strip fragment and parse URL
73+
const parsed = new URL(url.split('#')[0]);
74+
if (parsed.hostname !== 'github.com') {
75+
return url;
76+
}
77+
78+
// Path must contain /blob/ to be a GitHub blob URL
79+
const blobMarker = '/blob/';
80+
const blobIndex = parsed.pathname.indexOf(blobMarker);
81+
if (blobIndex === -1) {
82+
return url;
83+
}
84+
85+
// owner/repo is before /blob/
86+
const ownerRepo = parsed.pathname.slice(1, blobIndex);
87+
// Everything after /blob/ is branch-name/file-path
88+
const afterBlob = parsed.pathname.slice(blobIndex + blobMarker.length);
89+
const segments = afterBlob.split('/');
8390

84-
return url;
91+
// Need at least two segments: one for branch root and one for filename
92+
if (segments.length < 2) {
93+
return url;
94+
}
95+
96+
// The file is the last segment (plus any query params)
97+
const fileName = segments[segments.length - 1] + (parsed.search || '');
98+
// Everything before the last segment is the branch
99+
const branch = segments.slice(0, -1).join('/');
100+
101+
return `https://api.github.com/repos/${ownerRepo}/contents/${fileName}?ref=${branch}`;
102+
} catch {
103+
return url;
104+
}
85105
};
86106

87107
/**

0 commit comments

Comments
 (0)