From bf7aeb5e31584bb700df921ca056cfc97f615473 Mon Sep 17 00:00:00 2001 From: Lokesh Kaki Date: Sun, 15 Mar 2026 20:12:12 -0500 Subject: [PATCH] fix(catalog-backend-module-azure): fix ReDoS regex, URL encoding, error message, and missing deps Signed-off-by: Lokesh Kaki --- .../catalog-backend-module-azure/package.json | 2 ++ .../events/analyzeAzureDevOpsWebhookEvent.ts | 33 +++++++++++++++---- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index 9c8de87ffb..51e87bdd06 100644 --- a/plugins/catalog-backend-module-azure/package.json +++ b/plugins/catalog-backend-module-azure/package.json @@ -55,9 +55,11 @@ "@azure/storage-blob": "^12.5.0", "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", + "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-catalog-common": "workspace:^", "@backstage/plugin-catalog-node": "workspace:^", + "@backstage/plugin-events-node": "workspace:^", "uuid": "^11.0.0" }, "devDependencies": { diff --git a/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.ts b/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.ts index 054eb310f7..10bf5e75d7 100644 --- a/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.ts +++ b/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.ts @@ -154,9 +154,18 @@ function toLocationUrl(options: { return undefined; } + const url = new URL(options.remoteUrl); const branch = branchNameFromRef(options.branchRef); - const branchSuffix = branch ? `&version=GB${branch}` : ''; - return encodeURI(`${options.remoteUrl}?path=${options.path}${branchSuffix}`); + // Encode each path segment individually to protect against special chars while + // preserving '/' separators, which is what Azure DevOps expects in the path param. + const encodedPath = options.path + .split('/') + .map(encodeURIComponent) + .join('/'); + url.search = branch + ? `path=${encodedPath}&version=GB${encodeURIComponent(branch)}` + : `path=${encodedPath}`; + return url.toString(); } function toCommitUrl( @@ -359,11 +368,16 @@ function replaceRepoNameInRemoteUrl( if (!remoteUrl || !repoName) { return undefined; } - const match = remoteUrl.match(/^(.*\/_git\/)([^/?#]+)(.*)$/); - if (!match) { + const gitMarker = '/_git/'; + const gitIdx = remoteUrl.indexOf(gitMarker); + if (gitIdx === -1) { return undefined; } - return `${match[1]}${repoName}${match[3]}`; + const prefix = remoteUrl.slice(0, gitIdx + gitMarker.length); + const rest = remoteUrl.slice(gitIdx + gitMarker.length); + const endIdx = rest.search(/[/?#]/); + const suffix = endIdx === -1 ? '' : rest.slice(endIdx); + return `${prefix}${repoName}${suffix}`; } async function onPushEvent( @@ -465,11 +479,18 @@ async function onRepositoryEvent( if (eventType === 'git.repo.renamed' && toUrl) { const oldName = asString(resource?.oldName); + if (!oldName) { + return { + result: 'ignored', + reason: 'Azure DevOps repository renamed event is missing oldName', + }; + } const fromUrl = replaceRepoNameInRemoteUrl(toUrl, oldName); if (!fromUrl) { return { result: 'ignored', - reason: 'Azure DevOps repository renamed event is missing oldName', + reason: + 'Azure DevOps repository renamed event has an unexpected repository.remoteUrl format', }; }