Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
06ec553
Merge pull request #3678 from lidge-jun/codex/promote-main-243-01a07240
lidge-jun Sep 5, 2026
116c2ac
Merge commit '44ea9576e27c6be8be7f13a86e32bb349368c54d' into codex/re…
invalid-email-address Sep 6, 2026
07b48da
Merge pull request #3785 from lidge-jun/codex/release-244-main-07c0
lidge-jun Sep 6, 2026
bcdf559
chore(release): promote validated 2.45.0 to main [skip ci]
invalid-email-address Sep 6, 2026
b0900e5
chore(release): promote 2.45.0 to main (#3813)
lidge-jun Sep 6, 2026
3970601
chore(release): prepare 2.46.0 stable promotion
invalid-email-address Sep 7, 2026
bba6322
Merge pull request #3851 from lidge-jun/codex/release-246-main
lidge-jun Sep 7, 2026
3d53e5f
release: prepare 2.47.0 from audited regression candidate
invalid-email-address Sep 7, 2026
eda8754
Merge commit '48ab3e1e66cfa6e0c873de2fafa4540ac61d6c7d' into codex/re…
invalid-email-address Sep 7, 2026
f9e3515
Merge commit '57252193b' into codex/release-247-main
invalid-email-address Sep 7, 2026
6f71931
release: promote 2.47.0 to main (#3929)
lidge-jun Sep 7, 2026
9a60256
Merge commit 'd0737cff3' into codex/release-247-main-final
invalid-email-address Sep 7, 2026
9e9b1d3
Merge commit 'f48c322c0' into codex/release-247-main-final
invalid-email-address Sep 7, 2026
947bae9
Merge commit '0d7652ad1' into codex/release-247-main-final
invalid-email-address Sep 7, 2026
f7f890f
release: apply final roster correction to main (#3933)
lidge-jun Sep 7, 2026
544ebee
release: promote 2.48.0 to main
invalid-email-address Sep 8, 2026
d24ff57
release: set main channel version 2.48.0
invalid-email-address Sep 8, 2026
9a27e86
Merge pull request #4011 from lidge-jun/codex/release-248-main
lidge-jun Sep 8, 2026
62849df
release: promote verified 2.49.0 product tree to main
lidge-jun Sep 9, 2026
2f3f736
Merge pull request #4117 from lidge-jun/codex/release-249-main-01a08498
lidge-jun Sep 9, 2026
3a3de88
release: promote verified 2.50.0 product tree to main
lidge-jun Sep 10, 2026
2d4d7a2
Merge pull request #4195 from lidge-jun/codex/release-250-main-01a08a81
lidge-jun Sep 10, 2026
cf456e8
release: promote verified 2.51.0 product tree to main
lidge-jun Sep 11, 2026
c155cc7
Merge pull request #4271 from lidge-jun/codex/release-251-main
lidge-jun Sep 11, 2026
95c4875
release: promote verified 2.52.0 product tree to main
lidge-jun Sep 12, 2026
4d37c35
Merge pull request #4407 from lidge-jun/codex/release-2520-main
lidge-jun Sep 12, 2026
c2e5e77
fix(grok): bound protobuf length decoding
luvs01 Sep 14, 2026
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@bitkyc08/opencodex",
"version": "2.53.0",
"version": "2.52.0",
"description": "Universal provider proxy for OpenAI Codex & Claude Code — use any LLM with Codex CLI/App/SDK and Claude Code",
"type": "module",
"main": "./bin/package-main.mjs",
Expand Down
47 changes: 28 additions & 19 deletions src/grok/reset-coupons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,23 +56,34 @@ export function encodeVarint(value: number | bigint): Uint8Array {
* Decodes a protobuf varint from bytes at offset.
*/
export function decodeVarint(bytes: Uint8Array, offset: number): { value: number; bytesRead: number } {
if (!Number.isInteger(offset) || offset < 0 || offset >= bytes.length) {
throw new Error("Invalid protobuf varint offset");
}

let result = 0;
let shift = 0;
let count = 0;

while (offset + count < bytes.length) {
const b = bytes[offset + count];
count++;
result |= (b & 0x7f) << shift;
if ((b & 0x80) === 0) break;
shift += 7;
if (shift > 35) {
// For timestamps seconds, JS safe integers suffice.
break;
const part = (b & 0x7f) * (2 ** (7 * count));
if (!Number.isSafeInteger(part) || result > Number.MAX_SAFE_INTEGER - part) {
throw new Error("Protobuf varint exceeds JavaScript safe integer range");
}
result += part;
count++;
if ((b & 0x80) === 0) return { value: result, bytesRead: count };
}

return { value: result, bytesRead: count };
throw new Error("Truncated protobuf varint");
}

function decodeLength(bytes: Uint8Array, offset: number): { start: number; end: number } {
const { value: length, bytesRead } = decodeVarint(bytes, offset);
const start = offset + bytesRead;
if (!Number.isSafeInteger(length) || length < 0 || length > bytes.length - start) {
throw new Error("Invalid protobuf length-delimited field");
}
return { start, end: start + length };
}

/**
Expand Down Expand Up @@ -109,8 +120,8 @@ function decodeTimestamp(bytes: Uint8Array): number {
offset += bytesRead;
if (fieldNum === 1) seconds = value;
} else if (wireType === 2) {
const { value: len, bytesRead } = decodeVarint(bytes, offset);
offset += bytesRead + len;
const { end } = decodeLength(bytes, offset);
offset = end;
} else {
break;
}
Expand All @@ -135,10 +146,9 @@ function decodeConsumerResetToken(bytes: Uint8Array): GrokResetCoupon | null {
const wireType = tag & 0x7;

if (wireType === 2) {
const { value: len, bytesRead: lenRead } = decodeVarint(bytes, offset);
offset += lenRead;
const sub = bytes.subarray(offset, offset + len);
offset += len;
const { start, end } = decodeLength(bytes, offset);
const sub = bytes.subarray(start, end);
offset = end;

if (fieldNum === 10) {
tokenId = new TextDecoder("utf-8").decode(sub);
Expand Down Expand Up @@ -178,10 +188,9 @@ export function decodeGetRemainingResetsResponse(payload: Uint8Array): GrokReset
const wireType = tag & 0x7;

if (wireType === 2) {
const { value: len, bytesRead: lenRead } = decodeVarint(payload, offset);
offset += lenRead;
const sub = payload.subarray(offset, offset + len);
offset += len;
const { start, end } = decodeLength(payload, offset);
const sub = payload.subarray(start, end);
offset = end;

if (fieldNum === 10) {
const token = decodeConsumerResetToken(sub);
Expand Down
1 change: 1 addition & 0 deletions structure/runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ The server exposes `POST /api/stop` which restores native Codex config, stops an
| `src/providers/registry.ts` | Canonical provider presets for CLI, dashboard, OAuth, key providers, and metadata. |
| `src/providers/derive.ts` | Enrichment from provider presets into user config. |
| `src/oauth/` | OAuth providers, token storage, refresh, and auth-token resolution. The login callback listener binds a per-provider FIXED loopback port, so consecutive logins reuse the same number; every response it sends ends its connection (`Connection: close`, including non-callback paths such as a stray `/favicon.ico` 404). Stopping the listener does not close an established socket, so without that a pooled client would deliver the next login's callback to the retired flow, which rejects the unknown state as a CSRF mismatch while the live flow waits. |
| `src/grok/` | Grok-specific gRPC-Web framing and reset-coupon operations. Provider-controlled protobuf varints must fit JavaScript's safe integer range, and every length-delimited field is rejected unless its declared bytes remain inside the enclosing message. |
| `src/adapters/openai-responses.ts` | Native OpenAI/ChatGPT Responses passthrough. |
| `src/adapters/openai-chat.ts` | OpenAI-compatible Chat Completions bridge. |
| `src/adapters/anthropic.ts` | Anthropic Messages bridge. |
Expand Down
10 changes: 10 additions & 0 deletions tests/providers/xai/grok-reset-coupons.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import {
getGrokRemainingResets,
decodeGetRemainingResetsResponse,
decodeVarint,
encodeRedeemResetRequest,
encodeVarint,
GROK_GET_REMAINING_RESETS_ENDPOINT,
Expand Down Expand Up @@ -135,6 +136,15 @@ describe("grok reset coupons", () => {
expect(tokens[0].validityEnd).toBe(new Date(1728788400 * 1000).toISOString());
});

it("rejects oversized and truncated protobuf lengths without losing parser progress", () => {
const oversizedLength = new Uint8Array([0x52, 0x80, 0x80, 0x80, 0x80, 0x08]);
expect(() => decodeGetRemainingResetsResponse(oversizedLength)).toThrow(
"Invalid protobuf length-delimited field",
);

expect(() => decodeVarint(new Uint8Array([0x80]), 0)).toThrow("Truncated protobuf varint");
});

it("asserts auth headers and tokenAuth compatibility header on request", async () => {
let capturedHeaders: Headers | undefined;
let capturedBody: Uint8Array | undefined;
Expand Down
Loading