Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- Cursor: present legacy request-based plans as one Requests quota with the raw used/limit count instead of unrelated token-based Auto/API bars (#1420, fixes #1418). Thanks @hhh2210!
- Cost usage: memoize Codex priority-turn trace metadata incrementally so warm refreshes scan only appended rows instead of rescanning large trace databases (#1404). Thanks @ProspectOre!
- Security: reject insecure or malformed MiniMax and Alibaba endpoint overrides while preserving valid custom HTTPS deployments (#1269). Thanks @Hinotoi-agent!
- Security: reject insecure or malformed OpenRouter, Codebuff, Groq, and ElevenLabs endpoint overrides before sending provider credentials (#1256). Thanks @Hinotoi-agent!

## 0.33.0 — 2026-06-11

Expand Down
13 changes: 10 additions & 3 deletions Sources/CodexBarCore/ProviderEndpointOverrideValidator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,14 @@ struct ProviderEndpointOverrideValidator: Sendable {
guard let url else { return nil }
guard let scheme = url.scheme?.lowercased(), scheme == "https" else { return nil }
guard url.user == nil, url.password == nil else { return nil }
guard url.host(percentEncoded: false) != nil else { return nil }
guard let decodedHost = url.host(percentEncoded: false)?.lowercased(),
!decodedHost.isEmpty,
!decodedHost.contains("%"),
decodedHost.rangeOfCharacter(from: .whitespacesAndNewlines) == nil,
decodedHost.rangeOfCharacter(from: .controlCharacters) == nil,
let encodedHost = url.host(percentEncoded: true)?.lowercased(),
Self.hostHasNoEncodedDelimiters(encodedHost, decodedHost: decodedHost, url: url)
else { return nil }
return url
}

Expand Down Expand Up @@ -102,7 +109,7 @@ struct ProviderEndpointOverrideValidator: Sendable {
decodedHost.rangeOfCharacter(from: .whitespacesAndNewlines) == nil,
decodedHost.rangeOfCharacter(from: .controlCharacters) == nil,
let encodedHost = url.host(percentEncoded: true)?.lowercased(),
self.hostHasNoEncodedDelimiters(encodedHost, decodedHost: decodedHost, url: url)
Self.hostHasNoEncodedDelimiters(encodedHost, decodedHost: decodedHost, url: url)
else { return nil }

switch policy {
Expand All @@ -118,7 +125,7 @@ struct ProviderEndpointOverrideValidator: Sendable {
}
}

private func hostHasNoEncodedDelimiters(_ encodedHost: String, decodedHost: String, url: URL) -> Bool {
private static func hostHasNoEncodedDelimiters(_ encodedHost: String, decodedHost: String, url: URL) -> Bool {
if decodedHost.contains(":") {
guard encodedHost == decodedHost,
let componentHost = URLComponents(url: url, resolvingAgainstBaseURL: false)?.host,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,17 @@ struct CodebuffAPIFetchStrategy: ProviderFetchStrategy {
}

/// Errors related to Codebuff settings.
public enum CodebuffSettingsError: LocalizedError, Sendable {
public enum CodebuffSettingsError: LocalizedError, Sendable, Equatable {
case missingToken
case invalidEndpointOverride(String)

public var errorDescription: String? {
switch self {
case .missingToken:
"Codebuff API token not configured. Set CODEBUFF_API_KEY or run `codebuff login` to " +
"populate ~/.config/manicode/credentials.json."
case let .invalidEndpointOverride(key):
"Codebuff endpoint override \(key) must use HTTPS or a bare host."
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,20 @@ public enum CodebuffSettingsReader {

/// Returns the API base URL, defaulting to the production endpoint.
public static func apiURL(environment: [String: String] = ProcessInfo.processInfo.environment) -> URL {
if let override = environment["CODEBUFF_API_URL"],
let url = URL(string: cleaned(override) ?? "")
{
return url
if let override = self.validAPIURL(environment: environment) {
return override
}
return URL(string: "https://www.codebuff.com")!
}

public static func validateEndpointOverrides(
environment: [String: String] = ProcessInfo.processInfo.environment) throws
{
guard let raw = self.cleaned(environment["CODEBUFF_API_URL"]) else { return }
guard ProviderEndpointOverrideValidator.normalizedHTTPSURL(from: raw) == nil else { return }
throw CodebuffSettingsError.invalidEndpointOverride("CODEBUFF_API_URL")
}

/// Returns the auth token from the local credentials file if present.
public static func authToken(
authFileURL: URL? = nil,
Expand Down Expand Up @@ -60,6 +66,11 @@ public enum CodebuffSettingsReader {
value = value.trimmingCharacters(in: .whitespacesAndNewlines)
return value.isEmpty ? nil : value
}

private static func validAPIURL(environment: [String: String]) -> URL? {
guard let raw = self.cleaned(environment["CODEBUFF_API_URL"]) else { return nil }
return ProviderEndpointOverrideValidator.normalizedHTTPSURL(from: raw)
}
}

private struct CredentialsFile: Decodable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public enum CodebuffUsageFetcher {
guard !trimmed.isEmpty else {
throw CodebuffUsageError.missingCredentials
}
try CodebuffSettingsReader.validateEndpointOverrides(environment: environment)

let baseURL = CodebuffSettingsReader.apiURL(environment: environment)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,20 @@ public enum ElevenLabsSettingsReader {
}

public static func apiURL(environment: [String: String] = ProcessInfo.processInfo.environment) -> URL {
if let override = self.cleaned(environment[self.apiURLEnvironmentKey]),
let url = URL(string: override)
{
return url
if let override = self.validAPIURL(environment: environment) {
return override
}
return URL(string: "https://api.elevenlabs.io")!
}

public static func validateEndpointOverrides(
environment: [String: String] = ProcessInfo.processInfo.environment) throws
{
guard let raw = self.cleaned(environment[self.apiURLEnvironmentKey]) else { return }
guard ProviderEndpointOverrideValidator.normalizedHTTPSURL(from: raw) == nil else { return }
throw ElevenLabsSettingsError.invalidEndpointOverride(self.apiURLEnvironmentKey)
}

static func cleaned(_ raw: String?) -> String? {
guard var value = raw?.trimmingCharacters(in: .whitespacesAndNewlines), !value.isEmpty else {
return nil
Expand All @@ -37,4 +43,20 @@ public enum ElevenLabsSettingsReader {
value = value.trimmingCharacters(in: .whitespacesAndNewlines)
return value.isEmpty ? nil : value
}

private static func validAPIURL(environment: [String: String]) -> URL? {
guard let raw = self.cleaned(environment[self.apiURLEnvironmentKey]) else { return nil }
return ProviderEndpointOverrideValidator.normalizedHTTPSURL(from: raw)
}
}

public enum ElevenLabsSettingsError: LocalizedError, Sendable, Equatable {
case invalidEndpointOverride(String)

public var errorDescription: String? {
switch self {
case let .invalidEndpointOverride(key):
"ElevenLabs endpoint override \(key) must use HTTPS or a bare host."
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ public struct ElevenLabsUsageFetcher: Sendable {
guard !trimmed.isEmpty else {
throw ElevenLabsUsageError.missingCredentials
}
try ElevenLabsSettingsReader.validateEndpointOverrides(environment: environment)

let url = Self.subscriptionURL(baseURL: ElevenLabsSettingsReader.apiURL(environment: environment))
var request = URLRequest(url: url)
Expand Down
30 changes: 26 additions & 4 deletions Sources/CodexBarCore/Providers/Groq/GroqSettingsReader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,20 @@ public enum GroqSettingsReader {
public static func apiURL(
environment: [String: String] = ProcessInfo.processInfo.environment) -> URL
{
if let raw = self.cleaned(environment[self.apiURLEnvironmentKey]),
let url = URL(string: raw)
{
return url
if let override = self.validAPIURL(environment: environment) {
return override
}
return URL(string: "https://api.groq.com/v1")!
}

public static func validateEndpointOverrides(
environment: [String: String] = ProcessInfo.processInfo.environment) throws
{
guard let raw = self.cleaned(environment[self.apiURLEnvironmentKey]) else { return }
guard ProviderEndpointOverrideValidator.normalizedHTTPSURL(from: raw) == nil else { return }
throw GroqSettingsError.invalidEndpointOverride(self.apiURLEnvironmentKey)
}

static func cleaned(_ raw: String?) -> String? {
guard var value = raw?.trimmingCharacters(in: .whitespacesAndNewlines), !value.isEmpty else {
return nil
Expand All @@ -33,4 +39,20 @@ public enum GroqSettingsReader {
value = value.trimmingCharacters(in: .whitespacesAndNewlines)
return value.isEmpty ? nil : value
}

private static func validAPIURL(environment: [String: String]) -> URL? {
guard let raw = self.cleaned(environment[self.apiURLEnvironmentKey]) else { return nil }
return ProviderEndpointOverrideValidator.normalizedHTTPSURL(from: raw)
}
}

public enum GroqSettingsError: LocalizedError, Sendable, Equatable {
case invalidEndpointOverride(String)

public var errorDescription: String? {
switch self {
case let .invalidEndpointOverride(key):
"Groq endpoint override \(key) must use HTTPS or a bare host."
}
}
}
1 change: 1 addition & 0 deletions Sources/CodexBarCore/Providers/Groq/GroqUsageFetcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ public struct GroqUsageFetcher: Sendable {
guard !apiKey.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
throw GroqUsageError.missingCredentials
}
try GroqSettingsReader.validateEndpointOverrides(environment: environment)
let baseURL = GroqSettingsReader.apiURL(environment: environment)
.appendingPathComponent("metrics")
.appendingPathComponent("prometheus")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,16 @@ struct OpenRouterAPIFetchStrategy: ProviderFetchStrategy {
}

/// Errors related to OpenRouter settings
public enum OpenRouterSettingsError: LocalizedError, Sendable {
public enum OpenRouterSettingsError: LocalizedError, Sendable, Equatable {
case missingToken
case invalidEndpointOverride(String)

public var errorDescription: String? {
switch self {
case .missingToken:
"OpenRouter API token not configured. Set OPENROUTER_API_KEY environment variable or configure in Settings."
case let .invalidEndpointOverride(key):
"OpenRouter endpoint override \(key) must use HTTPS or a bare host."
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,20 @@ public enum OpenRouterSettingsReader {

/// Returns the API URL, defaulting to production endpoint
public static func apiURL(environment: [String: String] = ProcessInfo.processInfo.environment) -> URL {
if let override = environment["OPENROUTER_API_URL"],
let url = URL(string: cleaned(override) ?? "")
{
return url
if let override = self.validAPIURL(environment: environment) {
return override
}
return URL(string: "https://openrouter.ai/api/v1")!
}

public static func validateEndpointOverrides(
environment: [String: String] = ProcessInfo.processInfo.environment) throws
{
guard let raw = self.cleaned(environment["OPENROUTER_API_URL"]) else { return }
guard ProviderEndpointOverrideValidator.normalizedHTTPSURL(from: raw) == nil else { return }
throw OpenRouterSettingsError.invalidEndpointOverride("OPENROUTER_API_URL")
}

static func cleaned(_ raw: String?) -> String? {
guard var value = raw?.trimmingCharacters(in: .whitespacesAndNewlines), !value.isEmpty else {
return nil
Expand All @@ -34,4 +40,9 @@ public enum OpenRouterSettingsReader {
value = value.trimmingCharacters(in: .whitespacesAndNewlines)
return value.isEmpty ? nil : value
}

private static func validAPIURL(environment: [String: String]) -> URL? {
guard let raw = self.cleaned(environment["OPENROUTER_API_URL"]) else { return nil }
return ProviderEndpointOverrideValidator.normalizedHTTPSURL(from: raw)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ public struct OpenRouterUsageFetcher: Sendable {
guard !apiKey.isEmpty else {
throw OpenRouterUsageError.invalidCredentials
}
try OpenRouterSettingsReader.validateEndpointOverrides(environment: environment)

let baseURL = OpenRouterSettingsReader.apiURL(environment: environment)
let creditsURL = baseURL.appendingPathComponent("credits")
Expand Down
Loading