Add container output size limiting to prevent memory issues (#18)
* Fix potential memory DoS via unbounded container output Add CONTAINER_MAX_OUTPUT_SIZE (default 10MB) to limit accumulated stdout/stderr from container processes. Without this limit, a malicious or buggy container could emit huge output leading to host memory exhaustion. Changes: - Add configurable CONTAINER_MAX_OUTPUT_SIZE in config.ts - Implement size-limited output buffering in runContainerAgent - Log warnings when truncation occurs - Include truncation status in container logs https://claude.ai/code/session_01TjVDwwaGwbcFDdmrFF2y8B * Update package-lock.json https://claude.ai/code/session_01TjVDwwaGwbcFDdmrFF2y8B --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@ export const MAIN_GROUP_FOLDER = 'main';
|
||||
|
||||
export const CONTAINER_IMAGE = process.env.CONTAINER_IMAGE || 'nanoclaw-agent:latest';
|
||||
export const CONTAINER_TIMEOUT = parseInt(process.env.CONTAINER_TIMEOUT || '300000', 10);
|
||||
export const CONTAINER_MAX_OUTPUT_SIZE = parseInt(process.env.CONTAINER_MAX_OUTPUT_SIZE || '10485760', 10); // 10MB default
|
||||
export const IPC_POLL_INTERVAL = 1000;
|
||||
|
||||
function escapeRegex(str: string): string {
|
||||
|
||||
@@ -11,6 +11,7 @@ import pino from 'pino';
|
||||
import {
|
||||
CONTAINER_IMAGE,
|
||||
CONTAINER_TIMEOUT,
|
||||
CONTAINER_MAX_OUTPUT_SIZE,
|
||||
GROUPS_DIR,
|
||||
DATA_DIR
|
||||
} from './config.js';
|
||||
@@ -216,20 +217,40 @@ export async function runContainerAgent(
|
||||
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
let stdoutTruncated = false;
|
||||
let stderrTruncated = false;
|
||||
|
||||
container.stdin.write(JSON.stringify(input));
|
||||
container.stdin.end();
|
||||
|
||||
container.stdout.on('data', (data) => {
|
||||
stdout += data.toString();
|
||||
if (stdoutTruncated) return;
|
||||
const chunk = data.toString();
|
||||
const remaining = CONTAINER_MAX_OUTPUT_SIZE - stdout.length;
|
||||
if (chunk.length > remaining) {
|
||||
stdout += chunk.slice(0, remaining);
|
||||
stdoutTruncated = true;
|
||||
logger.warn({ group: group.name, size: stdout.length }, 'Container stdout truncated due to size limit');
|
||||
} else {
|
||||
stdout += chunk;
|
||||
}
|
||||
});
|
||||
|
||||
container.stderr.on('data', (data) => {
|
||||
stderr += data.toString();
|
||||
const lines = data.toString().trim().split('\n');
|
||||
const chunk = data.toString();
|
||||
const lines = chunk.trim().split('\n');
|
||||
for (const line of lines) {
|
||||
if (line) logger.debug({ container: group.folder }, line);
|
||||
}
|
||||
if (stderrTruncated) return;
|
||||
const remaining = CONTAINER_MAX_OUTPUT_SIZE - stderr.length;
|
||||
if (chunk.length > remaining) {
|
||||
stderr += chunk.slice(0, remaining);
|
||||
stderrTruncated = true;
|
||||
logger.warn({ group: group.name, size: stderr.length }, 'Container stderr truncated due to size limit');
|
||||
} else {
|
||||
stderr += chunk;
|
||||
}
|
||||
});
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
@@ -257,6 +278,8 @@ export async function runContainerAgent(
|
||||
`IsMain: ${input.isMain}`,
|
||||
`Duration: ${duration}ms`,
|
||||
`Exit Code: ${code}`,
|
||||
`Stdout Truncated: ${stdoutTruncated}`,
|
||||
`Stderr Truncated: ${stderrTruncated}`,
|
||||
``
|
||||
];
|
||||
|
||||
@@ -271,10 +294,10 @@ export async function runContainerAgent(
|
||||
`=== Mounts ===`,
|
||||
mounts.map(m => `${m.hostPath} -> ${m.containerPath}${m.readonly ? ' (ro)' : ''}`).join('\n'),
|
||||
``,
|
||||
`=== Stderr ===`,
|
||||
`=== Stderr${stderrTruncated ? ' (TRUNCATED)' : ''} ===`,
|
||||
stderr,
|
||||
``,
|
||||
`=== Stdout ===`,
|
||||
`=== Stdout${stdoutTruncated ? ' (TRUNCATED)' : ''} ===`,
|
||||
stdout
|
||||
);
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user