fix(discovery): report debugger start host restrictions (#7965)
This commit is contained in:
1 parent
70a8325c38
commit
3536ea2f49
7 files changed
+109
-2
No files matched your search
@@ -1,3 +1,4 @@
|
||||
import { persistentDebuggerHostSupported } from "../process/PersistentDebuggerHost.js";
|
||||
import { pcapCaptureHostSupported } from "../domain/pcap/pcapAnalysis.js";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
@@ -887,6 +888,32 @@ it("keeps independent native sessions discoverable without an unrelated binary t
|
||||
);
|
||||
});
|
||||
|
||||
it.each(["darwin", "linux", "win32"] as const)(
|
||||
"reports the persistent debugger start host restriction on %s",
|
||||
(platform) => {
|
||||
const entries = buildCapabilityInventory(status(), {
|
||||
...enabledPolicy,
|
||||
hostUnsupported: {
|
||||
persistentDebugger: !persistentDebuggerHostSupported(platform),
|
||||
},
|
||||
});
|
||||
for (const name of ["start_gdb_session", "start_rizin_debug_session"])
|
||||
expect(entries.find((entry) => entry.name === name)).toMatchObject(
|
||||
platform === "linux"
|
||||
? { available: true, reason: "available" }
|
||||
: {
|
||||
available: false,
|
||||
reason: "unsupported_host",
|
||||
remediation: expect.stringContaining("Linux"),
|
||||
},
|
||||
);
|
||||
for (const name of ["gdb_session_status", "close_rizin_debug_session"])
|
||||
expect(entries.find((entry) => entry.name === name)).toMatchObject({
|
||||
available: true,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
describe("summary tool availability", () => {
|
||||
it("summarizes every tool's current availability without static catalog flags", () => {
|
||||
const full = buildCapabilityInventory(status(), enabledPolicy);
|
||||
|
||||
@@ -71,6 +71,7 @@ export const HOST_RESTRICTED_FEATURES = [
|
||||
"firmwareInspection",
|
||||
"firmwareExtraction",
|
||||
"javascriptRecovery",
|
||||
"persistentDebugger",
|
||||
] as const;
|
||||
export type HostRestrictedFeature = (typeof HOST_RESTRICTED_FEATURES)[number];
|
||||
|
||||
@@ -444,6 +445,13 @@ const workflowAvailabilityFor = ({
|
||||
kind,
|
||||
policy,
|
||||
}: AvailabilityContext): Availability | null => {
|
||||
if (name === "start_gdb_session" || name === "start_rizin_debug_session")
|
||||
return hostRestrictedAvailability(
|
||||
policy,
|
||||
"persistentDebugger",
|
||||
true,
|
||||
"Run persistent debugger sessions on Linux with /proc descriptor readback.",
|
||||
);
|
||||
if (
|
||||
name === "inspect_desktop_application" ||
|
||||
name === "inspect_go_binary" ||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { persistentDebuggerHostSupported } from "../process/PersistentDebuggerHost.js";
|
||||
import { ABORTED, waitForAbortable } from "../process/AbortablePromise.js";
|
||||
import { snapshotGdbCommandRecords } from "./GdbCommandRecords.js";
|
||||
import { randomUUID } from "node:crypto";
|
||||
@@ -639,7 +640,7 @@ export class GdbSessionManager {
|
||||
? cancelledWithCallerReason("start_gdb_session", signal)
|
||||
: new AnalysisCancelledError("start_gdb_session"),
|
||||
);
|
||||
if (this.#platform !== "linux")
|
||||
if (!persistentDebuggerHostSupported(this.#platform))
|
||||
return err(
|
||||
new AnalysisCapabilityUnavailableError(
|
||||
"gdb",
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
/** Both persistent debugger managers require Linux procfs descriptor readback. */
|
||||
export const persistentDebuggerHostSupported = (
|
||||
platform: NodeJS.Platform,
|
||||
): boolean => platform === "linux";
|
||||
@@ -1,3 +1,4 @@
|
||||
import { persistentDebuggerHostSupported } from "../process/PersistentDebuggerHost.js";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { isUtf8 } from "node:buffer";
|
||||
import { open, readlink, realpath, stat } from "node:fs/promises";
|
||||
@@ -361,7 +362,7 @@ export class RizinDebugSessionManager {
|
||||
> {
|
||||
if (signal?.aborted)
|
||||
return err(rizinCancellation("start_rizin_debug_session", signal));
|
||||
if (this.#platform !== "linux")
|
||||
if (!persistentDebuggerHostSupported(this.#platform))
|
||||
return err(
|
||||
new AnalysisCapabilityUnavailableError(
|
||||
"rizin",
|
||||
|
||||
@@ -205,6 +205,7 @@ import { CutterBridgeClient } from "../cutter/CutterBridgeClient.js";
|
||||
import { CutterBridgeService } from "../cutter/CutterBridgeService.js";
|
||||
import { registerCutterTools } from "./registerCutterTools.js";
|
||||
import { GdbSessionManager } from "../gdb/GdbSessionManager.js";
|
||||
import { persistentDebuggerHostSupported } from "../process/PersistentDebuggerHost.js";
|
||||
import { registerGdbTools } from "./registerGdbTools.js";
|
||||
import { RizinDebugSessionManager } from "../rizin/RizinDebugSessionManager.js";
|
||||
import { registerRizinDebugTools } from "./registerRizinDebugTools.js";
|
||||
@@ -299,6 +300,7 @@ const installSessionToolAvailability = (
|
||||
const basePolicy = sessionAvailabilityPolicy(options.availabilityPolicy, {
|
||||
optionalProviderLoadFailures: options.optionalProviderLoadFailures,
|
||||
hostUnsupported: {
|
||||
persistentDebugger: !persistentDebuggerHostSupported(platform),
|
||||
pcapCapture: outsideVerifiedHost(
|
||||
options.pcapAnalysis,
|
||||
verifiedHost.pcapCapture,
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { expandToolAvailability } from "../../../src/contracts/toolOutputSchemaPrimitives.js";
|
||||
import { sessionOutputSchemas } from "../../../src/contracts/toolOutputSchemaGroups.js";
|
||||
import { createApplicationMcpHarness } from "../../fixtures/applicationMcpHarness.js";
|
||||
import { createTestBinarySession } from "../../fixtures/binarySession.js";
|
||||
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
|
||||
|
||||
it("reports debugger start host restrictions without requiring an analysis target", async () => {
|
||||
const session = createTestBinarySession(() => {
|
||||
throw new Error("discovery must not start binary analysis");
|
||||
});
|
||||
const { client } = await createApplicationMcpHarness({ session });
|
||||
const entries = async (detail: "summary" | "complete") => {
|
||||
const response = await client.callTool({
|
||||
name: "binary_session",
|
||||
arguments: { detail },
|
||||
});
|
||||
expect(response.isError).not.toBe(true);
|
||||
const parsed = sessionOutputSchemas.binary_session.parse(
|
||||
response.structuredContent,
|
||||
).result;
|
||||
expect(parsed.open).toBe(false);
|
||||
return expandToolAvailability(parsed.tool_availability);
|
||||
};
|
||||
for (const detail of ["summary", "complete"] as const) {
|
||||
const tools = await entries(detail);
|
||||
for (const name of ["start_gdb_session", "start_rizin_debug_session"]) {
|
||||
expect(tools.find((tool) => tool.name === name)).toMatchObject(
|
||||
process.platform === "linux"
|
||||
? { available: true, reason: "available" }
|
||||
: {
|
||||
available: false,
|
||||
reason: "unsupported_host",
|
||||
remediation: expect.stringContaining("Linux"),
|
||||
},
|
||||
);
|
||||
}
|
||||
expect(
|
||||
tools.find((tool) => tool.name === "gdb_session_status"),
|
||||
).toMatchObject({ available: true });
|
||||
}
|
||||
if (process.platform !== "linux") {
|
||||
for (const [name, args] of [
|
||||
["start_gdb_session", {}],
|
||||
[
|
||||
"start_rizin_debug_session",
|
||||
{ path: "./absent.bin", inferior_tty: "/dev/null" },
|
||||
],
|
||||
] as const) {
|
||||
const response = await client.callTool({ name, arguments: args });
|
||||
expect(response.isError).toBe(true);
|
||||
expect(parseMcpToolError(response)).toMatchObject({
|
||||
error: { code: "capability_unavailable" },
|
||||
});
|
||||
}
|
||||
const after = await entries("complete");
|
||||
for (const name of ["start_gdb_session", "start_rizin_debug_session"])
|
||||
expect(after.find((tool) => tool.name === name)).toMatchObject({
|
||||
available: false,
|
||||
reason: "unsupported_host",
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user