Skip to content

Commit 943bf5f

Browse files
pepicrftclaude
andcommitted
Validate source control URL before registry identity lookup
Add validation in `lookupIdentities` to detect malformed URLs before making HTTP requests to the registry server. This prevents sending invalid URLs that may contain git credential error messages concatenated to the original URL. When macOS git-credential-osxkeychain fails to find credentials, it outputs error messages in a format like: 'https://github.com/owner/repo.git': failed looking up identity for ... In some edge cases, these error messages can get concatenated with the original URL, resulting in malformed URLs being passed to the registry. This causes server-side errors and confusing diagnostics. The validation checks: - URL can be parsed by Foundation - URL has a non-empty host - URL doesn't contain patterns indicative of concatenated error messages 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 5547e32 commit 943bf5f

File tree

2 files changed

+37
-0
lines changed

2 files changed

+37
-0
lines changed

Sources/PackageRegistry/RegistryClient.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1017,6 +1017,19 @@ public final class RegistryClient: AsyncCancellable {
10171017
timeout: DispatchTimeInterval? = .none,
10181018
observabilityScope: ObservabilityScope
10191019
) async throws -> Set<PackageIdentity> {
1020+
// Validate that the source control URL is well-formed before making the request.
1021+
// This catches malformed URLs that may contain error messages (e.g., from git credential helpers).
1022+
// Git credential errors often produce messages like:
1023+
// "'https://github.com/owner/repo.git': failed looking up identity for ..."
1024+
// which can get concatenated with the URL.
1025+
guard let url = scmURL.url,
1026+
let host = url.host,
1027+
!host.isEmpty,
1028+
!scmURL.absoluteString.contains("': "),
1029+
!scmURL.absoluteString.contains("' :") else {
1030+
throw RegistryError.invalidSourceControlURL(scmURL)
1031+
}
1032+
10201033
guard let registry = self.configuration.defaultRegistry else {
10211034
throw RegistryError.registryNotConfigured(scope: nil)
10221035
}
@@ -1503,6 +1516,7 @@ public enum RegistryError: Error, CustomStringConvertible {
15031516
case registryNotConfigured(scope: PackageIdentity.Scope?)
15041517
case invalidPackageIdentity(PackageIdentity)
15051518
case invalidURL(URL)
1519+
case invalidSourceControlURL(SourceControlURL)
15061520
case invalidResponseStatus(expected: [Int], actual: Int)
15071521
case invalidContentVersion(expected: String, actual: String?)
15081522
case invalidContentType(expected: String, actual: String?)
@@ -1580,6 +1594,8 @@ public enum RegistryError: Error, CustomStringConvertible {
15801594
return "invalid package identifier '\(packageIdentity)'"
15811595
case .invalidURL(let url):
15821596
return "invalid URL '\(url)'"
1597+
case .invalidSourceControlURL(let scmURL):
1598+
return "invalid source control URL '\(scmURL)'"
15831599
case .invalidResponseStatus(let expected, let actual):
15841600
return "invalid registry response status '\(actual)', expected '\(expected)'"
15851601
case .invalidContentVersion(let expected, let actual):

Tests/PackageRegistryTests/RegistryClientTests.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3148,6 +3148,27 @@ fileprivate var availabilityURL = URL("\(registryURL)/availability")
31483148
let identities = try await registryClient.lookupIdentities(scmURL: packageURL)
31493149
#expect([PackageIdentity.plain("mona.LinkedList")] == identities)
31503150
}
3151+
3152+
@Test func invalidSourceControlURL() async throws {
3153+
// Test that malformed URLs (e.g., containing git credential error messages) are rejected
3154+
let malformedURL = SourceControlURL("https://github.com/owner/repo.git': failed looking up identity")
3155+
3156+
let httpClient = HTTPClient(implementation: { _, _ in
3157+
throw StringError("should not make HTTP request for invalid URL")
3158+
})
3159+
var configuration = RegistryConfiguration()
3160+
configuration.defaultRegistry = Registry(url: registryURL, supportsAvailability: false)
3161+
3162+
let registryClient = makeRegistryClient(configuration: configuration, httpClient: httpClient)
3163+
await #expect {
3164+
try await registryClient.lookupIdentities(scmURL: malformedURL)
3165+
} throws: { error in
3166+
if case RegistryError.invalidSourceControlURL(malformedURL) = error {
3167+
return true
3168+
}
3169+
return false
3170+
}
3171+
}
31513172
}
31523173

31533174
@Suite("Login") struct Login {

0 commit comments

Comments
 (0)