fix(pcap): share capture host admission with discovery

This commit is contained in:
morluto
2026-10-24 02:36:11 +08:00
parent b7eededf5a
commit ad629b2e31
8 changed files with 78 additions and 5 deletions
+2
View File
@@ -30,6 +30,8 @@ Evidence. Capture
inspection requires Linux with util-linux prlimit or Windows with owned Job
Objects. macOS capture inspection returns a capability gap because the new
native memory boundary is unverified; its version probes remain available.
`binary_session` advertises those operations separately: unsupported capture
hosts do not disable the client probe or remove either tool from the catalog.
A selected tool directory or unreadable tool is a configuration capability
refusal naming its selected path, before any capture bytes are opened.
Tools and installed libraries/plugins are caller-trusted. An empty private
@@ -1,3 +1,4 @@
import { pcapCaptureHostSupported } from "../domain/pcap/pcapAnalysis.js";
import { describe, expect, it } from "vitest";
import {
@@ -815,3 +816,30 @@ describe("host-restricted integrations", () => {
},
);
});
it.each(["darwin", "linux", "win32"])(
"distinguishes pcap capture host support from its target-free client probe on %s",
(platform) => {
const tools = buildCapabilityInventory(
{ open: false, capabilities: [] },
{
processCaptureEnabled: true,
hostUnsupported: { pcapCapture: !pcapCaptureHostSupported(platform) },
},
);
expect(
tools.find(({ name }) => name === "inspect_pcap_client"),
).toMatchObject({ available: true, reason: "available" });
expect(
tools.find(({ name }) => name === "inspect_pcap_capture"),
).toMatchObject(
platform === "darwin"
? {
available: false,
reason: "unsupported_host",
remediation: expect.stringContaining("native memory bound"),
}
: { available: true, reason: "available" },
);
},
);
+9
View File
@@ -1,3 +1,4 @@
import { PCAP_CAPTURE_UNSUPPORTED_HOST_REASON } from "../domain/pcap/pcapAnalysis.js";
import { DEFAULT_CALL_RELATIONSHIP_DIRECTION } from "../contracts/enhancedInputs.js";
import { FIRMWARE_TOOL_REQUIREMENTS } from "../contracts/firmware/firmwareToolRequirements.js";
import { CALL_RELATIONSHIP_TRAVERSALS } from "./CallRelationshipTraversal.js";
@@ -56,6 +57,7 @@ type ProviderDescriptor = ProviderCapability;
/** Optional integrations whose verified hosts exclude some platforms. */
export const HOST_RESTRICTED_FEATURES = [
"packetCapture",
"pcapCapture",
"signalRecording",
"evmInterface",
"binaryLayout",
@@ -602,6 +604,13 @@ const workflowAvailabilityFor = ({
remediation: "Run process capture on a supported Linux or macOS host.",
};
if (kind === "application") return { reason: "available", remediation: null };
if (name === "inspect_pcap_capture")
return hostRestrictedAvailability(
policy,
"pcapCapture",
true,
PCAP_CAPTURE_UNSUPPORTED_HOST_REASON,
);
if (kind === "pcap-provider" || kind === "flutter-provider")
return { reason: "available", remediation: null };
const electron = electronProviderAvailability(name, kind, policy);
+6
View File
@@ -2,6 +2,12 @@ import { z } from "zod";
import { localPathStringSchema } from "../localPath.js";
import { digestSchema } from "../digests.js";
/** Summary capture admission and discovery share this native-bound host profile. */
export const pcapCaptureHostSupported = (platform: string): boolean =>
platform === "linux" || platform === "win32";
export const PCAP_CAPTURE_UNSUPPORTED_HOST_REASON =
"Packet summaries require an enforceable native memory bound: Linux with util-linux prlimit, or Windows with owned Job Objects. This host's capture boundary remains unsupported.";
/**
* Caller intent for packet-capture analysis through caller-supplied tshark
* and capinfos. REA never installs Wireshark or its tools.
+6 -3
View File
@@ -1,3 +1,7 @@
import {
pcapCaptureHostSupported,
PCAP_CAPTURE_UNSUPPORTED_HOST_REASON,
} from "../domain/pcap/pcapAnalysis.js";
import { access, realpath } from "node:fs/promises";
import { constants } from "node:fs";
import { isAbsolute } from "node:path";
@@ -32,9 +36,8 @@ export const preparePcapNativeBound = async (
],
};
}
if (process.platform !== "linux") {
const reason =
"Packet summaries require an enforceable native memory bound: Linux with util-linux prlimit, or Windows with owned Job Objects. This host's capture boundary remains unsupported.";
if (!pcapCaptureHostSupported(process.platform)) {
const reason = PCAP_CAPTURE_UNSUPPORTED_HOST_REASON;
throw new AnalysisCapabilityUnavailableError(
"pcap",
"inspect_pcap_capture",
+2 -1
View File
@@ -8,6 +8,7 @@ import {
} from "../application/AnalysisProvider.js";
import {
pcapResultSchemas,
pcapCaptureHostSupported,
type PcapRequest,
type PcapOperation,
} from "../domain/pcap/pcapAnalysis.js";
@@ -505,7 +506,7 @@ export class PcapProvider {
): Promise<ProviderAvailability> {
try {
const { client } = await this.client("inspect_pcap_client", signal);
if (process.platform !== "linux" && process.platform !== "win32")
if (!pcapCaptureHostSupported(process.platform))
return {
status: "unavailable",
code: "unsupported_host",
+6
View File
@@ -1,3 +1,4 @@
import { pcapCaptureHostSupported } from "../domain/pcap/pcapAnalysis.js";
import { createGpuCodeObjectsService } from "../composition/gpu.js";
import { registerGpuCodeObjectsTools } from "./registerGpuCodeObjectsTools.js";
import type { PacketCaptureService } from "../application/captures/PacketCaptureService.js";
@@ -276,6 +277,7 @@ const installSessionToolAvailability = (
// Each provider owns its verified host; discovery and execution share it.
const verifiedHost = {
packetCapture: tsharkHostSupported(platform),
pcapCapture: pcapCaptureHostSupported(platform),
signalRecording: sigrokHostSupported(platform),
evmInterface: evmHostSupported(platform, arch),
pwntools: pwntoolsHostSupported(platform, arch),
@@ -291,6 +293,10 @@ const installSessionToolAvailability = (
const basePolicy = sessionAvailabilityPolicy(options.availabilityPolicy, {
optionalProviderLoadFailures: options.optionalProviderLoadFailures,
hostUnsupported: {
pcapCapture: outsideVerifiedHost(
options.pcapAnalysis,
verifiedHost.pcapCapture,
),
packetCapture: outsideVerifiedHost(
options.packetCapture,
verifiedHost.packetCapture,
@@ -18,6 +18,7 @@ const linux = process.platform === "linux";
const packetCaptureAvailability = async (
environment: Readonly<Record<string, string | undefined>>,
name = "inspect_packet_capture",
): Promise<Record<string, unknown> | undefined> => {
const session = createTestBinarySession(() => {
throw new Error("binary analysis must not start during status discovery");
@@ -44,7 +45,7 @@ const packetCaptureAvailability = async (
.object({ result: sessionProvider })
.parse(response.structuredContent).result;
return expandToolAvailability(parsed.tool_availability).find(
({ name }) => name === "inspect_packet_capture",
(tool) => tool.name === name,
);
} finally {
await Promise.allSettled([client.close(), server.close(), session.close()]);
@@ -106,3 +107,20 @@ it("reports packet tools unavailable when no absolute PATH entry holds TShark",
reason: linux ? "provider_missing" : "unsupported_host",
});
});
it("advertises pcap capture host support separately from the always available client probe", async () => {
expect(
await packetCaptureAvailability({}, "inspect_pcap_client"),
).toMatchObject({ available: true, reason: "available" });
expect(
await packetCaptureAvailability({}, "inspect_pcap_capture"),
).toMatchObject(
process.platform === "linux" || process.platform === "win32"
? { available: true, reason: "available" }
: {
available: false,
reason: "unsupported_host",
remediation: expect.stringContaining("native memory bound"),
},
);
});