[GH-ISSUE #134] Return detached snapshots from process capture render/checkpoint buffers #80

Closed
opened 2026-10-15 18:20:55 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @morluto on GitHub (Jul 14, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/134

Problem

ProcessCapture assembly currently leaks internal mutable arrays from renderer/checkpoint internals to callers.

  • src/application/TerminalRenderer.ts returns this.#frames directly from frames().
  • src/application/ProcessCheckpoints.ts returns this.#captures directly from finish().

Both call sites expose live references, so caller-side mutation changes internal state used to build final capture artifacts.

Reproduction

import { TerminalRenderer } from './dist/application/TerminalRenderer.js';
import { ProcessCheckpoints } from './dist/application/ProcessCheckpoints.js';

// TerminalRenderer: mutation is shared through the returned array
const renderer = new TerminalRenderer({
  columns: 40,
  rows: 12,
  scrollback: 1000,
  maxFrames: 10,
  maxBytes: 10000,
  normalize: (value) => value,
});
await renderer.write('A', 0);
const frames1 = await renderer.frames();
frames1[0].cursor_x = 999;
const frames2 = await renderer.frames();
console.log('terminal shared?', frames2[0].cursor_x); // 999

// ProcessCheckpoints: same mutable alias pattern
const checkpoints = new ProcessCheckpoints(
  { checkpoints: [], limits: { file_bytes: 10_000, files: 10, filesystem_depth: 16 } },
  0,
  { files: [], truncated: false },
  undefined,
);
const cp1 = await checkpoints.finish({ files: [], truncated: false });
cp1.push({ name: 'tamper', at_ms: 0, files: [], effects: [], truncated: false });
const cp2 = await checkpoints.finish({ files: [], truncated: false });
console.log('checkpoint length', cp1.length, cp2.length); // 4 and 4 in current repro sequence

Expected

Callers should receive detached snapshots that cannot mutate producer-owned state.

Actual

Mutating returned data mutates live internal arrays that feed capture serialization.

Impact

An untrusted caller can tamper with process capture output after it is produced, undermining downstream evidence integrity assumptions.

Files

  • src/application/TerminalRenderer.ts
  • src/application/ProcessCheckpoints.ts
  • src/application/ProcessCaptureLifecycle.ts
Originally created by @morluto on GitHub (Jul 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/134 ### Problem `ProcessCapture` assembly currently leaks internal mutable arrays from renderer/checkpoint internals to callers. - `src/application/TerminalRenderer.ts` returns `this.#frames` directly from `frames()`. - `src/application/ProcessCheckpoints.ts` returns `this.#captures` directly from `finish()`. Both call sites expose live references, so caller-side mutation changes internal state used to build final capture artifacts. ### Reproduction ```ts import { TerminalRenderer } from './dist/application/TerminalRenderer.js'; import { ProcessCheckpoints } from './dist/application/ProcessCheckpoints.js'; // TerminalRenderer: mutation is shared through the returned array const renderer = new TerminalRenderer({ columns: 40, rows: 12, scrollback: 1000, maxFrames: 10, maxBytes: 10000, normalize: (value) => value, }); await renderer.write('A', 0); const frames1 = await renderer.frames(); frames1[0].cursor_x = 999; const frames2 = await renderer.frames(); console.log('terminal shared?', frames2[0].cursor_x); // 999 // ProcessCheckpoints: same mutable alias pattern const checkpoints = new ProcessCheckpoints( { checkpoints: [], limits: { file_bytes: 10_000, files: 10, filesystem_depth: 16 } }, 0, { files: [], truncated: false }, undefined, ); const cp1 = await checkpoints.finish({ files: [], truncated: false }); cp1.push({ name: 'tamper', at_ms: 0, files: [], effects: [], truncated: false }); const cp2 = await checkpoints.finish({ files: [], truncated: false }); console.log('checkpoint length', cp1.length, cp2.length); // 4 and 4 in current repro sequence ``` ### Expected Callers should receive detached snapshots that cannot mutate producer-owned state. ### Actual Mutating returned data mutates live internal arrays that feed capture serialization. ### Impact An untrusted caller can tamper with process capture output after it is produced, undermining downstream evidence integrity assumptions. ### Files - src/application/TerminalRenderer.ts - src/application/ProcessCheckpoints.ts - src/application/ProcessCaptureLifecycle.ts
bsakaguchi 2026-10-15 18:20:55 +00:00
  • closed this issue
  • added the
    bug
    label
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#80
No description provided.