Repository navigation
fix(ghidra): bind the handshake before a lazily started first query - #8679
Closed
abhisheksharma001 wants to merge 1 commit into
Closed
abhisheksharma001 wants to merge 1 commit into
abhisheksharma001 wants to merge 1 commit into
Conversation
`checkSessionObservations` rejects analysis seeds, disabled-analyzer and extension reports that the committed profile did not request, but it ran only inside `startSession`, which a session called for `health` or when an eager-start term held (existing project, PE, extensions, seeds, disabled analyzers, soft-float MIPS, RISC-V calling convention). A plain ELF whose profile commits none of these started the bridge lazily inside `client.callTool`, so a report the profile did not commit was accepted and the first query returned Evidence claiming a profile that did not describe its analysis. When the client can report that it is not ready, run the checked start before the first query. The real client shares one startup attempt, so the bridge still launches once and cancellation flows through the same path. `providerStarted` stays unset on that lazy path, so a provider that dies during its first query keeps today's startup guidance; eager sessions and `health` are unchanged. Fakes without `isReady` keep the lazy call, which the shared startup-free fixtures rely on. Closes morluto#8612 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Contributor
[Medium impact] The reviewed changes appear safe to merge. SummaryThe PR checks a lazily started Ghidra handshake against the committed profile before sending the first query.
Diagram%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Valid query] --> B{Eager startup required?}
B -->|Yes| C[Start and check handshake]
B -->|No| D{Client reports not ready?}
D -->|Yes| C
D -->|No| E[Call query]
C --> F{Reports match profile?}
F -->|No| G[Close client and return error]
F -->|Yes| E
E --> H[Return checked query result]
Reviews (1) · Last reviewed commit: "fix(ghidra): bind the handshake before a..." · Reviewed by Greptile |
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #8612. The Ghidra bridge handshake can report analysis seeds, disabled-analyzer outcomes and extension results, and
checkSessionObservationsrejects each report the committed profile did not request. That check ran only insidestartSession, which a session called forhealthor when an eager-start term held. A plain x86_64 ELF whose profile commits none of those terms started the bridge lazily insideclient.callTool, so the report was never checked and the first query returned its result with Evidence claiming a profile that did not describe the analysis.Problem and expected behavior
Trigger: a fake
GhidraProviderClientFactorywhosestartreturns a valid handshake plus one uncommitteddisabled_analyzersreport. Withhealthfirst, the session fails closed (start 1, call 0, close 1). Withlist_proceduresfirst, base returnsok: truewith start 0, call 1, close 0. Expected: each report binds in both directions, and a session that receives a report its profile did not commit fails closed before it returns any analysis result, whatever the first operation is.Change and scope
src/ghidra/GhidraProviderClient.ts: splitstartSessionintocheckedStart(start the bridge, bind the handshake) and a thinstartSessionthat also recordsproviderStarted. On the lazy path, when the client reportsisReady() === false, runcheckedStartbefore the first query.GhidraProviderClientFactorygains optionalisReady, which the productionGhidraClientalready implements.startSessionbefore every query": that proposal changes recovery guidance (a provider lost during its first query would get theopen_binaryrestart guidance) and the MCP startup-cancellation lane's admission point, which the issue flags as needing a maintainer scope decision. This change leavesproviderStartedunset on the lazy path, so a provider that dies during its first query keeps the startup guidance pinned byghidraOomRetention("Then retry.") and "keeps installation guidance when Ghidra dies before it ever answered". Fakes withoutisReadykeep the lazy call, so the four startup-free fixtures and thestartCount0 pin are unchanged.callTool's internalstart()returns the completed attempt. Cancellation during that start flows through the same shared attempt as today.Contribution scope: lifecycle-adjacent fix within the existing provider adapter; opened as a draft so maintainers can decide whether the narrower gate or the issue's broader proposal is wanted.
Contract and boundary impact
Semantic owner and earliest changed stage: the Ghidra provider client's startup admission (
src/ghidra/GhidraProviderClient.ts); the bridge, launcher andGhidraClientare unchanged.Contribution scope fit and linked maintainer decision: the issue requests a maintainer decision for the broad fix; this draft is the narrow alternative for that decision.
CLI and MCP/tool-catalog contract: none.
Provider, bridge, target-format, or platform compatibility: every lazily started target (non-PE, no existing project) now binds its handshake before the first query; PE, existing projects and the other eager terms are unchanged.
Evidence, artifact, provenance, or reconstruction contract: Evidence from a lazily started session can no longer carry a profile that the handshake contradicted.
Target selection, authority, upstream behavior, and cleanup details: a failed check closes the client before any query, as the
healthpath does.Generated metadata, package, or installation impact: none.
Read existing files or captured data.
Evidence and regression coverage
src/ghidra/GhidraProvider.behavior.test.tsgains "fails closed on an uncommitted analyzer report before a lazily started query" (start 1, call 0, close 1 withlist_proceduresfirst) and "binds a lazily started handshake once and queries the ready client directly" (first query start 1 call 1; once the fake reports ready, the second query is call 2 with no further start).analyzerSessionaccepts an optionalisReady.GhidraProviderClient.tsswapped in, both new tests fail (expected true to be false;starts: 0) and the other 194 pass; with this change all 196 pass.ProviderAdapterErrorfor an uncommitted report, now also for a firstlist_procedures/analyze_function/… query.Validation performed
npm run test:local -- src/ghidra/GhidraProvider.behavior.test.ts— 196 passednpm run typecheck— passednpx oxlint/npx oxfmt --checkon the two files — only pre-existing warnings (max-paramsonprojectSessionError,max-depthin the test file)npm run test:focused -- tests/boundary/mcp/ghidraStartupCancellationMcp.test.ts tests/boundary/providers/ghidra/ghidraOomRetention.test.ts— 11 passed across the two files, so the pinned startup guidance and both MCP startup-cancellation lanes are unchangedCompatibility, safety, and release
Author readiness
Human owner: @abhisheksharma001
Before merge
type(scope): outcome.🤖 Generated with Claude Code