From bea8708b5da05546e384bd3ad7b8b786fc55fe58 Mon Sep 17 00:00:00 2001 From: Chinesezjc Date: Sun, 2 Aug 2026 19:42:50 +0800 Subject: [PATCH] fix(code-runtime-python): reject a non-integer maxLogBytes/maxValueBytes at load MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The child reads these byte budgets through int(...), which silently floors a float, so maxLogBytes: 3.5 would truncate at 3 bytes child-side while the host meters and marks at 3.5 — the two sides enforcing different public config. Gate them to integers at load, as the worker backend does; correct the stale comment that claimed the int()-truncated caps needed no gate. Adds a regression test. --- .../code-runtime/code-runtime-python/src/index.ts | 14 ++++++++++++-- .../code-runtime-python/tests/runtime.spec.ts | 12 ++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/code-runtime/code-runtime-python/src/index.ts b/packages/code-runtime/code-runtime-python/src/index.ts index 5a174929dd..879df1d855 100644 --- a/packages/code-runtime/code-runtime-python/src/index.ts +++ b/packages/code-runtime/code-runtime-python/src/index.ts @@ -450,8 +450,10 @@ export class PythonCodeRuntime extends CodeRuntime { } // cpuSeconds crosses to the child's setrlimit(RLIMIT_CPU) raw; a float // raises TypeError inside every child (a late per-run failure). Reject it - // at load. Other numeric caps are consumed as numbers host-side or - // int()-truncated in the bootstrap, so they need no integer gate. + // at load. maxLogBytes/maxValueBytes get their own integer gate below (the + // child int()-truncates them, so a float would diverge from the host); + // maxWallMs/graceMs/addressSpaceMb are consumed as numbers where a fraction + // is harmless. if (!Number.isInteger(this.config.cpuSeconds)) { throw new Error(`dsh-code-runtime-python: config.cpuSeconds must be a positive integer, got ${String(this.config.cpuSeconds)}`) } @@ -507,6 +509,14 @@ export class PythonCodeRuntime extends CodeRuntime { // is already inside the charge and must not be multiplied in again. The // admissible cap is therefore `ceiling - envelope`. for (const key of ['maxLogBytes', 'maxValueBytes'] as const) { + // Require an integer: the child reads these budgets through `int(...)`, + // which silently floors a float, so `maxLogBytes: 3.5` would truncate at 3 + // bytes child-side while the host meters and marks at 3.5 — the two sides + // enforcing different public config. Reject the float at load, as the + // worker backend does for its byte budgets. + if (!Number.isInteger(this.config[key])) { + throw new Error(`dsh-code-runtime-python: config.${key} must be a positive integer (the child reads it as an int, so a float diverges from the host), got ${String(this.config[key])}`) + } const limit = FRAME_CEILING_BYTES - FRAME_ENVELOPE_BYTES if (this.config[key] > limit) { throw new Error(`dsh-code-runtime-python: config.${key} must not exceed ${limit} (a payload that large cannot cross the ${FRAME_CEILING_BYTES}-byte fd-3 frame ceiling, so the run would fail as worker-exit rather than output-limit), got ${String(this.config[key])}`) diff --git a/packages/code-runtime/code-runtime-python/tests/runtime.spec.ts b/packages/code-runtime/code-runtime-python/tests/runtime.spec.ts index eec187a5a0..7c81c1f429 100644 --- a/packages/code-runtime/code-runtime-python/tests/runtime.spec.ts +++ b/packages/code-runtime/code-runtime-python/tests/runtime.spec.ts @@ -68,6 +68,18 @@ describe('PythonCodeRuntime — seam descriptors and misuse', () => { .rejects.toThrow(/cpuSeconds must be a positive integer, got 1.5/) }) + it('rejects a non-integer byte budget at load (the child int()-truncates it)', async () => { + // maxLogBytes/maxValueBytes cross to the child, which reads them through + // int(...): a float would floor there while the host meters the fraction, so + // the two sides would enforce different public config. Reject at load. + const ctxLog = new Context() + await expect(ctxLog.plugin(PythonCodeRuntime, { maxLogBytes: 3.5 })) + .rejects.toThrow(/maxLogBytes must be a positive integer/) + const ctxValue = new Context() + await expect(ctxValue.plugin(PythonCodeRuntime, { maxValueBytes: 1024.5 })) + .rejects.toThrow(/maxValueBytes must be a positive integer/) + }) + it('rejects finite numeric config that cannot cross as an exact rlimit integer', async () => { // `Number.isFinite` and `Number.isInteger` both admit values that cannot // round-trip. `addressSpaceMb: 1e308` overflows to `Infinity` once multiplied