Skip to content
Draft
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
13 changes: 11 additions & 2 deletions src/codex/auth-api/reset-credit-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,9 @@ export function createResetCreditWhamClient(config: OcxConfig, accountId: string
signal: AbortSignal.timeout(10_000),
});
if (!resp.ok) { await resp.body?.cancel().catch(() => {}); throw new Error(`upstream ${resp.status}`); }
return safeResetCreditConsumeDto(await resp.json());
const parsed = await readResetCreditJson(resp, AbortSignal.timeout(10_000));
if (!parsed.ok) throw new Error("invalid upstream reset-credit consume response");
return safeResetCreditConsumeDto(parsed.value);
}),
};
}
Expand Down Expand Up @@ -383,7 +385,14 @@ export async function consumeResetCredits(config: OcxConfig, accountId: string,
if (identity) markManualResetCreditOperationAmbiguous(identity);
return jsonResponse({ error: `Upstream error ${resp.status}` }, resp.status);
}
const result = safeResetCreditConsumeDto(await resp.json());
const consumed = await readResetCreditJson(resp, AbortSignal.timeout(10_000));
if (!consumed.ok) {
// The spend may already have landed upstream and its outcome code is unreadable,
// so this id must never come back as a new operation.
if (identity) markManualResetCreditOperationAmbiguous(identity);
return jsonResponse({ error: "Invalid upstream reset-credit consume response" }, 502);
}
const result = safeResetCreditConsumeDto(consumed.value);
if (identity) {
// Narrow explicitly rather than casting: `safeResetCreditConsumeDto`
// normalizes anything unrecognized to "unknown", and settling that
Expand Down
4 changes: 4 additions & 0 deletions structure/providers/openai-tiers.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ A confirmed manual reset-credit consumption may immediately reconcile that accou
eligible pre-existing ordinary reset-derived cooldown after a complete, non-exhausted usage
observation started after the reset. Paused or reauthentication-required accounts and
cooldowns held by another in-flight probe remain excluded; their cooldowns are retained.
Confirmation requires a readable answer. Every reset-credit read, including the consume
response on both the manual and background paths, goes through the shared bounded-body
reader, so an upstream answer past that bound is unconfirmed rather than buffered whole.
An unconfirmed manual consume leaves its operation ambiguous and reconciles nothing.
Recovery owns the specific cooldown and authenticates
main and added Pool accounts through their respective credential contracts. Main usage
publication keeps the latest successfully published observation authoritative. Pool recovery
Expand Down
12 changes: 2 additions & 10 deletions tests/codex-integration/codex-auth-api.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { registerResetCreditConsumeValidationTests } from "../helpers/reset-credit-consume-validation";
import * as usageHistoryModule from "../../src/usage/log";
import { getAccountQuotaHistory } from "../../src/codex/quota";
import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
Expand Down Expand Up @@ -2871,16 +2872,7 @@ describe("codex-auth API", () => {
});
});

test("reset-credit consume rejects invalid account ids before credential lookup", async () => {
const req = new Request("http://localhost/api/codex-auth/reset-credits/consume", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ accountId: "../bad" }),
});
const resp = await handleCodexAuthAPI(req, new URL(req.url), makeConfig());
expect(resp!.status).toBe(400);
expect(await resp!.json()).toMatchObject({ error: "Invalid account id format" });
});
registerResetCreditConsumeValidationTests(makeConfig, seedPoolAccount);

test("reset-credit consume returns remaining from refreshed quota, not the consume payload", async () => {
const config = makeConfig();
Expand Down
63 changes: 63 additions & 0 deletions tests/helpers/reset-credit-consume-validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { expect, test } from "bun:test";
import { handleCodexAuthAPI } from "../../src/codex/auth-api";
import { BOUNDED_BODY_MAX_BYTES } from "../../src/lib/bounded-body";
import type { OcxConfig } from "../../src/types";

export function registerResetCreditConsumeValidationTests(
makeConfig: () => OcxConfig,
seedPoolAccount: (config: OcxConfig, options: { id: string; email: string }) => unknown,
): void {
test("reset-credit consume rejects invalid account ids before credential lookup", async () => {
const req = new Request("http://localhost/api/codex-auth/reset-credits/consume", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ accountId: "../bad" }),
});
const resp = await handleCodexAuthAPI(req, new URL(req.url), makeConfig());
expect(resp!.status).toBe(400);
expect(await resp!.json()).toMatchObject({ error: "Invalid account id format" });
});

test("reset-credit consume refuses an upstream body past the shared bound instead of buffering it", async () => {
const config = makeConfig();
seedPoolAccount(config, { id: "pool-oversized", email: "oversized@example.test" });
const originalFetch = globalThis.fetch;
let usageCalls = 0;
try {
globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
const url = String(input);
if (url.includes("/backend-api/wham/rate-limit-reset-credits/consume")) {
// A 200 with an unbounded body was read whole by resp.json() before anything
// looked at its size, unlike every other reset-credit read on this path.
const padding = "x".repeat(BOUNDED_BODY_MAX_BYTES * 2);
return new Response(`{"code":"reset","padding":"${padding}"}`, {
status: 200,
headers: { "content-type": "application/json" },
});
}
if (url.includes("/backend-api/wham/usage")) {
usageCalls += 1;
return Response.json({
rate_limit: { primary_window: { used_percent: 10, reset_at: 1782000000 } },
rate_limit_reset_credits: { available_count: 2 },
});
}
return originalFetch(input, init);
}) as typeof fetch;

const req = new Request("http://localhost/api/codex-auth/reset-credits/consume", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ accountId: "pool-oversized" }),
});
const resp = await handleCodexAuthAPI(req, new URL(req.url), config);
expect(resp!.status).toBe(502);
expect(await resp!.json()).toEqual({ error: "Invalid upstream reset-credit consume response" });
// The outcome is unconfirmed, so nothing downstream may treat the redeem as observed.
expect(usageCalls).toBe(0);
} finally {
globalThis.fetch = originalFetch;
}
});

}
Loading