From 235b2e312838a2fa7b8e65eb0a5773d9c6e07a06 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 13 Jan 2026 23:41:13 +0000 Subject: [PATCH 1/5] Initial plan From 30af869571920cfb904966855ed38f512df1924d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 13 Jan 2026 23:45:33 +0000 Subject: [PATCH 2/5] Update link validation to check local files for any GitHub org Co-authored-by: hallipr <1291634+hallipr@users.noreply.github.com> --- .../templates/steps/verify-links.yml | 1 + eng/common/scripts/Verify-Links.ps1 | 81 ++++++++++++++++--- 2 files changed, 70 insertions(+), 12 deletions(-) diff --git a/eng/common/pipelines/templates/steps/verify-links.yml b/eng/common/pipelines/templates/steps/verify-links.yml index 896b30d0fe..595afc3456 100644 --- a/eng/common/pipelines/templates/steps/verify-links.yml +++ b/eng/common/pipelines/templates/steps/verify-links.yml @@ -31,4 +31,5 @@ steps: -checkLinkGuidance: ${{ parameters.CheckLinkGuidance }} -localBuildRepoName "$env:BUILD_REPOSITORY_NAME" -localBuildRepoPath $(Build.SourcesDirectory) + -localBuildTargetBranch "$env:SYSTEM_PULLREQUEST_TARGETBRANCH" -inputCacheFile "https://azuresdkartifacts.blob.core.windows.net/verify-links-cache/verify-links-cache.txt" diff --git a/eng/common/scripts/Verify-Links.ps1 b/eng/common/scripts/Verify-Links.ps1 index 0eb1798da6..868a5b579b 100644 --- a/eng/common/scripts/Verify-Links.ps1 +++ b/eng/common/scripts/Verify-Links.ps1 @@ -51,6 +51,9 @@ .PARAMETER localBuildRepoPath The path to the local build repo. This is used to resolve links to local files in the repo instead of making web requests. + .PARAMETER localBuildTargetBranch + The target branch of the PR. This is used to resolve links to local files when the link points to this branch. + .PARAMETER requestTimeoutSec The number of seconds before we timeout when sending an individual web request. Default is 15 seconds. @@ -80,6 +83,7 @@ param ( [string] $localGithubClonedRoot = "", [string] $localBuildRepoName = "", [string] $localBuildRepoPath = "", + [string] $localBuildTargetBranch = "", [string] $requestTimeoutSec = 15 ) @@ -91,20 +95,73 @@ $ProgressPreference = "SilentlyContinue"; # Disable invoke-webrequest progress d function ProcessLink([System.Uri]$linkUri) { # To help improve performance and rate limiting issues with github links we try to resolve them based on a local clone if one exists. - if (($localGithubClonedRoot -or $localBuildRepoName) -and $linkUri -match '^https://github.com/(?Azure)/(?[^/]+)/(?:blob|tree)/(main|.*_[^/]+|.*/v[^/]+)/(?.*)$') { - - if ($localBuildRepoName -eq ($matches['org'] + "/" + $matches['repo'])) { - # If the link is to the current repo, use the local build path - $localPath = Join-Path $localBuildRepoPath $matches['path'] - } - else { - # Otherwise use the local github clone path - $localPath = Join-Path $localGithubClonedRoot $matches['repo'] $matches['path'] + # Match github.com blob/tree URLs + $githubPattern = '^https://github\.com/(?[^/]+)/(?[^/]+)/(?:blob|tree)/(?[^/]+)/(?.*)$' + # Match raw.githubusercontent.com URLs + $rawPattern = '^https://raw\.githubusercontent\.com/(?[^/]+)/(?[^/]+)/(?[^/]+)/(?.*)$' + + $matchedPattern = $false + $org = $null + $repo = $null + $branch = $null + $path = $null + + if ($linkUri -match $githubPattern) { + $matchedPattern = $true + $org = $matches['org'] + $repo = $matches['repo'] + $branch = $matches['branch'] + $path = $matches['path'] + } + elseif ($linkUri -match $rawPattern) { + $matchedPattern = $true + $org = $matches['org'] + $repo = $matches['repo'] + $branch = $matches['branch'] + $path = $matches['path'] + } + + if ($matchedPattern -and ($localGithubClonedRoot -or $localBuildRepoName)) { + $repoFullName = "$org/$repo" + + # Check if this link points to the current repo + if ($localBuildRepoName -eq $repoFullName) { + # Check if the link points to the target branch (if specified) + # If no target branch is specified, fall back to checking any Azure org link with specific branch patterns (legacy behavior) + $shouldCheckLocalFile = $false + + if ($localBuildTargetBranch -and $branch -eq $localBuildTargetBranch) { + # Link points to current repo and target branch - check local filesystem + $shouldCheckLocalFile = $true + } + elseif (!$localBuildTargetBranch -and $org -eq "Azure" -and $branch -match '^(main|.*_[^/]+|.*/v[^/]+)$') { + # Legacy behavior: check local files for Azure org with specific branch patterns + $shouldCheckLocalFile = $true + } + + if ($shouldCheckLocalFile) { + $localPath = Join-Path $localBuildRepoPath $path + + if (Test-Path $localPath) { + # File exists locally - link will be valid + return $true + } + else { + # File does not exist locally - this PR would break the link + LogError "Link points to file that does not exist in local repo: $linkUri (local path: $localPath)" + return $false + } + } } - - if (Test-Path $localPath) { - return $true + elseif ($localGithubClonedRoot -and $org -eq "Azure") { + # For other Azure repos, use the local github clone path (legacy behavior) + $localPath = Join-Path $localGithubClonedRoot $repo $path + if (Test-Path $localPath) { + return $true + } } + + # If we didn't return above, fall through to standard link checking return ProcessStandardLink $linkUri } if ($linkUri -match '^https?://?github\.com/(?)[^/]+/(?)[^/]+/wiki/.+') { From d3bedbd1d9c550828b70807c2410e09e430fc393 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 13 Jan 2026 23:50:34 +0000 Subject: [PATCH 3/5] Address security concerns: prevent ReDoS and protect filesystem paths Co-authored-by: hallipr <1291634+hallipr@users.noreply.github.com> --- eng/common/scripts/Verify-Links.ps1 | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/eng/common/scripts/Verify-Links.ps1 b/eng/common/scripts/Verify-Links.ps1 index 868a5b579b..c7c17eb2bb 100644 --- a/eng/common/scripts/Verify-Links.ps1 +++ b/eng/common/scripts/Verify-Links.ps1 @@ -95,10 +95,10 @@ $ProgressPreference = "SilentlyContinue"; # Disable invoke-webrequest progress d function ProcessLink([System.Uri]$linkUri) { # To help improve performance and rate limiting issues with github links we try to resolve them based on a local clone if one exists. - # Match github.com blob/tree URLs - $githubPattern = '^https://github\.com/(?[^/]+)/(?[^/]+)/(?:blob|tree)/(?[^/]+)/(?.*)$' - # Match raw.githubusercontent.com URLs - $rawPattern = '^https://raw\.githubusercontent\.com/(?[^/]+)/(?[^/]+)/(?[^/]+)/(?.*)$' + # Match github.com blob/tree URLs (path excludes fragments and query params to prevent ReDoS) + $githubPattern = '^https://github\.com/(?[^/]+)/(?[^/]+)/(?:blob|tree)/(?[^/]+)/(?[^#?]*)$' + # Match raw.githubusercontent.com URLs (path excludes fragments and query params to prevent ReDoS) + $rawPattern = '^https://raw\.githubusercontent\.com/(?[^/]+)/(?[^/]+)/(?[^/]+)/(?[^#?]*)$' $matchedPattern = $false $org = $null @@ -124,6 +124,9 @@ function ProcessLink([System.Uri]$linkUri) { if ($matchedPattern -and ($localGithubClonedRoot -or $localBuildRepoName)) { $repoFullName = "$org/$repo" + # Legacy branch pattern for Azure org backward compatibility + $legacyAzureBranchPattern = '^(main|.*_[^/]+|.*/v[^/]+)$' + # Check if this link points to the current repo if ($localBuildRepoName -eq $repoFullName) { # Check if the link points to the target branch (if specified) @@ -134,7 +137,7 @@ function ProcessLink([System.Uri]$linkUri) { # Link points to current repo and target branch - check local filesystem $shouldCheckLocalFile = $true } - elseif (!$localBuildTargetBranch -and $org -eq "Azure" -and $branch -match '^(main|.*_[^/]+|.*/v[^/]+)$') { + elseif (!$localBuildTargetBranch -and $org -eq "Azure" -and $branch -match $legacyAzureBranchPattern) { # Legacy behavior: check local files for Azure org with specific branch patterns $shouldCheckLocalFile = $true } @@ -148,7 +151,13 @@ function ProcessLink([System.Uri]$linkUri) { } else { # File does not exist locally - this PR would break the link - LogError "Link points to file that does not exist in local repo: $linkUri (local path: $localPath)" + # Log relative path to avoid exposing full filesystem structure + $relativePath = if ($localBuildRepoPath) { + $path + } else { + $localPath + } + LogError "Link points to file that does not exist in local repo: $linkUri (path: $relativePath)" return $false } } From a5d92601af2ad13cb5a769b85a8009d954f7ff8c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 13 Jan 2026 23:52:27 +0000 Subject: [PATCH 4/5] Fix code review issues: simplify path logging and remove empty regex groups Co-authored-by: hallipr <1291634+hallipr@users.noreply.github.com> --- eng/common/scripts/Verify-Links.ps1 | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/eng/common/scripts/Verify-Links.ps1 b/eng/common/scripts/Verify-Links.ps1 index c7c17eb2bb..c87f6584ec 100644 --- a/eng/common/scripts/Verify-Links.ps1 +++ b/eng/common/scripts/Verify-Links.ps1 @@ -151,13 +151,8 @@ function ProcessLink([System.Uri]$linkUri) { } else { # File does not exist locally - this PR would break the link - # Log relative path to avoid exposing full filesystem structure - $relativePath = if ($localBuildRepoPath) { - $path - } else { - $localPath - } - LogError "Link points to file that does not exist in local repo: $linkUri (path: $relativePath)" + # Log only the relative path to avoid exposing full filesystem structure + LogError "Link points to file that does not exist in local repo: $linkUri (path: $path)" return $false } } @@ -173,7 +168,7 @@ function ProcessLink([System.Uri]$linkUri) { # If we didn't return above, fall through to standard link checking return ProcessStandardLink $linkUri } - if ($linkUri -match '^https?://?github\.com/(?)[^/]+/(?)[^/]+/wiki/.+') { + if ($linkUri -match '^https?://?github\.com/[^/]+/[^/]+/wiki/.+') { # in an unauthenticated session, urls for missing pages will redirect to the wiki root return ProcessRedirectLink $linkUri -invalidStatusCodes 302 } From ee0a05175197eff69cb613a7d63d8e971afdec68 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 13 Jan 2026 23:55:06 +0000 Subject: [PATCH 5/5] Fix wiki link regex pattern to properly match http/https schemes Co-authored-by: hallipr <1291634+hallipr@users.noreply.github.com> --- eng/common/scripts/Verify-Links.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/common/scripts/Verify-Links.ps1 b/eng/common/scripts/Verify-Links.ps1 index c87f6584ec..a003c98635 100644 --- a/eng/common/scripts/Verify-Links.ps1 +++ b/eng/common/scripts/Verify-Links.ps1 @@ -168,7 +168,7 @@ function ProcessLink([System.Uri]$linkUri) { # If we didn't return above, fall through to standard link checking return ProcessStandardLink $linkUri } - if ($linkUri -match '^https?://?github\.com/[^/]+/[^/]+/wiki/.+') { + if ($linkUri -match '^https?://github\.com/[^/]+/[^/]+/wiki/.+') { # in an unauthenticated session, urls for missing pages will redirect to the wiki root return ProcessRedirectLink $linkUri -invalidStatusCodes 302 }