Skip to content
Closed
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
27 changes: 8 additions & 19 deletions src/server/live.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,39 +80,29 @@ export const LIVE_CLIENT_PROTOCOL_HEADERS = [
*
* When `OCX_LIVE_FRAME_LOG` is set to a file path, every relayed sideband frame appends one
* JSONL record: direction, frame kind, byte length, and whether the payload contains U+FFFD.
* Privacy: full frame payloads are never written — only when U+FFFD is present, a short
* excerpt around the first replacement character is included so the corruption point can be
* attributed (upstream vs relay vs client). Disabled entirely when the env var is unset.
* Privacy: frame payloads are never written. The log is created with owner-only permissions and
* is disabled entirely when the env var is unset.
*/
export const LIVE_FRAME_LOG_ENV = "OCX_LIVE_FRAME_LOG";
const LIVE_FRAME_LOG_CONTEXT_CHARS = 24;

function fffdContext(text: string): string | undefined {
const idx = text.indexOf("\uFFFD");
if (idx < 0) return undefined;
const start = Math.max(0, idx - LIVE_FRAME_LOG_CONTEXT_CHARS);
const end = Math.min(text.length, idx + LIVE_FRAME_LOG_CONTEXT_CHARS);
return text.slice(start, end);
}

export function logLiveSidebandFrame(dir: "c2u" | "u2c", data: unknown): void {
const logPath = process.env[LIVE_FRAME_LOG_ENV];
if (!logPath) return;
try {
let kind: "text" | "binary" = "binary";
let bytes = 0;
let context: string | undefined;
let fffd = false;
if (typeof data === "string") {
kind = "text";
bytes = Buffer.byteLength(data);
context = fffdContext(data);
fffd = data.includes("\uFFFD");
} else if (data instanceof ArrayBuffer) {
bytes = data.byteLength;
context = fffdContext(new TextDecoder().decode(new Uint8Array(data)));
fffd = new TextDecoder().decode(new Uint8Array(data)).includes("\uFFFD");
} else if (ArrayBuffer.isView(data)) {
const view = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
bytes = data.byteLength;
context = fffdContext(new TextDecoder().decode(view));
fffd = new TextDecoder().decode(view).includes("\uFFFD");
} else {
return;
}
Expand All @@ -121,10 +111,9 @@ export function logLiveSidebandFrame(dir: "c2u" | "u2c", data: unknown): void {
dir,
kind,
bytes,
fffd: context !== undefined,
...(context !== undefined ? { context } : {}),
fffd,
};
appendFileSync(logPath, `${JSON.stringify(record)}\n`);
appendFileSync(logPath, `${JSON.stringify(record)}\n`, { mode: 0o600 });
} catch {
// Frame forensics must never break the relay.
}
Expand Down
10 changes: 6 additions & 4 deletions tests/server-live.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* so the proxy must relay it to an OpenAI upstream instead of the /v1/* JSON-404 guard.
*/
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
import { existsSync, mkdirSync, readFileSync, rmSync, statSync } from "node:fs";
import { join } from "node:path";
import { saveCodexAccountCredential } from "../src/codex/account-store";
import { clearAccountNeedsReauth, clearAccountQuota } from "../src/codex/auth-api";
Expand Down Expand Up @@ -760,7 +760,7 @@ test("sideband relay preserves multibyte UTF-8 frames byte-identically in both d
// The env-gated frame forensic log (OCX_LIVE_FRAME_LOG) records per-frame metadata and
// U+FFFD presence without writing full payloads — the attribution tool for multibyte
// transcript corruption reports.
test("sideband frame log records direction, kind, and U+FFFD context without full payloads", async () => {
test("sideband frame log records metadata without payload content", async () => {
const frameLogPath = join(TEST_DIR, "frames.jsonl");
process.env.OCX_LIVE_FRAME_LOG = frameLogPath;
const FFFD_TEXT = "가볍게 ��기핼봐요";
Expand Down Expand Up @@ -839,13 +839,15 @@ test("sideband frame log records direction, kind, and U+FFFD context without ful
expect(u2cFffd).toBeDefined();
expect(u2cFffd.kind).toBe("text");
expect(u2cFffd.bytes).toBeGreaterThan(0);
expect(u2cFffd.context).toContain("�");
expect(u2cFffd).not.toHaveProperty("context");
expect(c2uClean).toBeDefined();
expect(c2uClean.fffd).toBe(false);
// Full payloads must never be logged — only short FFFD context excerpts.
// No payload content is logged, including frames containing U+FFFD.
for (const line of lines) {
expect(JSON.stringify(line)).not.toContain("clean-frame");
expect(JSON.stringify(line)).not.toContain(FFFD_TEXT);
}
if (process.platform !== "win32") expect(statSync(frameLogPath).mode & 0o777).toBe(0o600);

client.close();
} finally {
Expand Down
Loading