chore(proxy): add STRICT_HEADERS and RESPONSES_BETA override; minimal allowlist mode

This commit is contained in:
James Peter
2025-09-12 23:24:59 +10:00
parent ee7012e4fd
commit 1ddedb8b48
3 changed files with 46 additions and 15 deletions
+19 -1
View File
@@ -127,7 +127,7 @@ pub enum HistoryPersistence {
}
/// Collection of settings that are specific to the TUI.
#[derive(Deserialize, Debug, Clone, PartialEq, Default)]
#[derive(Deserialize, Debug, Clone, PartialEq)]
pub struct Tui {
/// Theme configuration for the TUI
#[serde(default)]
@@ -157,6 +157,24 @@ pub struct Tui {
pub alternate_screen: bool,
}
// Important: Provide a manual Default so that when no config file exists and we
// construct `Config` via `unwrap_or_default()`, we still honor the intended
// default of `alternate_screen = true`. Deriving `Default` would set booleans to
// `false`, which caused fresh installs (or a temporary CODEX_HOME) to start in
// standard-terminal mode until the user pressed Ctrl+T.
impl Default for Tui {
fn default() -> Self {
Self {
theme: ThemeConfig::default(),
highlight: HighlightConfig::default(),
show_reasoning: false,
stream: StreamConfig::default(),
spinner: SpinnerSelection::default(),
alternate_screen: true,
}
}
}
/// Streaming behavior configuration for the TUI.
#[derive(Deserialize, Debug, Clone, PartialEq)]
pub struct StreamConfig {
@@ -1595,10 +1595,6 @@ impl WidgetRef for ChatComposer {
spans.push(Span::from("Ctrl+D").style(key_hint_style));
spans.push(Span::from(" diff viewer").style(label_style));
}
// Screen toggle hint is always available when space permits
if !spans.is_empty() { spans.push(Span::from(" • ").style(label_style)); }
spans.push(Span::from("Ctrl+T").style(key_hint_style));
spans.push(Span::from(" screen").style(label_style));
// Always show help at the end of the command hints
if !spans.is_empty() { spans.push(Span::from(" • ").style(label_style)); }
spans.push(Span::from("Ctrl+H").style(key_hint_style));
+27 -10
View File
@@ -51,6 +51,8 @@ const ALLOWED = ['/v1/chat/completions', '/v1/responses'];
// IDEMPOTENCY_KEY=auto -> Add an Idempotency-Key header per request (uuid)
// LOG_ERROR_BODY=1 -> Log non-2xx response body (truncated)
// LOG_ERROR_BODY_BYTES=1024 -> Max bytes to log from error body
// STRICT_HEADERS=1 -> Rebuild upstream headers from a minimal allowlist
// RESPONSES_BETA="responses=experimental"|"responses=v1" (override beta header)
const EXIT_ON_5XX = process.env.EXIT_ON_5XX === '1' || false;
const READY_FILE = process.env.READY_FILE || '';
const LOG_DEST = (process.env.LOG_DEST || 'stdout').toLowerCase();
@@ -61,6 +63,8 @@ const STRIP_SESSION_ID = process.env.STRIP_SESSION_ID === '1' || false;
const IDEMPOTENCY_KEY = (process.env.IDEMPOTENCY_KEY || '').toLowerCase();
const LOG_ERROR_BODY = process.env.LOG_ERROR_BODY === '1' || false;
const LOG_ERROR_BODY_BYTES = Number(process.env.LOG_ERROR_BODY_BYTES || 2048);
const STRICT_HEADERS = process.env.STRICT_HEADERS === '1' || false;
const RESPONSES_BETA = process.env.RESPONSES_BETA || 'responses=experimental';
function outWrite(s) {
if (LOG_DEST === 'stderr') process.stderr.write(s + '\n');
@@ -108,22 +112,35 @@ const server = http.createServer((req, res) => {
req.on('end', () => {
const body = Buffer.concat(chunks);
const up = new URL(req.url, UPSTREAM);
const incoming = { ...req.headers };
if (!incoming['content-type']) incoming['content-type'] = 'application/json';
if (!incoming['accept']) incoming['accept'] = 'text/event-stream';
if (NO_GZIP) incoming['accept-encoding'] = 'identity';
incoming['authorization'] = `Bearer ${API_KEY}`; // replace with real key
if (up.pathname.startsWith('/v1/responses')) {
if (!incoming['openai-beta']) incoming['openai-beta'] = 'responses=experimental';
let incoming;
if (STRICT_HEADERS) {
incoming = {};
incoming['authorization'] = `Bearer ${API_KEY}`;
incoming['content-type'] = 'application/json';
incoming['accept'] = 'text/event-stream';
if (NO_GZIP) incoming['accept-encoding'] = 'identity';
if (up.pathname.startsWith('/v1/responses')) incoming['openai-beta'] = RESPONSES_BETA;
incoming['user-agent'] = 'code-proxy/1.0';
incoming['originator'] = 'codex_cli_rs';
if (IDEMPOTENCY_KEY === 'auto') incoming['idempotency-key'] = crypto.randomUUID();
} else {
incoming = { ...req.headers };
if (!incoming['content-type']) incoming['content-type'] = 'application/json';
if (!incoming['accept']) incoming['accept'] = 'text/event-stream';
if (NO_GZIP) incoming['accept-encoding'] = 'identity';
incoming['authorization'] = `Bearer ${API_KEY}`; // replace with real key
if (up.pathname.startsWith('/v1/responses')) {
if (!incoming['openai-beta']) incoming['openai-beta'] = RESPONSES_BETA;
}
if (!incoming['originator']) incoming['originator'] = 'codex_cli_rs';
if (STRIP_SESSION_ID && 'session_id' in incoming) delete incoming['session_id'];
if (IDEMPOTENCY_KEY === 'auto') incoming['idempotency-key'] = crypto.randomUUID();
}
if (!incoming['originator']) incoming['originator'] = 'codex_cli_rs';
if (STRIP_SESSION_ID && 'session_id' in incoming) delete incoming['session_id'];
delete incoming['host']; incoming['host'] = up.host;
incoming['connection'] = FORCE_CLOSE ? 'close' : 'keep-alive';
if (incoming['content-length']) incoming['content-length'] = String(body.length);
delete incoming['proxy-connection'];
delete incoming['proxy-authorization'];
if (IDEMPOTENCY_KEY === 'auto') incoming['idempotency-key'] = crypto.randomUUID();
log({ level: 'info', rid, phase: 'request', method: req.method, url: up.toString(), headers: redactHeaders(incoming), body_bytes: body.length });