* feat: add Frida instrumentation support * fix: resolve Frida CI checks * test: cover Frida contract validation branches * docs: link Frida instrumentation guide * fix(frida): handle cancellation cleanup safely * fix(frida): address PR review findings * fix(frida): preserve resume and message observations * fix(ci): use canonical registry for rolldown * fix(frida): declare foreign code effects * fix(frida): retain failed startup resources for cleanup retries Keep failed startup session and spawn handles owned until detach, resume, and remote lease cleanup complete. Serialize retries and release each remote reference once. Await CLI provider cleanup while preserving the primary failure and any completed observations. Add regressions for retained startup resources, concurrent cleanup, shared remote leases, and CLI cleanup outcomes. Validation: 41 focused tests pass on the PR head and its latest-main integration; serial static and docs checks pass. Real Linux Frida 17.23.2 workflows and the native spawn/controlled-failure cleanup probe pass. Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing) --------- Co-authored-by: N0zoM1z0 <1705004398@qq.com>
171 lines
5.2 KiB
TypeScript
171 lines
5.2 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import { TOOL_CONTRACTS } from "./toolContracts.js";
|
|
import { FRIDA_KEEPALIVE_INTERVAL_DESCRIPTION } from "./fridaToolContracts.js";
|
|
import { isValidFridaRemoteAddress } from "./fridaRemoteAddress.js";
|
|
|
|
const contract = (name: string) => {
|
|
const found = TOOL_CONTRACTS.find((candidate) => candidate.name === name);
|
|
if (found === undefined) throw new Error(`Missing contract ${name}`);
|
|
return found.inputSchema;
|
|
};
|
|
|
|
describe("Frida tool contracts", () => {
|
|
it("documents Frida Core keepalive intervals in seconds", () => {
|
|
expect(FRIDA_KEEPALIVE_INTERVAL_DESCRIPTION).toBe(
|
|
"Remote keepalive interval in seconds",
|
|
);
|
|
});
|
|
|
|
it("rejects incomplete or conflicting mode and source selectors", () => {
|
|
const start = contract("start_frida_session");
|
|
const load = contract("load_frida_script");
|
|
const instrument = contract("instrument_with_frida");
|
|
const processList = contract("list_frida_processes");
|
|
|
|
expect(start.safeParse({ mode: "attach", pid: 7 }).success).toBe(true);
|
|
expect(start.safeParse({ mode: "spawn", program: "app" }).success).toBe(
|
|
true,
|
|
);
|
|
for (const input of [
|
|
{ mode: "attach" },
|
|
{ mode: "attach", pid: 7, program: "app" },
|
|
{ mode: "spawn" },
|
|
{ mode: "spawn", program: "app", pid: 7 },
|
|
{
|
|
mode: "attach",
|
|
pid: 7,
|
|
device_id: "local",
|
|
remote: { address: "host" },
|
|
},
|
|
])
|
|
expect(start.safeParse(input).success).toBe(false);
|
|
|
|
expect(
|
|
load.safeParse({
|
|
session_id: "00000000-0000-4000-8000-000000000001",
|
|
source_kind: "inline",
|
|
source: "",
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
load.safeParse({
|
|
session_id: "00000000-0000-4000-8000-000000000001",
|
|
source_kind: "file",
|
|
path: "script.js",
|
|
}).success,
|
|
).toBe(true);
|
|
for (const input of [
|
|
{
|
|
session_id: "00000000-0000-4000-8000-000000000001",
|
|
source_kind: "inline",
|
|
},
|
|
{
|
|
session_id: "00000000-0000-4000-8000-000000000001",
|
|
source_kind: "inline",
|
|
source: "",
|
|
path: "script.js",
|
|
},
|
|
{
|
|
session_id: "00000000-0000-4000-8000-000000000001",
|
|
source_kind: "file",
|
|
},
|
|
])
|
|
expect(load.safeParse(input).success).toBe(false);
|
|
|
|
expect(
|
|
instrument.safeParse({
|
|
mode: "attach",
|
|
pid: 7,
|
|
source_kind: "inline",
|
|
source: "",
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
instrument.safeParse({
|
|
mode: "spawn",
|
|
program: "app",
|
|
source_kind: "file",
|
|
path: "script.js",
|
|
}).success,
|
|
).toBe(true);
|
|
for (const input of [
|
|
{
|
|
mode: "attach",
|
|
pid: 7,
|
|
source_kind: "file",
|
|
path: "script.js",
|
|
source: "",
|
|
},
|
|
{ mode: "spawn", program: "app", source_kind: "inline" },
|
|
{
|
|
mode: "spawn",
|
|
program: "app",
|
|
pid: 7,
|
|
source_kind: "inline",
|
|
source: "",
|
|
},
|
|
{
|
|
mode: "attach",
|
|
source_kind: "inline",
|
|
source: "",
|
|
device_id: "local",
|
|
remote: { address: "host" },
|
|
},
|
|
])
|
|
expect(instrument.safeParse(input).success).toBe(false);
|
|
|
|
expect(processList.safeParse({ device_id: "local" }).success).toBe(true);
|
|
expect(processList.safeParse({}).success).toBe(false);
|
|
expect(
|
|
processList.safeParse({ device_id: "local", remote: { address: "host" } })
|
|
.success,
|
|
).toBe(false);
|
|
});
|
|
|
|
it("publishes the Frida device, process, and session operations", () => {
|
|
expect(TOOL_CONTRACTS.map(({ name }) => name)).toEqual(
|
|
expect.arrayContaining([
|
|
"list_frida_devices",
|
|
"list_frida_processes",
|
|
"start_frida_session",
|
|
"load_frida_script",
|
|
"resume_frida_session",
|
|
"unload_frida_script",
|
|
"frida_session_status",
|
|
"close_frida_session",
|
|
"instrument_with_frida",
|
|
]),
|
|
);
|
|
});
|
|
|
|
it("declares target authority for every operation that changes instrumentation", () => {
|
|
for (const name of [
|
|
"start_frida_session",
|
|
"load_frida_script",
|
|
"resume_frida_session",
|
|
"unload_frida_script",
|
|
"close_frida_session",
|
|
"instrument_with_frida",
|
|
] as const) {
|
|
const contract = TOOL_CONTRACTS.find(
|
|
(candidate) => candidate.name === name,
|
|
);
|
|
expect(contract?.effects.mutatesTarget).toBe(true);
|
|
expect(contract?.effects.idempotent).toBe(false);
|
|
}
|
|
});
|
|
|
|
it("accepts Frida host addresses and rejects URL-like or malformed endpoints", () => {
|
|
expect(isValidFridaRemoteAddress("127.0.0.1:27042")).toBe(true);
|
|
expect(isValidFridaRemoteAddress("frida-target.example")).toBe(true);
|
|
expect(isValidFridaRemoteAddress("[::1]:27042")).toBe(true);
|
|
expect(isValidFridaRemoteAddress("tcp://127.0.0.1:27042")).toBe(false);
|
|
expect(isValidFridaRemoteAddress("127.0.0.1:70000")).toBe(false);
|
|
expect(isValidFridaRemoteAddress("127.0.0.1:0")).toBe(false);
|
|
expect(isValidFridaRemoteAddress("127.0.0.1:")).toBe(false);
|
|
expect(isValidFridaRemoteAddress("frida-target/path")).toBe(false);
|
|
expect(isValidFridaRemoteAddress("user@frida-target")).toBe(false);
|
|
});
|
|
});
|