summaryrefslogtreecommitdiff
path: root/Hutch/Networking
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-03-18 19:16:02 -0500
committerChristian Cleberg <[email protected]>2026-03-18 19:16:02 -0500
commit7fba8922e1540741240555560cc9504342bdb091 (patch)
tree8f87280f5b50dcf2199de2e8305e020276d51421 /Hutch/Networking
parent1745311950fd68ad81957b3ba64f8566cd86ce19 (diff)
downloadhutch-7fba8922e1540741240555560cc9504342bdb091.tar.gz
hutch-7fba8922e1540741240555560cc9504342bdb091.tar.bz2
hutch-7fba8922e1540741240555560cc9504342bdb091.zip
Guard authenticated text fetches to sr.ht hosts and add regression test
Diffstat (limited to 'Hutch/Networking')
-rw-r--r--Hutch/Networking/SRHTClient.swift14
-rw-r--r--Hutch/Networking/SRHTError.swift4
2 files changed, 18 insertions, 0 deletions
diff --git a/Hutch/Networking/SRHTClient.swift b/Hutch/Networking/SRHTClient.swift
index a24fe24..6032cab 100644
--- a/Hutch/Networking/SRHTClient.swift
+++ b/Hutch/Networking/SRHTClient.swift
@@ -435,6 +435,9 @@ final class SRHTClient: Sendable {
guard let token = _token.withLock({ $0 }), !token.isEmpty else {
throw SRHTError.unauthorized
}
+ guard Self.isTrustedAuthenticatedTextURL(url) else {
+ throw SRHTError.invalidAuthenticatedURL(url)
+ }
var request = URLRequest(url: url)
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
@@ -512,6 +515,17 @@ final class SRHTClient: Sendable {
// MARK: - Data Helper
+private extension SRHTClient {
+ static func isTrustedAuthenticatedTextURL(_ url: URL) -> Bool {
+ guard url.scheme?.localizedCaseInsensitiveCompare("https") == .orderedSame,
+ let host = url.host?.lowercased() else {
+ return false
+ }
+
+ return host.hasSuffix(".sr.ht")
+ }
+}
+
private extension Data {
mutating func append(_ string: String) {
if let data = string.data(using: .utf8) {
diff --git a/Hutch/Networking/SRHTError.swift b/Hutch/Networking/SRHTError.swift
index f148c00..f2da408 100644
--- a/Hutch/Networking/SRHTError.swift
+++ b/Hutch/Networking/SRHTError.swift
@@ -6,6 +6,8 @@ enum SRHTError: LocalizedError, Sendable {
case graphQLErrors([GraphQLError])
/// The HTTP response had a non-2xx status code.
case httpError(Int)
+ /// The client refused to send credentials to an unexpected URL.
+ case invalidAuthenticatedURL(URL)
/// The response data could not be decoded.
case decodingError(any Error)
/// A networking error from URLSession (timeout, DNS, connectivity, etc.).
@@ -20,6 +22,8 @@ enum SRHTError: LocalizedError, Sendable {
return "GraphQL error: \(messages)"
case .httpError(let code):
return "Server returned HTTP \(code)."
+ case .invalidAuthenticatedURL(let url):
+ return "Refused to authenticate request to unexpected URL: \(url.absoluteString)"
case .decodingError(let error):
return "Failed to decode response: \(error.localizedDescription)"
case .networkError(let error):