Skip to content

Commit fc07764

Browse files
jancurnclaude
andauthored
Fix unbounded IPC buffer growth between CLI and bridge (#44)
Both sides of the bridge IPC accumulated socket data into an unbounded string buffer. A message without a newline delimiter would grow the buffer until OOM. Added a 10 MB ceiling that destroys the socket if exceeded. https://claude.ai/code/session_016foZUbDw68HevDoSakfUHF Co-authored-by: Claude <noreply@anthropic.com>
1 parent 12c8049 commit fc07764

File tree

3 files changed

+21
-0
lines changed

3 files changed

+21
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
- E2E tests now run under the Bun runtime (in addition to Node.js); use `./test/e2e/run.sh --runtime bun` or `npm run test:e2e:bun`
1414

1515
### Fixed
16+
- IPC buffer between CLI and bridge process is now capped at 10 MB; sockets are destroyed if the limit is exceeded, preventing unbounded memory growth
1617
- `validateOptions()` no longer includes subcommand-specific options (`--full`, `--x402`, `--proxy`, etc.) in global known-options list; misplaced flags now produce clear "Unknown option" errors instead of confusing Commander rejections
1718
- Sessions requiring authentication now correctly show as `expired` instead of `live` when the server rejects unauthenticated connections
1819
- Auth errors wrapped in `NetworkError` by bridge IPC are now detected on first health check, avoiding unnecessary bridge restart

src/bridge/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ setGlobalDispatcher(new EnvHttpProxyAgent());
4545
// Keepalive ping interval in milliseconds (30 seconds)
4646
const KEEPALIVE_INTERVAL_MS = 30_000;
4747

48+
// Maximum IPC buffer size (10 MB) — destroy socket if exceeded
49+
const MAX_BUFFER_SIZE = 10 * 1024 * 1024;
50+
4851
const logger = createLogger('bridge');
4952

5053
interface BridgeOptions {
@@ -789,6 +792,13 @@ class BridgeProcess {
789792
socket.on('data', (data) => {
790793
buffer += data.toString();
791794

795+
if (buffer.length > MAX_BUFFER_SIZE) {
796+
logger.error(`IPC buffer exceeded ${MAX_BUFFER_SIZE} bytes, destroying socket`);
797+
socket.destroy();
798+
this.connections.delete(socket);
799+
return;
800+
}
801+
792802
// Process complete JSON messages (newline-delimited)
793803
let newlineIndex: number;
794804
while ((newlineIndex = buffer.indexOf('\n')) !== -1) {

src/lib/bridge-client.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ const REQUEST_TIMEOUT = 3 * 60 * 1000;
2828
// Timeout for initial socket connection (5 seconds)
2929
const CONNECT_TIMEOUT = 5 * 1000;
3030

31+
// Maximum IPC buffer size (10 MB) — destroy socket if exceeded
32+
const MAX_BUFFER_SIZE = 10 * 1024 * 1024;
33+
3134
export class BridgeClient extends EventEmitter {
3235
private socket: Socket | null = null;
3336
private socketPath: string;
@@ -108,6 +111,13 @@ export class BridgeClient extends EventEmitter {
108111
this.socket.on('data', (data) => {
109112
this.buffer += data.toString();
110113

114+
if (this.buffer.length > MAX_BUFFER_SIZE) {
115+
logger.error(`IPC buffer exceeded ${MAX_BUFFER_SIZE} bytes, destroying socket`);
116+
this.socket?.destroy();
117+
this.cleanup();
118+
return;
119+
}
120+
111121
// Process complete JSON messages (newline-delimited)
112122
let newlineIndex: number;
113123
while ((newlineIndex = this.buffer.indexOf('\n')) !== -1) {

0 commit comments

Comments
 (0)