VYPR
Medium severity5.3NVD Advisory· Published Jun 11, 2026· Updated Jun 11, 2026

CVE-2026-49949

CVE-2026-49949

Description

CodexBar before 0.33.0 allows network-adjacent attackers to intercept cookies, tokens, or API keys via cross-origin or HTTP-downgrade redirects.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

CodexBar before 0.33.0 allows network-adjacent attackers to intercept cookies, tokens, or API keys via cross-origin or HTTP-downgrade redirects.

Vulnerability

CodexBar versions before 0.33.0 contain a credential forwarding vulnerability in the shared ProviderHTTPClient transport handler [1][2][4]. The class ProviderHTTPClient used a URLSession that did not validate redirect requests; when the session received an HTTP redirect from a credentialed provider request, it would follow it regardless of destination scheme or origin. This allowed attackers to redirect requests carrying browser cookies, bearer tokens, or API keys to an unintended host, port, or plaintext HTTP endpoint [4]. The fix, introduced in release 0.33.0, adds a ProviderHTTPRedirectGuardDelegate that blocks redirects leaving the original HTTPS origin while preserving same-origin redirects [3].

Exploitation

An attacker must be network-adjacent (e.g., on the same local network or with Man-in-the-Middle position) and capable of injecting an HTTP redirect response into the communication path between the CodexBar application and the provider server [4]. No additional authentication or user interaction is required beyond the victim using CodexBar with a configured provider. By issuing a cross-origin or HTTP-downgrade redirect, the attacker can capture the credentialed request before it reaches the legitimate provider [1][4].

Impact

Successful exploitation allows the attacker to intercept sensitive credentials—including browser cookies, bearer tokens, and API keys—that are sent as part of the provider request [4]. This leads to unauthorized access to the victim's provider accounts and any downstream data or services protected by those credentials. The compromise scope is limited to the credential contents of the redirected request; no code execution or persistent system access is achieved.

Mitigation

The vulnerability is fixed in CodexBar version 0.33.0, released on or before June 11, 2026 [2]. Users should update to v0.33.0 or later. No workaround is available; the fix introduces a redirect guard that rejects cross-origin and HTTP-downgrade redirects [3]. The CVE is not listed in CISA's Known Exploited Vulnerabilities catalog as of the publication date.

AI Insight generated on Jun 11, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2
  • CodexBar/Codexbarreferences2 versions
    (expand)+ 1 more
    • (no CPE)
    • (no CPE)range: <0.33.0

Patches

1
08c171b6b487

[security] fix(providers): guard credentialed redirects (#1237)

https://github.com/steipete/CodexBarHinotobiJun 10, 2026via nvd-ref
5 files changed · +115 5
  • CHANGELOG.md+1 0 modified
    @@ -8,6 +8,7 @@
     
     ### Fixed
     - Copilot: keep explicitly unlimited chat quotas visible instead of dropping their zero-entitlement payload as unavailable (#1320). Thanks @soumikbhatta!
    +- Security: block credentialed provider redirects that leave the original HTTPS origin while preserving same-origin redirects (#1237). Thanks @Hinotoi-agent!
     - Doubao: confirm zero-remaining HTTP 200 request limits before falling back, preserving genuine exhaustion and avoiding false 100% usage (#1383). Thanks @LeoLin990405 and @foobra!
     - Menu bar: defer pasteboard writes and copy feedback outside the `NSMenu` tracking callback so in-menu copy buttons no longer beachball on macOS 26 (#1388). Thanks @LeoLin990405!
     - Menu bar: defer Overview-row provider transitions out of AppKit's click callback so opening provider detail no longer performs a full synchronous menu rebuild (#1325).
    
  • Sources/CodexBarCore/OpenAIWeb/OpenAIDashboardBrowserCookieImporter.swift+1 1 modified
    @@ -538,7 +538,7 @@ public struct OpenAIDashboardBrowserCookieImporter {
                 request.setValue("application/json", forHTTPHeaderField: "Accept")
     
                 do {
    -                let (data, response) = try await URLSession.shared.data(for: request)
    +                let (data, response) = try await ProviderHTTPClient.shared.data(for: request)
                     let status = (response as? HTTPURLResponse)?.statusCode ?? -1
                     logger("API \(url.host ?? "chatgpt.com") \(url.path) status=\(status)")
                     guard status >= 200, status < 300 else { continue }
    
  • Sources/CodexBarCore/OpenAIWeb/OpenAIDashboardFetcher.swift+2 2 modified
    @@ -763,7 +763,7 @@ public struct OpenAIDashboardFetcher {
             guard !cookieHeader.isEmpty else { return nil }
     
             do {
    -            let (data, response) = try await URLSession.shared.data(
    +            let (data, response) = try await ProviderHTTPClient.shared.data(
                     for: self.dashboardUsageAPIRequest(cookieHeader: cookieHeader))
                 let status = (response as? HTTPURLResponse)?.statusCode ?? -1
                 logger("usage api status=\(status)")
    @@ -793,7 +793,7 @@ public struct OpenAIDashboardFetcher {
     
             for url in endpoints {
                 do {
    -                let (data, response) = try await URLSession.shared.data(
    +                let (data, response) = try await ProviderHTTPClient.shared.data(
                         for: self.dashboardIdentityAPIRequest(url: url, cookieHeader: cookieHeader))
                     let status = (response as? HTTPURLResponse)?.statusCode ?? -1
                     logger("identity api \(url.path) status=\(status)")
    
  • Sources/CodexBarCore/ProviderHTTPClient.swift+46 2 modified
    @@ -171,7 +171,7 @@ public final class ProviderHTTPClient: ProviderHTTPTransport, @unchecked Sendabl
         private let session: URLSession
     
         public init(session: URLSession? = nil) {
    -        self.session = session ?? URLSession(configuration: Self.defaultConfiguration())
    +        self.session = session ?? Self.redirectGuardedSession()
         }
     
         static func defaultConfiguration() -> URLSessionConfiguration {
    @@ -189,7 +189,16 @@ public final class ProviderHTTPClient: ProviderHTTPTransport, @unchecked Sendabl
                 // XCTest URLProtocol.registerClass stubs only intercept URLSession.shared on macOS.
                 return .shared
             }
    -        return URLSession(configuration: self.defaultConfiguration())
    +        return self.redirectGuardedSession()
    +    }
    +
    +    static func redirectGuardedSession(
    +        configuration: URLSessionConfiguration = ProviderHTTPClient.defaultConfiguration()) -> URLSession
    +    {
    +        URLSession(
    +            configuration: configuration,
    +            delegate: ProviderHTTPRedirectGuardDelegate(),
    +            delegateQueue: nil)
         }
     
         private static var isRunningTests: Bool {
    @@ -207,3 +216,38 @@ public final class ProviderHTTPClient: ProviderHTTPTransport, @unchecked Sendabl
             try await self.session.data(for: request)
         }
     }
    +
    +final class ProviderHTTPRedirectGuardDelegate: NSObject, URLSessionTaskDelegate, @unchecked Sendable {
    +    func urlSession(
    +        _: URLSession,
    +        task: URLSessionTask,
    +        willPerformHTTPRedirection _: HTTPURLResponse,
    +        newRequest request: URLRequest,
    +        completionHandler: @escaping @Sendable (URLRequest?) -> Void)
    +    {
    +        completionHandler(Self.guardedRedirectRequest(originalURL: task.originalRequest?.url, redirectRequest: request))
    +    }
    +
    +    static func guardedRedirectRequest(originalURL: URL?, redirectRequest request: URLRequest) -> URLRequest? {
    +        guard let originalURL, let redirectedURL = request.url else { return nil }
    +        guard originalURL.scheme?.caseInsensitiveCompare("https") == .orderedSame else { return nil }
    +        guard redirectedURL.scheme?.caseInsensitiveCompare("https") == .orderedSame else { return nil }
    +        guard self.isSameOrigin(originalURL, redirectedURL) else { return nil }
    +        return request
    +    }
    +
    +    private static func isSameOrigin(_ lhs: URL, _ rhs: URL) -> Bool {
    +        lhs.scheme?.lowercased() == rhs.scheme?.lowercased()
    +            && lhs.host?.lowercased() == rhs.host?.lowercased()
    +            && self.normalizedPort(lhs) == self.normalizedPort(rhs)
    +    }
    +
    +    private static func normalizedPort(_ url: URL) -> Int? {
    +        if let port = url.port { return port }
    +        switch url.scheme?.lowercased() {
    +        case "http": return 80
    +        case "https": return 443
    +        default: return nil
    +        }
    +    }
    +}
    
  • Tests/CodexBarTests/ProviderHTTPClientTests.swift+65 0 modified
    @@ -129,6 +129,71 @@ struct ProviderHTTPClientTests {
             #expect(response.statusCode == 403)
             #expect(await script.requestCount() == 1)
         }
    +
    +    @Test
    +    func `redirect guard blocks cross origin redirects`() throws {
    +        var redirectRequest = try URLRequest(url: #require(URL(string: "https://attacker.example/capture")))
    +        redirectRequest.setValue("[REDACTED]", forHTTPHeaderField: "Cookie")
    +        redirectRequest.setValue("[REDACTED]", forHTTPHeaderField: "x-api-key")
    +
    +        let guarded = ProviderHTTPRedirectGuardDelegate.guardedRedirectRequest(
    +            originalURL: URL(string: "https://provider.example/usage"),
    +            redirectRequest: redirectRequest)
    +
    +        #expect(guarded == nil)
    +    }
    +
    +    @Test
    +    func `redirect guard blocks non HTTPS redirects`() throws {
    +        var redirectRequest = try URLRequest(url: #require(URL(string: "http://provider.example/capture")))
    +        redirectRequest.setValue("[REDACTED]", forHTTPHeaderField: "Cookie")
    +
    +        let guarded = ProviderHTTPRedirectGuardDelegate.guardedRedirectRequest(
    +            originalURL: URL(string: "https://provider.example/usage"),
    +            redirectRequest: redirectRequest)
    +
    +        #expect(guarded == nil)
    +    }
    +
    +    @Test
    +    func `redirect guard blocks redirects without an original URL`() throws {
    +        let redirectRequest = try URLRequest(url: #require(URL(string: "https://provider.example/usage/next")))
    +
    +        let guarded = ProviderHTTPRedirectGuardDelegate.guardedRedirectRequest(
    +            originalURL: nil,
    +            redirectRequest: redirectRequest)
    +
    +        #expect(guarded == nil)
    +    }
    +
    +    @Test
    +    func `redirect guard blocks port changes`() throws {
    +        let redirectRequest = try URLRequest(url: #require(URL(string: "https://provider.example:8443/usage")))
    +
    +        let guarded = ProviderHTTPRedirectGuardDelegate.guardedRedirectRequest(
    +            originalURL: URL(string: "https://provider.example/usage"),
    +            redirectRequest: redirectRequest)
    +
    +        #expect(guarded == nil)
    +    }
    +
    +    @Test
    +    func `redirect guard preserves same origin HTTPS requests`() throws {
    +        var redirectRequest = try URLRequest(url: #require(URL(string: "https://provider.example/usage/next")))
    +        redirectRequest.setValue("[REDACTED]", forHTTPHeaderField: "Cookie")
    +        redirectRequest.setValue("[REDACTED]", forHTTPHeaderField: "Authorization")
    +        redirectRequest.setValue("[REDACTED]", forHTTPHeaderField: "x-api-key")
    +        redirectRequest.setValue("application/json", forHTTPHeaderField: "Accept")
    +
    +        let guarded = try #require(ProviderHTTPRedirectGuardDelegate.guardedRedirectRequest(
    +            originalURL: URL(string: "https://provider.example/usage"),
    +            redirectRequest: redirectRequest))
    +
    +        #expect(guarded.value(forHTTPHeaderField: "Cookie") == "[REDACTED]")
    +        #expect(guarded.value(forHTTPHeaderField: "Authorization") == "[REDACTED]")
    +        #expect(guarded.value(forHTTPHeaderField: "x-api-key") == "[REDACTED]")
    +        #expect(guarded.value(forHTTPHeaderField: "Accept") == "application/json")
    +    }
     }
     
     extension ProviderHTTPRetryPolicy {
    

Vulnerability mechanics

Root cause

"Missing redirect-origin validation in the shared ProviderHTTPClient transport allowed credentialed requests to be forwarded to arbitrary hosts, ports, or plaintext HTTP destinations."

Attack vector

A network-adjacent attacker can intercept credentials by issuing a cross-origin or HTTP-downgrade redirect in response to a credentialed provider request. Because the shared `ProviderHTTPClient` transport previously followed redirects without origin checks, browser cookies, bearer tokens, and API keys carried in the `Cookie`, `Authorization`, and `x-api-key` headers would be forwarded to an attacker-controlled host, port, or plaintext HTTP destination [ref_id=1]. The attack requires the attacker to be on a network path where they can inject a redirect response (e.g., a compromised proxy or man-in-the-middle position).

Affected code

The vulnerability resides in `ProviderHTTPClient.swift` where the shared `ProviderHTTPClient` transport created a `URLSession` without a redirect delegate, allowing credentialed redirects to arbitrary hosts. The patch adds `ProviderHTTPRedirectGuardDelegate` and routes all shared-client sessions through it. Three OpenAI request paths in `OpenAIDashboardFetcher.swift` and `OpenAIDashboardBrowserCookieImporter.swift` were also migrated from `URLSession.shared` to `ProviderHTTPClient.shared` to inherit the guard [patch_id=5642280].

What the fix does

The patch introduces `ProviderHTTPRedirectGuardDelegate`, a `URLSessionTaskDelegate` that intercepts redirect callbacks and applies a same-origin check via `guardedRedirectRequest(originalURL:redirectRequest:)`. The guard rejects any redirect where the original URL is missing, the scheme is not HTTPS, the host changes, or the normalized port differs [patch_id=5642280]. The shared `ProviderHTTPClient` now creates its `URLSession` with this delegate, and three OpenAI cookie-bearing request paths were migrated from `URLSession.shared` to `ProviderHTTPClient.shared` to ensure they also benefit from the guard.

Preconditions

  • networkAttacker must be network-adjacent (e.g., on the same LAN or able to intercept/modify HTTP responses) to inject a redirect response.
  • configThe victim must be using CodexBar before 0.33.0 with a provider that sends credentials (cookies, bearer tokens, or API keys) via the shared ProviderHTTPClient transport.

Generated on Jun 11, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.