From 5757bc270ed69f0b6bc5386a4f528f5b4fdd1e73 Mon Sep 17 00:00:00 2001 From: Matthew Alvernaz Date: Fri, 3 Jul 2026 09:41:13 -0700 Subject: [PATCH] Don't put the ABS token in download URLs Send the bearer through the Authorization header instead. Background URLSession serializes task URLs to disk and query strings end up in proxy/CDN access logs, so a token in the URL leaks. The download endpoint already accepts Authorization: Bearer like every other authenticated call in this service. --- .../Network/AudiobookShelfConnectionService.swift | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/BookPlayer/AudiobookShelf/Network/AudiobookShelfConnectionService.swift b/BookPlayer/AudiobookShelf/Network/AudiobookShelfConnectionService.swift index 0db6eabd6..5f010fc01 100644 --- a/BookPlayer/AudiobookShelf/Network/AudiobookShelfConnectionService.swift +++ b/BookPlayer/AudiobookShelf/Network/AudiobookShelfConnectionService.swift @@ -521,22 +521,28 @@ class AudiobookShelfConnectionService: BPLogger { throw URLError(.userAuthenticationRequired) } + // The token is deliberately not appended as a query parameter. ABS accepts + // the bearer via `Authorization: Bearer` on this endpoint, and a token-bearing + // URL leaks: background URLSession serializes task URLs to disk, and query + // strings land in proxy/CDN access logs (Authorization headers don't). return connection.url .appendingPathComponent("api") .appendingPathComponent("items") .appendingPathComponent(item.id) .appendingPathComponent("download") - .appending(queryItems: [URLQueryItem(name: "token", value: connection.apiToken)]) } - /// Returns a URLRequest for downloading a library item, carrying the user-defined + /// Returns a URLRequest for downloading a library item, carrying the bearer + /// token via the standard `Authorization: Bearer` header plus the user-defined /// custom HTTP headers (needed for servers behind Cloudflare Access etc.). public func createItemDownloadRequest(_ item: AudiobookShelfLibraryItem) throws -> URLRequest { - guard connection != nil else { + guard let connection else { throw URLError(.userAuthenticationRequired) } let url = try createItemDownloadUrl(item) - return wrapWithCustomHeaders(url) + var request = wrapWithCustomHeaders(url) + applyAuthenticatedHeaders(to: &request, connection: connection) + return request } /// Wraps an arbitrary URL (e.g. a cover image or stream URL) in a URLRequest that carries