[GH-ISSUE #130] Return defensive copies from unknown-lookup APIs to prevent in-place state mutation #77

Closed
opened 2026-10-15 18:20:53 +00:00 by bsakaguchi · 1 comment
Owner

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

Problem

Residual unknown registries return internal object references from list and verify paths, allowing caller-side mutation to corrupt session state.

In src/application/UnknownRegistry.ts, listUnknowns() returns values from #heads without cloning (lines ~97-107), and verifyUnknownResolution() returns unknown directly (lines ~115-118). src/application/EvidenceLedger.ts has the same pattern in listUnknowns() (lines ~265-280) and verifyUnknownResolution() (lines ~296-299), and BinarySession forwards both directly.

Reproduction

import { UnknownRegistry } from './dist/application/UnknownRegistry.js';
const registry = new UnknownRegistry({ maxRecords: 10, maxRelationships: 10 });
const id = `ev_${'a'.repeat(64)}`;
const created = registry.recordUnknown({
  question: 'x', severity: 'high', domain: 'd',
  required_authority: 'analyst-inference', required_confidence: 'observed',
  required_environment: null,
  supporting_evidence_ids: [], contradicting_evidence_ids: [],
  recommended_probes: [], relationships: [], approved: true,
}, id);
if (!created.ok) throw new Error('create failed');

const listed = registry.listUnknowns().value;
const leaked = listed[0];
if (!leaked) throw new Error('missing unknown');

leaked.status = 'resolved';
const check = registry.verifyUnknownResolution(leaked.unknown_id);
console.log(check.ok ? check.value.valid : check.error);

Expected: verifyUnknownResolution() should remain false because stored status is open.

Actual: it becomes true because caller-mutated object is the same reference as internal state.

Impact

A user/agent that mutates results returned by unknowns / list_unknowns can silently corrupt internal state and bypass immutability expectations.

Files

  • src/application/UnknownRegistry.ts
  • src/application/EvidenceLedger.ts
  • src/application/BinarySession.ts
Originally created by @morluto on GitHub (Jul 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/130 ### Problem Residual unknown registries return internal object references from list and verify paths, allowing caller-side mutation to corrupt session state. In `src/application/UnknownRegistry.ts`, `listUnknowns()` returns values from `#heads` without cloning (lines ~97-107), and `verifyUnknownResolution()` returns `unknown` directly (lines ~115-118). `src/application/EvidenceLedger.ts` has the same pattern in `listUnknowns()` (lines ~265-280) and `verifyUnknownResolution()` (lines ~296-299), and `BinarySession` forwards both directly. ### Reproduction ```ts import { UnknownRegistry } from './dist/application/UnknownRegistry.js'; const registry = new UnknownRegistry({ maxRecords: 10, maxRelationships: 10 }); const id = `ev_${'a'.repeat(64)}`; const created = registry.recordUnknown({ question: 'x', severity: 'high', domain: 'd', required_authority: 'analyst-inference', required_confidence: 'observed', required_environment: null, supporting_evidence_ids: [], contradicting_evidence_ids: [], recommended_probes: [], relationships: [], approved: true, }, id); if (!created.ok) throw new Error('create failed'); const listed = registry.listUnknowns().value; const leaked = listed[0]; if (!leaked) throw new Error('missing unknown'); leaked.status = 'resolved'; const check = registry.verifyUnknownResolution(leaked.unknown_id); console.log(check.ok ? check.value.valid : check.error); ``` Expected: `verifyUnknownResolution()` should remain `false` because stored status is `open`. Actual: it becomes `true` because caller-mutated object is the same reference as internal state. ### Impact A user/agent that mutates results returned by `unknowns` / `list_unknowns` can silently corrupt internal state and bypass immutability expectations. ### Files - src/application/UnknownRegistry.ts - src/application/EvidenceLedger.ts - src/application/BinarySession.ts
bsakaguchi 2026-10-15 18:20:53 +00:00
  • closed this issue
  • added the
    bug
    label
Author
Owner

@morluto commented on GitHub (Jul 17, 2026):

Implemented on main. EvidenceLedger listUnknowns and verifyUnknownResolution return structuredClone values, preventing callers from mutating retained session state. The older standalone UnknownRegistry surface cited by this issue has also been retired.

<!-- gh-comment-id:5001294657 --> @morluto commented on GitHub (Jul 17, 2026): Implemented on main. EvidenceLedger listUnknowns and verifyUnknownResolution return structuredClone values, preventing callers from mutating retained session state. The older standalone UnknownRegistry surface cited by this issue has also been retired.
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#77
No description provided.