[GH-ISSUE #3322] [Bug] Frida close omits foreign execution from advertised effects #1193

Closed
opened 2026-10-15 18:31:53 +00:00 by bsakaguchi · 2 comments
Owner

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

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

close_frida_session advertises destructiveHint: false and omits executesForeignCode from session discovery, although closing a paused spawned session resumes its selected target. The separately exposed resume_frida_session operation correctly declares both effects. A caller choosing operations from the machine-readable catalog receives different effect classifications for the same target-resume action.

The MCP reproduction below observed a successful close and a resume(512) dispatch through the production lifecycle manager to a simulated native Frida device. Both tools/list and binary_session omitted the corresponding execution effect. The simulated device only recorded the request; this observation does not establish real Frida engine compatibility or actual target execution. The mismatch concerns effect metadata, and the existing cleanup behavior should remain intact.

Reproduction

  1. Start from a clean source checkout and prepare the locked dependencies and built runtime:
git clone https://github.com/morluto/rea.git rea-close-effects
cd rea-close-effects
git checkout 8dde6370d0809ad04cda3342d4e6f290f48aa063
npm ci
npm run build:cached
  1. From the repository root, run the following inline MCP integration reproduction. It uses the production server, lifecycle manager, contracts, and Evidence ledger with a simulated native Frida device. It requires no Frida engine, executes no target program, and needs no external input files:
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;
  console.log(JSON.stringify({
    state: started.structuredContent.state,
    closeDestructiveHint: tools.find(t => t.name === 'close_frida_session').annotations.destructiveHint,
    resumeDestructiveHint: tools.find(t => t.name === 'resume_frida_session').annotations.destructiveHint,
  }));
  const closed = await client.callTool({
    name: 'close_frida_session', arguments: { session_id: sessionId },
  });
  console.log(JSON.stringify({ closeSucceeded: closed.isError !== true, resumedPids: resumed }));
  const inventory = await client.callTool({ name: 'binary_session', arguments: {} });
  const group = inventory.structuredContent.result.tool_availability.groups.find(g => g.names.includes('close_frida_session'));
  console.log(JSON.stringify({
    declaresForeignExecution: group.effects.includes('executesForeignCode'),
    declaresDestructive: group.annotations.includes('destructive'),
  }));
} finally {
  await client.close();
  await server.close();
  await session.close();
}
JS
  1. Compare the close and explicit-resume annotations, the recorded resume dispatch, and the close entry in binary_session. The observed output is under Relevant output or evidence. The reported reproduction used already installed locked dependencies and a prebuilt runtime; the install and build commands above are clean-checkout prerequisites.

Expected behavior

The effect inventory must cover possible behavior across supported inputs. A close operation that can resume target execution must declare executesForeignCode and carry the same destructive classification as explicitly resuming that target. Close should still release the session and avoid leaving a spawned target suspended.

docs/mcp-contracts.md:478 defines destructiveHint for operations that run or drive foreign code and requires effects to cover possible supported behavior. src/contracts/toolEffects.ts:13 defines executesForeignCode to include driving an analysis target. src/contracts/fridaToolContracts.ts:253 explicitly promises that close resumes paused spawns.

Impact and scope

P2, supported. Both tool discovery and session discovery understate a supported operation's target-execution effect. Callers using these advertised classifications cannot distinguish cleanup that may resume target code from cleanup without that effect. No target data loss or authorization bypass was observed.

Workaround available and verified in the same simulated MCP integration setup. Explicitly resuming through the correctly classified resume_frida_session before close succeeded and produced one resume dispatch before close, with no additional resume dispatch during close. This preserves the intended running-target and released-session result. Real-engine behavior was not exercised.

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 runtime used the checkout's locked dependencies and the locked MCP client/server SDK over linked in-memory transports, with a simulated native Frida device.

Target and artifact context

Simulated local native Frida device, spawn program fixture, and synthetic PID 512. Production server, lifecycle manager, contracts, and Evidence ledger were used. No real target program or Frida engine was executed.

Interface

MCP

Earliest failure stage

Transport or result projection

Relevant output or evidence

The executed MCP reproduction printed:

{"state":"paused","closeDestructiveHint":false,"resumeDestructiveHint":true}
{"closeSucceeded":true,"resumedPids":[512]}
{"declaresForeignExecution":false,"declaresDestructive":false}

The focused contract tests for effect annotations, Frida inputs, and advertised examples all passed. They do not check close's automatic-resume effect against the declared metadata.

Typed outcome or recovery expected

Repeat the paused-spawn reproduction. Close must still succeed and dispatch one resume, while tools/list declares destructiveHint: true and the close entry in binary_session includes executesForeignCode and the destructive annotation. Keep the explicit-resume-then-close control successful, with no second resume during close. These checks restore truthful metadata while preserving the existing cleanup behavior.

Analysis or suspected cause

Source-derived reasoning: src/contracts/toolEffects.ts:235 defines close metadata without executesForeignCode, while src/server/registerFridaTools.ts:229 reaches the production close service. src/frida/FridaInstrumentationManager.ts:656 selects the paused-spawn case, and src/frida/FridaInstrumentationManager.ts:687 dispatches the resume when no scripts remain. These source paths explain the observed mismatch between successful resume dispatch and both discovery channels.

Additional context

  • Related closed #2209 reports script-file validation after spawn that lets cleanup run an uninstrumented target. This report concerns the advertised effects of an intentional successful close; its restoration check preserves automatic resume.
  • Related closed #101 covers broad capability and policy discovery. It does not identify the close tool's omitted execution effect.
  • Related open #968 tracks the Frida adapter and lifecycle requirements. It does not report this metadata mismatch.
Originally created by @coygeek on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3322 ### 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 `close_frida_session` advertises `destructiveHint: false` and omits `executesForeignCode` from session discovery, although closing a paused spawned session resumes its selected target. The separately exposed `resume_frida_session` operation correctly declares both effects. A caller choosing operations from the machine-readable catalog receives different effect classifications for the same target-resume action. The MCP reproduction below observed a successful close and a `resume(512)` dispatch through the production lifecycle manager to a simulated native Frida device. Both `tools/list` and `binary_session` omitted the corresponding execution effect. The simulated device only recorded the request; this observation does not establish real Frida engine compatibility or actual target execution. The mismatch concerns effect metadata, and the existing cleanup behavior should remain intact. ### Reproduction 1. Start from a clean source checkout and prepare the locked dependencies and built runtime: ```bash git clone https://github.com/morluto/rea.git rea-close-effects cd rea-close-effects git checkout 8dde6370d0809ad04cda3342d4e6f290f48aa063 npm ci npm run build:cached ``` 2. From the repository root, run the following inline MCP integration reproduction. It uses the production server, lifecycle manager, contracts, and Evidence ledger with a simulated native Frida device. It requires no Frida engine, executes no target program, and needs no external input files: ```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; console.log(JSON.stringify({ state: started.structuredContent.state, closeDestructiveHint: tools.find(t => t.name === 'close_frida_session').annotations.destructiveHint, resumeDestructiveHint: tools.find(t => t.name === 'resume_frida_session').annotations.destructiveHint, })); const closed = await client.callTool({ name: 'close_frida_session', arguments: { session_id: sessionId }, }); console.log(JSON.stringify({ closeSucceeded: closed.isError !== true, resumedPids: resumed })); const inventory = await client.callTool({ name: 'binary_session', arguments: {} }); const group = inventory.structuredContent.result.tool_availability.groups.find(g => g.names.includes('close_frida_session')); console.log(JSON.stringify({ declaresForeignExecution: group.effects.includes('executesForeignCode'), declaresDestructive: group.annotations.includes('destructive'), })); } finally { await client.close(); await server.close(); await session.close(); } JS ``` 3. Compare the close and explicit-resume annotations, the recorded resume dispatch, and the close entry in `binary_session`. The observed output is under Relevant output or evidence. The reported reproduction used already installed locked dependencies and a prebuilt runtime; the install and build commands above are clean-checkout prerequisites. ### Expected behavior The effect inventory must cover possible behavior across supported inputs. A close operation that can resume target execution must declare `executesForeignCode` and carry the same destructive classification as explicitly resuming that target. Close should still release the session and avoid leaving a spawned target suspended. `docs/mcp-contracts.md:478` defines `destructiveHint` for operations that run or drive foreign code and requires effects to cover possible supported behavior. `src/contracts/toolEffects.ts:13` defines `executesForeignCode` to include driving an analysis target. `src/contracts/fridaToolContracts.ts:253` explicitly promises that close resumes paused spawns. ### Impact and scope P2, supported. Both tool discovery and session discovery understate a supported operation's target-execution effect. Callers using these advertised classifications cannot distinguish cleanup that may resume target code from cleanup without that effect. No target data loss or authorization bypass was observed. Workaround available and verified in the same simulated MCP integration setup. Explicitly resuming through the correctly classified `resume_frida_session` before close succeeded and produced one resume dispatch before close, with no additional resume dispatch during close. This preserves the intended running-target and released-session result. Real-engine behavior was not exercised. ### 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 runtime used the checkout's locked dependencies and the locked MCP client/server SDK over linked in-memory transports, with a simulated native Frida device. ### Target and artifact context Simulated local native Frida device, spawn program `fixture`, and synthetic PID 512. Production server, lifecycle manager, contracts, and Evidence ledger were used. No real target program or Frida engine was executed. ### Interface MCP ### Earliest failure stage Transport or result projection ### Relevant output or evidence The executed MCP reproduction printed: ```text {"state":"paused","closeDestructiveHint":false,"resumeDestructiveHint":true} {"closeSucceeded":true,"resumedPids":[512]} {"declaresForeignExecution":false,"declaresDestructive":false} ``` The focused contract tests for effect annotations, Frida inputs, and advertised examples all passed. They do not check close's automatic-resume effect against the declared metadata. ### Typed outcome or recovery expected Repeat the paused-spawn reproduction. Close must still succeed and dispatch one resume, while `tools/list` declares `destructiveHint: true` and the close entry in `binary_session` includes `executesForeignCode` and the destructive annotation. Keep the explicit-resume-then-close control successful, with no second resume during close. These checks restore truthful metadata while preserving the existing cleanup behavior. ### Analysis or suspected cause Source-derived reasoning: `src/contracts/toolEffects.ts:235` defines close metadata without `executesForeignCode`, while `src/server/registerFridaTools.ts:229` reaches the production close service. `src/frida/FridaInstrumentationManager.ts:656` selects the paused-spawn case, and `src/frida/FridaInstrumentationManager.ts:687` dispatches the resume when no scripts remain. These source paths explain the observed mismatch between successful resume dispatch and both discovery channels. ### Additional context - Related closed [#2209](https://github.com/morluto/rea/issues/2209) reports script-file validation after spawn that lets cleanup run an uninstrumented target. This report concerns the advertised effects of an intentional successful close; its restoration check preserves automatic resume. - Related closed [#101](https://github.com/morluto/rea/issues/101) covers broad capability and policy discovery. It does not identify the close tool's omitted execution effect. - Related open [#968](https://github.com/morluto/rea/issues/968) tracks the Frida adapter and lifecycle requirements. It does not report this metadata mismatch.
Author
Owner

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

Implemented the close-effect correction on branch 1052326311:fix/frida-close-effects, current commit 73181d821e490129c6cddb92c7d6178f038552b5.

The canonical effect now includes executesForeignCode, and regression coverage runs the production MCP server/lifecycle manager with a simulated native device. Both tools/list and binary_session report destructive target execution. Direct close resumes once; explicit resume followed by close does not resume again; both detach once and retain target_state: unknown.

The two new regressions fail on base ab45bf64; the candidate passes 47 focused Frida contract/CLI/MCP tests, npm run check, npm run docs:check, and diff checking on macOS arm64 with pinned Node/npm. This is metadata/lifecycle proof with a simulated device, not real Frida compatibility verification. It is separate from #3902's status/Evidence metadata correction.

The branch is published, but GitHub currently denies PR creation for this account (CreatePullRequest permission error; REST creation also rejected). No PR has been opened. Prepared comparison: https://github.com/morluto/rea/compare/main...1052326311:fix/frida-close-effects?expand=1 .

<!-- gh-comment-id:6186403419 --> @1052326311 commented on GitHub (Oct 15, 2026): Implemented the close-effect correction on branch [`1052326311:fix/frida-close-effects`](https://github.com/1052326311/rea/tree/fix/frida-close-effects), current commit `73181d821e490129c6cddb92c7d6178f038552b5`. The canonical effect now includes `executesForeignCode`, and regression coverage runs the production MCP server/lifecycle manager with a simulated native device. Both `tools/list` and `binary_session` report destructive target execution. Direct close resumes once; explicit resume followed by close does not resume again; both detach once and retain `target_state: unknown`. The two new regressions fail on base `ab45bf64`; the candidate passes 47 focused Frida contract/CLI/MCP tests, `npm run check`, `npm run docs:check`, and diff checking on macOS arm64 with pinned Node/npm. This is metadata/lifecycle proof with a simulated device, not real Frida compatibility verification. It is separate from #3902's status/Evidence metadata correction. The branch is published, but GitHub currently denies PR creation for this account (`CreatePullRequest` permission error; REST creation also rejected). No PR has been opened. Prepared comparison: https://github.com/morluto/rea/compare/main...1052326311:fix/frida-close-effects?expand=1 .
Author
Owner

@coygeek commented on GitHub (Oct 24, 2026):

Checked the reported fix on main at d87e73f2db.

close_frida_session now includes executesForeignCode in canonical effects. Annotation derivation therefore marks it destructive, and the MCP lifecycle regression explicitly checks both tools/list destructiveHint and binary_session effect/destructive fields before exercising a paused-spawn close/cleanup retry. The change keeps automatic resume/cleanup behavior.

Implementation: toolEffects.ts, fridaLifecycleMcp.test.ts.

The relevant regression checks in fridaLifecycleMcp.test.ts passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.

<!-- gh-comment-id:6380737767 --> @coygeek commented on GitHub (Oct 24, 2026): Checked the reported fix on `main` at d87e73f2db50be72d33a4b3c7199af35518b441c. close_frida_session now includes executesForeignCode in canonical effects. Annotation derivation therefore marks it destructive, and the MCP lifecycle regression explicitly checks both tools/list destructiveHint and binary_session effect/destructive fields before exercising a paused-spawn close/cleanup retry. The change keeps automatic resume/cleanup behavior. Implementation: [toolEffects.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/contracts/toolEffects.ts#L242), [fridaLifecycleMcp.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/tests/boundary/mcp/fridaLifecycleMcp.test.ts#L299). The relevant regression checks in [fridaLifecycleMcp.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/tests/boundary/mcp/fridaLifecycleMcp.test.ts) passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.
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#1193
No description provided.