diff --git a/.changeset/remove-output-size-limit.md b/.changeset/remove-output-size-limit.md new file mode 100644 index 00000000..2568c6fb --- /dev/null +++ b/.changeset/remove-output-size-limit.md @@ -0,0 +1,7 @@ +--- +'@cloudflare/sandbox': patch +--- + +Remove output size limit for command execution + +The 10MB output size limit that was intended to prevent OOM attacks has been removed. This limit was too restrictive for legitimate use cases like reading large media files. Developers are now trusted to manage their own resource usage and handle potential OOM situations. diff --git a/docs/SESSION_EXECUTION.md b/docs/SESSION_EXECUTION.md index 06bdeb22..55ca174a 100644 --- a/docs/SESSION_EXECUTION.md +++ b/docs/SESSION_EXECUTION.md @@ -73,7 +73,6 @@ mkfifo "$sp" "$ep" ## Error Handling and Limits - Invalid `cwd` (foreground): we write a prefixed stderr line (binary prefix) indicating the failure and return exit code `1`. -- Output size limit: large logs are rejected during parsing to protect memory (`MAX_OUTPUT_SIZE_BYTES`). - Timeouts: foreground commands can be configured to time out; an error is raised if the exit file does not appear in time. ## Why Two Patterns? @@ -83,7 +82,7 @@ mkfifo "$sp" "$ep" ## Testing Notes -- Foreground tests cover silent commands (`cd`, variable assignment), error scenarios, multiline output, and size limits. +- Foreground tests cover silent commands (`cd`, variable assignment), error scenarios, and multiline output. - Background/streaming tests cover concurrent output, stderr separation, and completion events. - The previous hang class was caused by FIFO open/close races in foreground on silent commands; process substitution removes this class entirely. diff --git a/package-lock.json b/package-lock.json index 4b4a715e..b36c2076 100644 --- a/package-lock.json +++ b/package-lock.json @@ -401,7 +401,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -423,7 +422,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -563,7 +561,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1509,8 +1506,7 @@ "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20251126.0.tgz", "integrity": "sha512-DSeI1Q7JYmh5/D/tw5eZCjrKY34v69rwj63hHt60nSQW5QLwWCbj/lLtNz9f2EPa+JCACwpLXHgCXfzJ29x66w==", "devOptional": true, - "license": "MIT OR Apache-2.0", - "peer": true + "license": "MIT OR Apache-2.0" }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", @@ -2824,7 +2820,6 @@ "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -4294,7 +4289,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -4310,7 +4304,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -4320,7 +4313,6 @@ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -4447,7 +4439,6 @@ "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/utils": "3.2.4", "pathe": "^2.0.3", @@ -4463,7 +4454,6 @@ "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/pretty-format": "3.2.4", "magic-string": "^0.30.17", @@ -4492,7 +4482,6 @@ "integrity": "sha512-hGISOaP18plkzbWEcP/QvtRW1xDXF2+96HbEX6byqQhAUbiS5oH6/9JwW+QsQCIYON2bI6QZBF+2PvOmrRZ9wA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/utils": "3.2.4", "fflate": "^0.8.2", @@ -4640,7 +4629,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -5386,7 +5374,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -5671,7 +5658,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", @@ -7606,7 +7592,6 @@ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "devOptional": true, "license": "MIT", - "peer": true, "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -7871,7 +7856,6 @@ "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", "devOptional": true, "license": "MPL-2.0", - "peer": true, "dependencies": { "detect-libc": "^2.0.3" }, @@ -7908,6 +7892,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -7928,6 +7913,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -7948,6 +7934,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -7968,6 +7955,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -7988,6 +7976,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8008,6 +7997,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8028,6 +8018,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8048,6 +8039,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8068,6 +8060,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8088,6 +8081,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8108,6 +8102,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8157,6 +8152,7 @@ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "license": "MIT", + "peer": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -9725,7 +9721,6 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -9942,7 +9937,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -9952,7 +9946,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -9964,7 +9957,8 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-katex": { "version": "3.1.0", @@ -10338,7 +10332,6 @@ "integrity": "sha512-ZRLgPlS91l4JztLYEZnmMcd3Umcla1hkXJgiEiR4HloRJBBoeaX8qogTu5Jfu36rRMVLndzqYv0h+M5gJAkUfg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@oxc-project/types": "=0.98.0", "@rolldown/pluginutils": "1.0.0-beta.51" @@ -11154,7 +11147,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -11354,7 +11346,6 @@ "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" @@ -11514,7 +11505,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11584,7 +11574,6 @@ "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz", "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", "license": "MIT", - "peer": true, "dependencies": { "pathe": "^2.0.3" } @@ -12026,7 +12015,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.4.tgz", "integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -12141,7 +12129,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -12174,7 +12161,6 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -12600,7 +12586,6 @@ "integrity": "sha512-Om5ns0Lyx/LKtYI04IV0bjIrkBgoFNg0p6urzr2asekJlfP18RqFzyqMFZKf0i9Gnjtz/JfAS/Ol6tjCe5JJsQ==", "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "bin": { "workerd": "bin/workerd" }, @@ -13364,7 +13349,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/packages/sandbox-container/src/config.ts b/packages/sandbox-container/src/config.ts index 7bc7eef1..9946c9a0 100644 --- a/packages/sandbox-container/src/config.ts +++ b/packages/sandbox-container/src/config.ts @@ -37,18 +37,6 @@ const COMMAND_TIMEOUT_MS = (() => { return val === 0 ? undefined : val; })(); -/** - * Maximum output size in bytes to prevent OOM attacks. - * This is a security measure, not a timeout. - * - * Default: 10MB - * Environment variable: MAX_OUTPUT_SIZE_BYTES - */ -const MAX_OUTPUT_SIZE_BYTES = parseInt( - process.env.MAX_OUTPUT_SIZE_BYTES || String(10 * 1024 * 1024), - 10 -); - /** * Delay between chunks when streaming output. * This debounces file system watch events for better performance. @@ -68,7 +56,6 @@ export const CONFIG = { INTERPRETER_SPAWN_TIMEOUT_MS, INTERPRETER_EXECUTION_TIMEOUT_MS, COMMAND_TIMEOUT_MS, - MAX_OUTPUT_SIZE_BYTES, STREAM_CHUNK_DELAY_MS, DEFAULT_CWD } as const; diff --git a/packages/sandbox-container/src/session.ts b/packages/sandbox-container/src/session.ts index 3889afdd..cc9328f2 100644 --- a/packages/sandbox-container/src/session.ts +++ b/packages/sandbox-container/src/session.ts @@ -58,9 +58,6 @@ export interface SessionOptions { /** Command timeout in milliseconds (overrides CONFIG.COMMAND_TIMEOUT_MS) */ commandTimeoutMs?: number; - /** Maximum output size in bytes (overrides CONFIG.MAX_OUTPUT_SIZE_BYTES) */ - maxOutputSizeBytes?: number; - /** Logger instance for structured logging (optional - uses no-op logger if not provided) */ logger?: Logger; } @@ -119,7 +116,6 @@ export class Session { private readonly id: string; private readonly options: SessionOptions; private readonly commandTimeoutMs: number | undefined; - private readonly maxOutputSizeBytes: number; private readonly logger: Logger; /** Map of running commands for tracking and killing */ private runningCommands = new Map(); @@ -129,8 +125,6 @@ export class Session { this.options = options; this.commandTimeoutMs = options.commandTimeoutMs ?? CONFIG.COMMAND_TIMEOUT_MS; - this.maxOutputSizeBytes = - options.maxOutputSizeBytes ?? CONFIG.MAX_OUTPUT_SIZE_BYTES; // Use provided logger or create no-op logger (for backward compatibility/tests) this.logger = options.logger ?? createNoOpLogger(); } @@ -959,13 +953,6 @@ export class Session { return { stdout: '', stderr: '' }; } - // Safety check: prevent OOM attacks - if (file.size > this.maxOutputSizeBytes) { - throw new Error( - `Output too large: ${file.size} bytes (max: ${this.maxOutputSizeBytes})` - ); - } - const content = await file.text(); const lines = content.split('\n'); diff --git a/packages/sandbox-container/tests/session.test.ts b/packages/sandbox-container/tests/session.test.ts index 2e98635a..ed9247fa 100644 --- a/packages/sandbox-container/tests/session.test.ts +++ b/packages/sandbox-container/tests/session.test.ts @@ -267,8 +267,8 @@ describe('Session', () => { ).toHaveLength(100); }); - it('should handle output within size limit', async () => { - // Generate ~5KB of output (well below the 10MB default) + it('should handle large output without size limits', async () => { + // Generate ~5KB of output (no longer limited) const result = await session.exec( 'yes "test line with some text" | head -n 500' ); @@ -280,28 +280,17 @@ describe('Session', () => { expect(lines.length).toBe(500); }); - it('should reject output that exceeds max size', async () => { - // Create session with 1KB limit - const smallSession = new Session({ - id: 'small-output-session', - cwd: testDir, - maxOutputSizeBytes: 1024 // 1KB - }); - await smallSession.initialize(); + it('should handle very large output without errors', async () => { + // Generate >1KB of output (~12KB) - should work without issues + const result = await session.exec( + 'yes "test line with text here" | head -n 1000' + ); - try { - // Try to generate >1KB of output (~12KB) - await smallSession.exec( - 'yes "test line with text here" | head -n 1000' - ); - expect.unreachable('Should have thrown an error for oversized output'); - } catch (error) { - expect(error).toBeInstanceOf(Error); - expect((error as Error).message).toContain('Output too large'); - expect((error as Error).message).toContain('1024'); - } finally { - await smallSession.destroy(); - } + expect(result.exitCode).toBe(0); + const lines = result.stdout + .split('\n') + .filter((l) => l === 'test line with text here'); + expect(lines.length).toBe(1000); }); it('should handle commands with special characters', async () => {