[GH-ISSUE #3326] [Bug] Frida session status advertises read-only while retaining Evidence #1197

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

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

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

MCP or agent integration

Observed behavior

frida_session_status adds a complete status Evidence record to the MCP session while advertising readOnlyHint: true and declaring no mutatesSession effect. REA's documented meaning of read-only includes session state and explicitly treats additive Evidence recording as non-read-only. Callers receive an inaccurate effect declaration for an operation that changes their retained investigation history.

The executed MCP reproduction changed the session from zero Evidence records to one retained frida_session_status record. tools/list still labeled the operation read-only, and binary_session omitted session mutation. The native device was simulated; the retained Evidence and advertised metadata came from the production server and session ledger.

Reproduction

  1. Prepare a clean checkout and build the runtime with the locked dependencies. These are clean-checkout prerequisites; the reported execution used installed locked dependencies and prebuilt runtime artifacts.
git clone https://github.com/morluto/rea.git rea-status-reproduction
cd rea-status-reproduction
git checkout --detach 8dde6370d0809ad04cda3342d4e6f290f48aa063
npm ci
npm run build:cached
  1. Run the following complete inline reproduction from the repository root. It uses the production server, lifecycle manager, contracts, and Evidence ledger with a simulated native Frida device. It requires no Frida engine and executes no target program. It creates a valid paused session through start_frida_session and uses the returned session ID for the MCP status call, rather than calling an internal status helper.
node --input-type=module <<'JS'
import { Client, InMemoryTransport } from '@modelcontextprotocol/client';
import { createServer } from './dist/server/createServer.js';
import { createManagedBinarySession } from './dist/composition/binary.js';
import { FridaInstrumentationManager } from './dist/frida/FridaInstrumentationManager.js';
let detached = false;
const resumed = [];
const nativeSession = {
  pid: 512,
  detached: { connect() {}, disconnect() {} },
  isDetached: () => detached,
  async detach() { detached = true; },
};
const device = {
  id: 'local', name: 'Local fixture', type: 'local',
  async spawn() { return 512; },
  async attach() { return nativeSession; },
  async resume(pid) { resumed.push(pid); },
};
const manager = new FridaInstrumentationManager({
  deviceManager: { async getDeviceById() { return device; } },
});
const session = createManagedBinarySession();
const server = createServer({ kind: 'session', session }, {
  fridaInstrumentation: manager,
  environment: { PATH: process.env.PATH, REA_LOG_LEVEL: 'silent' },
});
const client = new Client({ name: 'contract-check', version: '1' });
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
try {
  await server.connect(serverTransport);
  await client.connect(clientTransport);
  const tools = (await client.listTools()).tools;
  const started = await client.callTool({
    name: 'start_frida_session',
    arguments: { mode: 'spawn', device_id: 'local', program: 'fixture' },
  });
  if (started.isError) throw new Error(JSON.stringify(started.content));
  const sessionId = started.structuredContent.session_id;
  const before = await client.callTool({ name: 'get_evidence_bundle', arguments: { detail: 'summary' } });
  console.log(JSON.stringify({
    readOnlyHint: tools.find(t => t.name === 'frida_session_status').annotations.readOnlyHint,
    recordsBeforeStatus: before.structuredContent.result.records.length,
  }));
  const status = await client.callTool({
    name: 'frida_session_status', arguments: { session_id: sessionId },
  });
  if (status.isError) throw new Error(JSON.stringify(status.content));
  const after = await client.callTool({ name: 'get_evidence_bundle', arguments: { detail: 'summary' } });
  console.log(JSON.stringify({
    statusSucceeded: true,
    retainedStatusRecords: after.structuredContent.result.records.filter(r => r.operation === 'frida_session_status').length,
  }));
  const inventory = await client.callTool({ name: 'binary_session', arguments: {} });
  const group = inventory.structuredContent.result.tool_availability.groups.find(g => g.names.includes('frida_session_status'));
  console.log(JSON.stringify({ declaresSessionMutation: group.effects.includes('mutatesSession') }));
  await client.callTool({ name: 'close_frida_session', arguments: { session_id: sessionId } });
} finally {
  await client.close();
  await server.close();
  await session.close();
}
JS
  1. Compare the advertised read-only annotation and session effects with the retained Evidence record. The observed output appears below.

Expected behavior

A successful status call that records Evidence must declare session mutation and advertise readOnlyHint: false. It should continue returning complete status Evidence and retaining that record for later discovery or export. Additive Evidence recording does not require a destructive classification.

docs/mcp-contracts.md:478 states that readOnlyHint includes session state and that a call recording additive Evidence is non-read-only even when it leaves the target unchanged. src/contracts/toolEffects.ts:4 explicitly includes additive Evidence recording in mutatesSession.

Impact and scope

Impact level P2, with a supported assessment. A supported status call changes retained investigation state despite advertising a read-only operation. The discrepancy affects both public discovery representations and prevents callers from classifying session writes accurately; the reproduction did not lose or corrupt Evidence.

Workaround status is unverified. A caller could treat this operation as session-mutating regardless of the advertisement, but no client's annotation-override mechanism was tested. No tool argument makes a successful status call avoid Evidence retention.

REA version

6.4.0 (main at 8dde6370d0)

Runtime and platform

Node.js v26.10.0 on macOS 27.0.1. Architecture was not recorded. The checkout's locked dependencies and prebuilt runtime were used. MCP used the locked client/server SDK over linked in-memory transports, with a simulated native Frida device.

Target and artifact context

A simulated local Frida device with PID 512 and program name fixture; a valid paused spawned session; complete status Evidence retained in the production MCP session ledger. No real target program or Frida engine was used.

Interface

MCP

Earliest failure stage

Transport or result projection

Relevant output or evidence

The executed MCP reproduction printed:

{"readOnlyHint":true,"recordsBeforeStatus":0}
{"statusSucceeded":true,"retainedStatusRecords":1}
{"declaresSessionMutation":false}

The reproduction was observed, locally reproduced, and complete. The focused contract tests for effect annotations, Frida inputs, and advertised examples all passed. Their checks do not compare successful status retention with the read-only declaration.

Typed outcome or recovery expected

Repeat the reproduction on a fresh session. The successful status call must still return and retain the complete status Evidence, while tools/list advertises readOnlyHint: false and the status entry in binary_session includes mutatesSession. Keep destructiveHint: false for ordinary additive status recording. A read-only bundle lookup should remain available to confirm the retained record without adding another status observation.

Analysis or suspected cause

Source-derived reasoning: src/contracts/toolEffects.ts:234 declares status with no mutation effects, while src/server/registerFridaTools.ts:209 generates status Evidence and records it before returning the successful reply. That differs from the read-only contract in docs/mcp-contracts.md:478 and the additive Evidence definition in src/contracts/toolEffects.ts:4. The executed MCP reproduction observed the retained record and the contrary discovery declarations together.

Additional context

  • #968 tracks broad Frida adapter scope and effect-aware contracts; it does not report successful status calls retaining Evidence while advertising read-only behavior.
  • #101, closed as completed, concerns dynamic capability and policy-aware discovery; it does not identify this status-retention mismatch.
  • #2213, closed as completed, concerns missing argument details on rejected Frida calls; this report concerns a successful status call with a valid session ID.
  • #2544, closed as completed, concerns successful script-load Evidence after cancellation; this report concerns successful status Evidence being misclassified as read-only.
  • #3184 concerns CLI attach silently ignoring spawn arguments; this report concerns MCP status mutation metadata.
Originally created by @coygeek on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3326 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area MCP or agent integration ### Observed behavior `frida_session_status` adds a complete status Evidence record to the MCP session while advertising `readOnlyHint: true` and declaring no `mutatesSession` effect. REA's documented meaning of read-only includes session state and explicitly treats additive Evidence recording as non-read-only. Callers receive an inaccurate effect declaration for an operation that changes their retained investigation history. The executed MCP reproduction changed the session from zero Evidence records to one retained `frida_session_status` record. `tools/list` still labeled the operation read-only, and `binary_session` omitted session mutation. The native device was simulated; the retained Evidence and advertised metadata came from the production server and session ledger. ### Reproduction 1. Prepare a clean checkout and build the runtime with the locked dependencies. These are clean-checkout prerequisites; the reported execution used installed locked dependencies and prebuilt runtime artifacts. ```sh git clone https://github.com/morluto/rea.git rea-status-reproduction cd rea-status-reproduction git checkout --detach 8dde6370d0809ad04cda3342d4e6f290f48aa063 npm ci npm run build:cached ``` 2. Run the following complete inline reproduction from the repository root. It uses the production server, lifecycle manager, contracts, and Evidence ledger with a simulated native Frida device. It requires no Frida engine and executes no target program. It creates a valid paused session through `start_frida_session` and uses the returned session ID for the MCP status call, rather than calling an internal status helper. ```bash node --input-type=module <<'JS' import { Client, InMemoryTransport } from '@modelcontextprotocol/client'; import { createServer } from './dist/server/createServer.js'; import { createManagedBinarySession } from './dist/composition/binary.js'; import { FridaInstrumentationManager } from './dist/frida/FridaInstrumentationManager.js'; let detached = false; const resumed = []; const nativeSession = { pid: 512, detached: { connect() {}, disconnect() {} }, isDetached: () => detached, async detach() { detached = true; }, }; const device = { id: 'local', name: 'Local fixture', type: 'local', async spawn() { return 512; }, async attach() { return nativeSession; }, async resume(pid) { resumed.push(pid); }, }; const manager = new FridaInstrumentationManager({ deviceManager: { async getDeviceById() { return device; } }, }); const session = createManagedBinarySession(); const server = createServer({ kind: 'session', session }, { fridaInstrumentation: manager, environment: { PATH: process.env.PATH, REA_LOG_LEVEL: 'silent' }, }); const client = new Client({ name: 'contract-check', version: '1' }); const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); try { await server.connect(serverTransport); await client.connect(clientTransport); const tools = (await client.listTools()).tools; const started = await client.callTool({ name: 'start_frida_session', arguments: { mode: 'spawn', device_id: 'local', program: 'fixture' }, }); if (started.isError) throw new Error(JSON.stringify(started.content)); const sessionId = started.structuredContent.session_id; const before = await client.callTool({ name: 'get_evidence_bundle', arguments: { detail: 'summary' } }); console.log(JSON.stringify({ readOnlyHint: tools.find(t => t.name === 'frida_session_status').annotations.readOnlyHint, recordsBeforeStatus: before.structuredContent.result.records.length, })); const status = await client.callTool({ name: 'frida_session_status', arguments: { session_id: sessionId }, }); if (status.isError) throw new Error(JSON.stringify(status.content)); const after = await client.callTool({ name: 'get_evidence_bundle', arguments: { detail: 'summary' } }); console.log(JSON.stringify({ statusSucceeded: true, retainedStatusRecords: after.structuredContent.result.records.filter(r => r.operation === 'frida_session_status').length, })); const inventory = await client.callTool({ name: 'binary_session', arguments: {} }); const group = inventory.structuredContent.result.tool_availability.groups.find(g => g.names.includes('frida_session_status')); console.log(JSON.stringify({ declaresSessionMutation: group.effects.includes('mutatesSession') })); await client.callTool({ name: 'close_frida_session', arguments: { session_id: sessionId } }); } finally { await client.close(); await server.close(); await session.close(); } JS ``` 3. Compare the advertised read-only annotation and session effects with the retained Evidence record. The observed output appears below. ### Expected behavior A successful status call that records Evidence must declare session mutation and advertise `readOnlyHint: false`. It should continue returning complete status Evidence and retaining that record for later discovery or export. Additive Evidence recording does not require a destructive classification. `docs/mcp-contracts.md:478` states that `readOnlyHint` includes session state and that a call recording additive Evidence is non-read-only even when it leaves the target unchanged. `src/contracts/toolEffects.ts:4` explicitly includes additive Evidence recording in `mutatesSession`. ### Impact and scope Impact level P2, with a supported assessment. A supported status call changes retained investigation state despite advertising a read-only operation. The discrepancy affects both public discovery representations and prevents callers from classifying session writes accurately; the reproduction did not lose or corrupt Evidence. Workaround status is unverified. A caller could treat this operation as session-mutating regardless of the advertisement, but no client's annotation-override mechanism was tested. No tool argument makes a successful status call avoid Evidence retention. ### REA version 6.4.0 (main at 8dde6370d0809ad04cda3342d4e6f290f48aa063) ### Runtime and platform Node.js v26.10.0 on macOS 27.0.1. Architecture was not recorded. The checkout's locked dependencies and prebuilt runtime were used. MCP used the locked client/server SDK over linked in-memory transports, with a simulated native Frida device. ### Target and artifact context A simulated local Frida device with PID 512 and program name `fixture`; a valid paused spawned session; complete status Evidence retained in the production MCP session ledger. No real target program or Frida engine was used. ### Interface MCP ### Earliest failure stage Transport or result projection ### Relevant output or evidence The executed MCP reproduction printed: ```text {"readOnlyHint":true,"recordsBeforeStatus":0} {"statusSucceeded":true,"retainedStatusRecords":1} {"declaresSessionMutation":false} ``` The reproduction was observed, locally reproduced, and complete. The focused contract tests for effect annotations, Frida inputs, and advertised examples all passed. Their checks do not compare successful status retention with the read-only declaration. ### Typed outcome or recovery expected Repeat the reproduction on a fresh session. The successful status call must still return and retain the complete status Evidence, while `tools/list` advertises `readOnlyHint: false` and the status entry in `binary_session` includes `mutatesSession`. Keep `destructiveHint: false` for ordinary additive status recording. A read-only bundle lookup should remain available to confirm the retained record without adding another status observation. ### Analysis or suspected cause Source-derived reasoning: `src/contracts/toolEffects.ts:234` declares status with no mutation effects, while `src/server/registerFridaTools.ts:209` generates status Evidence and records it before returning the successful reply. That differs from the read-only contract in `docs/mcp-contracts.md:478` and the additive Evidence definition in `src/contracts/toolEffects.ts:4`. The executed MCP reproduction observed the retained record and the contrary discovery declarations together. ### Additional context - [#968](https://github.com/morluto/rea/issues/968) tracks broad Frida adapter scope and effect-aware contracts; it does not report successful status calls retaining Evidence while advertising read-only behavior. - [#101](https://github.com/morluto/rea/issues/101), closed as completed, concerns dynamic capability and policy-aware discovery; it does not identify this status-retention mismatch. - [#2213](https://github.com/morluto/rea/issues/2213), closed as completed, concerns missing argument details on rejected Frida calls; this report concerns a successful status call with a valid session ID. - [#2544](https://github.com/morluto/rea/issues/2544), closed as completed, concerns successful script-load Evidence after cancellation; this report concerns successful status Evidence being misclassified as read-only. - [#3184](https://github.com/morluto/rea/issues/3184) concerns CLI attach silently ignoring spawn arguments; this report concerns MCP status mutation metadata.
Author
Owner

@z2Ace0107 commented on GitHub (Oct 15, 2026):

I'd like to work on this issue. I reproduced the annotation/Evidence mismatch on Windows using the production MCP server and Evidence ledger with a simulated Frida device; no real engine or target execution was involved.

I'll keep the fix limited to the canonical effect metadata and MCP regression coverage, preserving status Evidence recording and non-destructive behavior.

<!-- gh-comment-id:6184784138 --> @z2Ace0107 commented on GitHub (Oct 15, 2026): I'd like to work on this issue. I reproduced the annotation/Evidence mismatch on Windows using the production MCP server and Evidence ledger with a simulated Frida device; no real engine or target execution was involved. I'll keep the fix limited to the canonical effect metadata and MCP regression coverage, preserving status Evidence recording and non-destructive behavior.
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#1197
No description provided.