From 8af4494ccc90494ef09899b157733c7086aecf87 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 1 Sep 2026 03:41:01 -0700 Subject: [PATCH 1/2] fix(grok): preserve unknown usage for malformed billing tags --- CHANGELOG.md | 1 + .../Grok/GrokWebBillingFetcher.swift | 3 +- Tests/CodexBarTests/GrokZeroUsageTests.swift | 34 +++++++++++++++---- docs/grok.md | 2 ++ 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e55c0c115..c15e083b9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### Fixed - Keychain: limit repeated cache ACL validation and memory growth while preserving recovery after temporary failures or external repairs (#3300, #3301). Thanks @IgorKhramtsov! - Grok: restore 0% usage for a validated active billing period with an omitted usage scalar, preserving unknown usage for incomplete responses (#3261, #3325). Thanks @sf-jin-ku and @olddonkey! +- Grok: keep unknown billing usage from becoming 0% when a response contains invalid protobuf field numbers or overflowing varints. - Ollama: restore usage bars for monthly included credits and show matching history tabs while preserving legacy quota parsing and saved history (#3346). Thanks @haixing23! - Menu bar: keep status components and website links scoped to their provider when switching cached tabs, preventing Claude status from appearing under Grok or Codex (#3320). Thanks @gianpaj! - Usage & Spend: keep stalled or failed Codex catch-up paused until explicit Refresh, preventing background synchronization from restarting CPU-heavy scans (partial fix for #3316). Thanks @heyajulia! diff --git a/Sources/CodexBarCore/Providers/Grok/GrokWebBillingFetcher.swift b/Sources/CodexBarCore/Providers/Grok/GrokWebBillingFetcher.swift index c244d14b21..99d3575b5e 100644 --- a/Sources/CodexBarCore/Providers/Grok/GrokWebBillingFetcher.swift +++ b/Sources/CodexBarCore/Providers/Grok/GrokWebBillingFetcher.swift @@ -465,7 +465,7 @@ public enum GrokWebBillingFetcher { while index < bytes.count { let fieldStart = index - guard let key = Self.readVarint(bytes, index: &index), key != 0 else { + guard let key = Self.readVarint(bytes, index: &index), key >> 3 > 0, key >> 3 <= 536_870_911 else { scan.isComplete = false index = fieldStart + 1 continue @@ -540,6 +540,7 @@ public enum GrokWebBillingFetcher { while index < bytes.count, shift < 64 { let byte = bytes[index] index += 1 + if shift == 63, byte > 1 { return nil } value |= UInt64(byte & 0x7F) << shift if byte & 0x80 == 0 { return value diff --git a/Tests/CodexBarTests/GrokZeroUsageTests.swift b/Tests/CodexBarTests/GrokZeroUsageTests.swift index e13939ddf1..c8d81f98db 100644 --- a/Tests/CodexBarTests/GrokZeroUsageTests.swift +++ b/Tests/CodexBarTests/GrokZeroUsageTests.swift @@ -45,12 +45,7 @@ struct GrokZeroUsageTests { #expect(try await Self.resolve(parsed).snapshot.usedPercent == 0) } - @Test(arguments: [ - Data([0x00]), // Invalid field key. - Data([0x0D, 0x00]), // Truncated percentage. - Data([0x72, 0x04, 0x08]), // Truncated nested message. - Data([0x70, 0x80]), // Truncated varint. - ]) + @Test(arguments: Self.malformedSuffixes) func `malformed frames cannot turn unknown usage into zero`(suffix: Data) async throws { let parsed = try GrokWebBillingFetcher.parseGRPCWebResponse(Self.payload(suffix: suffix), now: Self.now) @@ -58,6 +53,17 @@ struct GrokZeroUsageTests { #expect(try await Self.resolve(parsed).snapshot.usedPercent == nil) } + @Test(arguments: [ + Self.fixed64Field(tag: [0xF9, 0xFF, 0xFF, 0xFF, 0x0F]), // Highest valid field number. + Data([0x70]) + Self.varint(.max), // A valid UInt64.max scalar. + ]) + func `valid unknown fields preserve implicit zero`(suffix: Data) async throws { + let parsed = try GrokWebBillingFetcher.parseGRPCWebResponse(Self.payload(suffix: suffix), now: Self.now) + + #expect(parsed.usedPercentIsImplicitZero) + #expect(try await Self.resolve(parsed).snapshot.usedPercent == 0) + } + @Test(arguments: [0, 3]) func `unknown period types cannot supply implicit zero`(periodType: UInt8) throws { #expect(throws: GrokWebBillingError.self) { @@ -81,6 +87,22 @@ struct GrokZeroUsageTests { } } + private static let malformedSuffixes: [Data] = [ + Data([0x00]), // Invalid field key. + Self.fixed64Field(tag: [0x01]), // Invalid field zero with a complete fixed64 value. + Data([0x02, 0x00]), // Invalid field zero with an empty length-delimited value. + Self.fixed64Field(tag: [0x81, 0x80, 0x80, 0x80, 0x10]), // Field number exceeds 29 bits. + // Overflowing varint must not truncate to a valid fixed64 tag. + Self.fixed64Field(tag: [0x89, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x02]), + Data([0x0D, 0x00]), // Truncated percentage. + Data([0x72, 0x04, 0x08]), // Truncated nested message. + Data([0x70, 0x80]), // Truncated varint. + ] + + private static func fixed64Field(tag: [UInt8]) -> Data { + Data(tag + [UInt8](repeating: 0, count: 8)) + } + private static let now = Date(timeIntervalSince1970: 1_788_000_000) private static let proxyReset = Date(timeIntervalSince1970: 1_900_000_000) private static let credentials = GrokCredentials( diff --git a/docs/grok.md b/docs/grok.md index 401234e0c4..dec37e814c 100644 --- a/docs/grok.md +++ b/docs/grok.md @@ -73,6 +73,8 @@ The grok.com billing gRPC-web endpoint remains a best-effort fallback. from wire-published percentages; a bare inferred zero, historical-only period, or malformed response cannot replace unknown proxy usage. Proxy reset and plan metadata remain authoritative when the zero is adopted. + Invalid protobuf field numbers and overflowing varints prevent that response + from qualifying as complete; valid unknown fields remain supported. Grok's public web client also reads its omitted proto3 scalar as zero, and its [billing descriptor](https://cdn.grok.com/_next/static/chunks/32g78bk5hhe1q.js) declares `credit_usage_percent` as an implicit-presence float (checked From c9832713f4901ed551722665efbc6326b8723c5d Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 1 Sep 2026 04:03:44 -0700 Subject: [PATCH 2/2] fix(grok): restrict billing recursion to known messages --- CHANGELOG.md | 2 +- .../Grok/GrokWebBillingFetcher.swift | 15 ++++- Tests/CodexBarTests/GrokZeroUsageTests.swift | 57 ++++++++++++++++++- docs/grok.md | 4 +- 4 files changed, 74 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c15e083b9b..f5798d4de3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ ### Fixed - Keychain: limit repeated cache ACL validation and memory growth while preserving recovery after temporary failures or external repairs (#3300, #3301). Thanks @IgorKhramtsov! - Grok: restore 0% usage for a validated active billing period with an omitted usage scalar, preserving unknown usage for incomplete responses (#3261, #3325). Thanks @sf-jin-ku and @olddonkey! -- Grok: keep unknown billing usage from becoming 0% when a response contains invalid protobuf field numbers or overflowing varints. +- Grok: keep malformed billing responses from turning unknown usage into 0%, while safely ignoring unknown byte fields (#3357). - Ollama: restore usage bars for monthly included credits and show matching history tabs while preserving legacy quota parsing and saved history (#3346). Thanks @haixing23! - Menu bar: keep status components and website links scoped to their provider when switching cached tabs, preventing Claude status from appearing under Grok or Codex (#3320). Thanks @gianpaj! - Usage & Spend: keep stalled or failed Codex catch-up paused until explicit Refresh, preventing background synchronization from restarting CPU-heavy scans (partial fix for #3316). Thanks @heyajulia! diff --git a/Sources/CodexBarCore/Providers/Grok/GrokWebBillingFetcher.swift b/Sources/CodexBarCore/Providers/Grok/GrokWebBillingFetcher.swift index 99d3575b5e..8c0d4582ae 100644 --- a/Sources/CodexBarCore/Providers/Grok/GrokWebBillingFetcher.swift +++ b/Sources/CodexBarCore/Providers/Grok/GrokWebBillingFetcher.swift @@ -498,7 +498,7 @@ public enum GrokWebBillingFetcher { } let start = index let end = index + Int(length) - if depth < 4 { + if depth < 4, Self.isKnownBillingMessage(path: fieldPath) { let nested = Self.scanProtobuf( Data(bytes[start.. Bool { + // The billing descriptor declares these messages; other length-delimited fields may be opaque bytes. + switch path { + case [1], + [1, 2], [1, 3], [1, 4], [1, 5], [1, 6], [1, 7], [1, 8], [1, 12], + [1, 6, 1], [1, 6, 2], [1, 6, 3], [1, 8, 2], [1, 8, 3], + [1, 6, 3, 2], [1, 6, 3, 3]: + true + default: + false + } + } + private static func readVarint(_ bytes: [UInt8], index: inout Int) -> UInt64? { var value: UInt64 = 0 var shift: UInt64 = 0 diff --git a/Tests/CodexBarTests/GrokZeroUsageTests.swift b/Tests/CodexBarTests/GrokZeroUsageTests.swift index c8d81f98db..2c6573f0cc 100644 --- a/Tests/CodexBarTests/GrokZeroUsageTests.swift +++ b/Tests/CodexBarTests/GrokZeroUsageTests.swift @@ -64,6 +64,42 @@ struct GrokZeroUsageTests { #expect(try await Self.resolve(parsed).snapshot.usedPercent == 0) } + @Test(arguments: [false, true], Self.opaquePayloads) + func `unknown byte fields cannot invalidate or invent billing values`( + atRoot: Bool, bytes: Data) async throws + { + let opaqueField = Self.message(path: [14], contents: bytes) + let payload = atRoot ? Self.payload() + opaqueField : Self.payload(suffix: opaqueField) + let parsed = try GrokWebBillingFetcher.parseGRPCWebResponse(payload, now: Self.now) + + #expect(parsed.usedPercent == 0) + #expect(parsed.usedPercentIsImplicitZero) + #expect(!parsed.usedPercentIsWirePublished) + #expect(parsed.resetsAt == Date(timeIntervalSince1970: 1_789_000_000)) + #expect(try await Self.resolve(parsed).snapshot.usedPercent == 0) + } + + @Test(arguments: Self.billingMessagePaths) + func `malformed known messages still prevent implicit zero`(path: [UInt64]) async throws { + let malformedMessage = Self.message(path: path, contents: Self.fixed64Field(tag: [0x01])) + let parsed = try GrokWebBillingFetcher.parseGRPCWebResponse( + Self.payload() + malformedMessage, now: Self.now) + + #expect(!parsed.usedPercentIsImplicitZero) + #expect(try await Self.resolve(parsed).snapshot.usedPercent == nil) + } + + @Test + func `historical period timestamps remain readable without becoming current usage`() throws { + let timestamp = Data([0x08]) + Self.varint(1_789_000_000) + let payload = Self.message(path: [1, 6, 3, 3], contents: timestamp) + let parsed = try GrokWebBillingFetcher.parseGRPCWebResponse(payload, now: Self.now) + + #expect(parsed.resetsAt == Date(timeIntervalSince1970: 1_789_000_000)) + #expect(!parsed.usedPercentIsImplicitZero) + #expect(!parsed.usedPercentIsWirePublished) + } + @Test(arguments: [0, 3]) func `unknown period types cannot supply implicit zero`(periodType: UInt8) throws { #expect(throws: GrokWebBillingError.self) { @@ -95,10 +131,29 @@ struct GrokZeroUsageTests { // Overflowing varint must not truncate to a valid fixed64 tag. Self.fixed64Field(tag: [0x89, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x02]), Data([0x0D, 0x00]), // Truncated percentage. - Data([0x72, 0x04, 0x08]), // Truncated nested message. + Data([0x72, 0x04, 0x08]), // Truncated unknown length-delimited field. Data([0x70, 0x80]), // Truncated varint. ] + private static let opaquePayloads: [Data] = [ + Self.fixed64Field(tag: [0x01]), // Valid opaque bytes, not a valid protobuf message. + Data([0x0D, 0x00, 0x00, 0x14, 0x42]), // Looks like a published 37% usage field. + Data([0x08]) + Self.varint(1_788_500_000), // Looks like an earlier future reset. + ] + + private static let billingMessagePaths: [[UInt64]] = [ + [1], + [1, 2], [1, 3], [1, 4], [1, 5], [1, 6], [1, 7], [1, 8], [1, 12], + [1, 6, 1], [1, 6, 2], [1, 6, 3], [1, 8, 2], [1, 8, 3], + [1, 6, 3, 2], [1, 6, 3, 3], + ] + + private static func message(path: [UInt64], contents: Data) -> Data { + path.reversed().reduce(contents) { payload, field in + Self.varint((field << 3) | 2) + Self.varint(UInt64(payload.count)) + payload + } + } + private static func fixed64Field(tag: [UInt8]) -> Data { Data(tag + [UInt8](repeating: 0, count: 8)) } diff --git a/docs/grok.md b/docs/grok.md index dec37e814c..6d4b6dcf34 100644 --- a/docs/grok.md +++ b/docs/grok.md @@ -74,7 +74,9 @@ The grok.com billing gRPC-web endpoint remains a best-effort fallback. or malformed response cannot replace unknown proxy usage. Proxy reset and plan metadata remain authoritative when the zero is adopted. Invalid protobuf field numbers and overflowing varints prevent that response - from qualifying as complete; valid unknown fields remain supported. + from qualifying as complete. Only schema-declared messages are recursively + decoded; valid unknown length-delimited fields are skipped as opaque data, + so their contents cannot invalidate the response or invent usage/reset values. Grok's public web client also reads its omitted proto3 scalar as zero, and its [billing descriptor](https://cdn.grok.com/_next/static/chunks/32g78bk5hhe1q.js) declares `credit_usage_percent` as an implicit-presence float (checked