Files
reverse-engineering-anything/tests/boundary/pcap/pcapProviderCrossPlatform.test.ts
T

507 lines
17 KiB
TypeScript

/**
* Cross-platform pcap provider tests.
*
* POSIX hosts run the #!/bin/sh fixtures;
* Windows runs the .cmd stubs from tests/fixtures/pcap/. Both validate
* the same provider contract surface: tool probing, capture inspection,
* refusal paths, and cancellation before spawn.
*/
import { createHash } from "node:crypto";
import {
chmod,
mkdir,
open,
readFile,
realpath,
symlink,
unlink,
writeFile,
} from "node:fs/promises";
import { join } from "node:path";
import { execFile } from "node:child_process";
import { promisify } from "node:util";
import { expect, it, onTestFinished, vi } from "vitest";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { PcapProvider } from "../../../src/pcap/PcapProvider.js";
import {
CAPINFOS_COMMAND_ENV,
TSHARK_COMMAND_ENV,
} from "../../../src/pcap/PcapConfiguration.js";
import { PcapSnapshot } from "../../../src/pcap/PcapSnapshot.js";
import * as regularFile from "../../../src/filesystem/RegularFile.js";
import { OwnedFileHandle } from "../../../src/filesystem/OwnedFileHandle.js";
import type { PcapCommandLauncher } from "../../../src/pcap/PcapCommand.js";
import { projectAnalysisError } from "../../../src/domain/analysisErrorProjection.js";
import type { PcapRequest } from "../../../src/domain/pcap/pcapAnalysis.js";
import { cleanupOwnedProcessGroup } from "../../../src/process/ProcessOwnership.js";
import { spawnOwnedProviderProcess } from "../../../src/process/ProviderProcess.js";
const isWindows = process.platform === "win32";
const hasNativeBound = process.platform === "linux" || isWindows;
const stubDir = join(import.meta.dirname, "../../fixtures/pcap");
const tsharkStub = isWindows
? join(stubDir, "tshark-stub.cmd")
: join(stubDir, "tshark-stub.sh");
const capinfosStub = isWindows
? join(stubDir, "capinfos-stub.cmd")
: join(stubDir, "capinfos-stub.sh");
const providerOver = (): PcapProvider => {
const provider = new PcapProvider({
environment: {
REA_TSHARK_COMMAND: tsharkStub,
REA_CAPINFOS_COMMAND: capinfosStub,
},
});
onTestFinished(() => provider.close());
return provider;
};
it.skipIf(isWindows)(
"preserves selected Wireshark wrappers and re-admits changed symlink targets after success",
async () => {
const directory = await createTestTempDirectory(
"rea-pcap-selected-wrapper-",
);
const bin = join(directory, "bin"),
libexec = join(directory, "libexec");
await mkdir(bin);
await mkdir(libexec);
const source = join(libexec, "wrapper"),
replacement = join(libexec, "replacement");
const script =
'#!/bin/sh\nselected="${0%/*}/${0##*/}"\nIFS= read -r banner < "$selected.version" || exit 19\nprintf "%s\\n" "$banner"\nif [ -f "$selected.retarget" ]; then\n IFS= read -r target < "$selected.retarget"\n /bin/ln -sf "$target" "$0"\nfi\n';
for (const path of [source, replacement]) {
await writeFile(path, script);
await chmod(path, 0o755);
}
const tshark = join(bin, "tshark"),
capinfos = join(bin, "capinfos");
for (const [path, name] of [
[tshark, "TShark"],
[capinfos, "Capinfos"],
] as const) {
await symlink(source, path);
await writeFile(`${path}.version`, `${name} (Wireshark) 4.6.6-stub\n`);
}
expect(
(await promisify(execFile)(tshark, ["--version"])).stdout.trim(),
).toBe("TShark (Wireshark) 4.6.6-stub");
const provider = new PcapProvider({
environment: {
REA_TSHARK_COMMAND: tshark,
REA_CAPINFOS_COMMAND: capinfos,
},
});
onTestFinished(() => provider.close());
const request = { operation: "inspect_pcap_client", input: {} } as const;
const first = await evidenceResult(provider, request);
expect(first.client).toMatchObject({
tshark_command: tshark,
tshark_resolved_path: await realpath(source),
capinfos_command: capinfos,
});
await writeFile(`${tshark}.retarget`, `${replacement}\n`);
const changed = await provider.execute(request);
if (changed.ok) throw new Error("Changed selected wrapper was accepted");
expect(projectAnalysisError(changed.error)).toMatchObject({
code: "artifact_changed",
details: {
path: tshark,
captured_output: { stdout: "TShark (Wireshark) 4.6.6-stub\n" },
},
});
await unlink(`${tshark}.retarget`);
const fresh = await evidenceResult(provider, request);
expect(fresh.client).toMatchObject({
tshark_command: tshark,
tshark_resolved_path: await realpath(replacement),
});
expect(first.client).toMatchObject({
tshark_resolved_path: await realpath(source),
});
},
);
const evidenceResult = async (
provider: PcapProvider,
request: PcapRequest,
): Promise<Record<string, unknown>> => {
const result = await provider.execute(request);
if (!result.ok) throw new Error(result.error.message);
return result.value.result as Record<string, unknown>;
};
// Windows keeps PATH/PATHEXT resolution in the launcher; POSIX hosts locate the file.
it.skipIf(isWindows)(
"runs and reports the tools located on PATH rather than bare names",
async () => {
const directory = await createTestTempDirectory("rea-pcap-path-");
await symlink(tsharkStub, join(directory, "tshark"));
await symlink(capinfosStub, join(directory, "capinfos"));
const provider = new PcapProvider({
environment: { PATH: `relative-entry:${directory}` },
});
onTestFinished(() => provider.close());
const result = await evidenceResult(provider, {
operation: "inspect_pcap_client",
input: {},
});
expect(result.client).toMatchObject({
tshark_command: join(directory, "tshark"),
tshark_source: "path",
tshark_version: expect.stringContaining("4.6.6") as unknown,
tshark_resolved_path: await realpath(tsharkStub),
tshark_sha256: createHash("sha256")
.update(await readFile(join(directory, "tshark")))
.digest("hex"),
tshark_identity_status: "verified",
capinfos_command: join(directory, "capinfos"),
capinfos_source: "path",
capinfos_resolved_path: await realpath(capinfosStub),
capinfos_sha256: createHash("sha256")
.update(await readFile(join(directory, "capinfos")))
.digest("hex"),
capinfos_identity_status: "verified",
});
},
);
it("inspects both tools through cross-platform stubs", async () => {
const result = await evidenceResult(providerOver(), {
operation: "inspect_pcap_client",
input: {},
});
expect(result.client).toMatchObject({
tshark_source: "environment",
tshark_version: expect.stringContaining("4.6.6") as unknown,
tshark_resolved_path: await realpath(tsharkStub),
capinfos_source: "environment",
capinfos_version: expect.stringContaining("4.6.6") as unknown,
capinfos_resolved_path: await realpath(capinfosStub),
tshark_sha256: createHash("sha256")
.update(await readFile(tsharkStub))
.digest("hex"),
tshark_identity_status: "verified",
capinfos_sha256: createHash("sha256")
.update(await readFile(capinfosStub))
.digest("hex"),
capinfos_identity_status: "verified",
});
});
it("projects a missing configured tool as a capability gap", async () => {
const provider = new PcapProvider({
environment: {
REA_TSHARK_COMMAND: tsharkStub,
REA_CAPINFOS_COMMAND: join(import.meta.dirname, "missing-capinfos"),
},
});
onTestFinished(() => provider.close());
const result = await provider.execute({
operation: "inspect_pcap_client",
input: {},
});
expect(result.ok).toBe(false);
if (result.ok) return;
const projected = projectAnalysisError(result.error);
expect(projected).toMatchObject({
code: "capability_unavailable",
details: { provider_id: "pcap", operation: "inspect_pcap_client" },
});
expect(projected.message).toContain(TSHARK_COMMAND_ENV);
expect(projected.message).toContain(CAPINFOS_COMMAND_ENV);
expect(projected.message).toContain("Wireshark 4.x");
expect(projected.message).toContain("PATH");
expect(projected.message).toContain("absolute");
expect(projected.message).toContain("does not install");
});
it.skipIf(isWindows)(
"rejects TShark that changes during its version probe",
async () => {
const directory = await createTestTempDirectory("rea-pcap-mutated-tool-");
const tshark = join(directory, "tshark");
const capinfos = join(directory, "capinfos");
await writeFile(
tshark,
`#!/bin/sh
if [ "$1" = "--version" ]; then
printf 'TShark (Wireshark) 4.6.6-stub\\n'
printf '\\n# changed during version probe\\n' >> "$0"
exit 0
fi
exit 2
`,
{ mode: 0o755 },
);
await symlink(capinfosStub, capinfos);
const provider = new PcapProvider({
environment: {
REA_TSHARK_COMMAND: tshark,
REA_CAPINFOS_COMMAND: capinfos,
},
});
onTestFinished(() => provider.close());
const result = await provider.execute({
operation: "inspect_pcap_client",
input: {},
});
expect(result.ok).toBe(false);
if (!result.ok)
expect(projectAnalysisError(result.error)).toMatchObject({
code: "artifact_changed",
});
},
);
it.each([0, 7])(
"retains failed-probe cleanup across admission retry and resolves after owner close: exit=%s",
async (exitCode) => {
let mayClean = false;
let launches = 0;
const launcher: PcapCommandLauncher = async (spawn) => {
const launched = await spawnOwnedProviderProcess({
...spawn,
command: process.execPath,
arguments: [
"-e",
`process.stdout.write("TShark (Wireshark) 4.6.6\\n"); process.exitCode = ${exitCode}`,
],
expectedCommand: process.execPath,
});
launches++;
if (launches !== 1) return launched;
const cleanup =
launched.cleanup ??
(() => cleanupOwnedProcessGroup(launched.ownership));
return {
...launched,
cleanup: async () => {
const result = await cleanup();
if (!mayClean) throw new Error("injected tool-probe cleanup failure");
return result;
},
};
};
const provider = new PcapProvider({
environment: {
REA_TSHARK_COMMAND: tsharkStub,
REA_CAPINFOS_COMMAND: capinfosStub,
},
launcher,
});
onTestFinished(async () => {
mayClean = true;
await provider.close();
});
const result = await provider.execute({
operation: "inspect_pcap_client",
input: {},
});
if (result.ok) throw new Error("Expected retained tool-probe cleanup");
expect(result.error.cleanupIncomplete).toBe(true);
if (exitCode === 0)
expect(result.error.diagnostics).toMatchObject({
reason: "injected tool-probe cleanup failure",
});
else {
expect(result.error).toMatchObject({
_tag: "AnalysisCapabilityUnavailableError",
capturedOutput: { exit_code: 7 },
});
}
expect(projectAnalysisError(result.error).code).toBe("cleanup_incomplete");
const retry = await provider.execute({
operation: "inspect_pcap_client",
input: {},
});
if (retry.ok)
throw new Error("Expected retained cleanup to block admission");
expect(projectAnalysisError(retry.error)).toMatchObject({
code: "cleanup_incomplete",
details: {
resources: expect.arrayContaining([...result.error.cleanupResources]),
},
});
expect(retry.error.cleanupResources.length).toBeGreaterThan(0);
expect(launches).toBe(1);
mayClean = true;
await provider.close();
expect(result.error.cleanupIncomplete).toBe(false);
expect(retry.error.cleanupIncomplete).toBe(false);
expect(launches).toBe(1);
},
);
it.runIf(hasNativeBound)(
"inspects a capture with metadata, endpoints, DNS, and protocol facts",
async () => {
const root = await createTestTempDirectory("rea-pcap-x-");
const capturePath = join(root, "capture.pcap");
const captureBytes = "cross-platform-pcap-fixture";
await writeFile(capturePath, captureBytes);
const result = await evidenceResult(providerOver(), {
operation: "inspect_pcap_capture",
input: { path: capturePath },
});
expect(result.target).toMatchObject({
path: capturePath,
bytes: captureBytes.length,
sha256: createHash("sha256").update(captureBytes).digest("hex"),
});
expect(result.file_type).toContain("pcap");
expect(result.packet_count).toBe(3);
expect(result.capture_start).toBe("2026-10-11T14:13:40.608505Z");
expect(result.capture_end).toBe("2026-10-11T14:13:40.981978Z");
expect((result.endpoints as unknown[]).length).toBeGreaterThan(0);
expect((result.protocol_distribution as unknown[]).length).toBeGreaterThan(
0,
);
expect(result.coverage).toBe("complete");
},
);
// Hosts without a native bound refuse before touching the path; that order
// is pinned by the unsupported-boundary test below.
it.runIf(hasNativeBound)(
"rejects a path that is not a readable file",
async () => {
const result = await providerOver().execute({
operation: "inspect_pcap_capture",
input: { path: "/nonexistent/definitely-missing.pcap" },
});
expect(result.ok).toBe(false);
if (result.ok) return;
expect(projectAnalysisError(result.error)).toMatchObject({
code: "invalid_request",
});
},
);
it("reports a missing binary as a capability gap", async () => {
const provider = new PcapProvider({
environment: { REA_TSHARK_COMMAND: "/nonexistent/tshark" },
});
expect(await provider.inspectAvailability()).toMatchObject({
status: "unavailable",
code: "executable_missing",
});
});
it("cancels before spawning anything", async () => {
const controller = new AbortController();
controller.abort();
const result = await providerOver().execute(
{ operation: "inspect_pcap_capture", input: { path: "/any/capture.pcap" } },
{ signal: controller.signal },
);
expect(result.ok).toBe(false);
if (result.ok) return;
expect(projectAnalysisError(result.error)).toMatchObject({
code: "cancelled",
});
});
it.runIf(!hasNativeBound)(
"reports an unsupported native memory boundary before reading the capture",
async () => {
const provider = providerOver();
expect(await provider.inspectAvailability()).toMatchObject({
status: "unavailable",
code: "unsupported_host",
});
const result = await provider.execute({
operation: "inspect_pcap_capture",
input: { path: "/not-opened/capture.pcap" },
});
expect(result.ok).toBe(false);
if (result.ok) return;
const projected = projectAnalysisError(result.error);
expect(projected).toMatchObject({
code: "capability_unavailable",
details: { provider_id: "pcap", operation: "inspect_pcap_capture" },
});
// The refusal concerns this host, so it must not suggest another target.
const reason =
projected.details &&
"reason" in projected.details &&
typeof projected.details.reason === "string"
? projected.details.reason
: "";
expect(reason).toContain("Linux");
expect(reason).toContain("util-linux");
expect(reason).toContain("prlimit");
expect(reason).toContain("Windows");
expect(reason).toContain("owned");
expect(reason).toContain("Job Objects");
expect(projected.remediation.action).toBe(projected.message);
},
);
it("retains a verification admission owner before translating cancellation", async () => {
const root = await createTestTempDirectory("rea-pcap-verify-admission-");
const path = join(root, "held.bin");
await writeFile(path, "held capture bytes");
const handle = await open(path, "r");
const owner = new OwnedFileHandle(handle);
const controller = new AbortController();
const reason = null;
const snapshot = await PcapSnapshot.create();
const admission = vi
.spyOn(regularFile, "openRegularFile")
.mockImplementationOnce(async () => {
controller.abort(reason);
throw new regularFile.RegularFileAdmissionFailure(
snapshot.path,
new regularFile.RegularFileOperationCancelled(reason),
new Error("admission close unavailable"),
owner,
);
});
try {
await expect(
snapshot.verify({ bytes: 0, sha256: "unused" }, path, controller.signal),
).rejects.toMatchObject({ _tag: "AnalysisCancelledError", cause: reason });
expect(handle.fd).toBeGreaterThanOrEqual(0);
await snapshot.close();
expect(handle.fd).toBe(-1);
} finally {
admission.mockRestore();
await snapshot.close();
await owner.close();
}
});
it.each(["tshark", "capinfos"] as const)(
"classifies directory-selected %s as a configuration refusal with its path",
async (tool) => {
const root = await createTestTempDirectory("rea-pcap-tool-directory-");
const provider = new PcapProvider({
environment: {
REA_TSHARK_COMMAND: tool === "tshark" ? root : tsharkStub,
REA_CAPINFOS_COMMAND: tool === "capinfos" ? root : capinfosStub,
},
});
onTestFinished(() => provider.close());
const result = await provider.execute({
operation: "inspect_pcap_client",
input: {},
});
if (result.ok) throw new Error("Expected selected tool admission refusal");
expect(projectAnalysisError(result.error)).toMatchObject({
code: "capability_unavailable",
message: expect.stringContaining(root),
details: { provider_id: "pcap", operation: "inspect_pcap_client" },
});
expect(projectAnalysisError(result.error).message).toContain(
"Wireshark 4.x",
);
},
);