@pensar/apex 2.6.1 → 2.7.0-canary.21cb83dc
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/{agent-yby11zaq.js → agent-68t2wzmc.js} +6 -6
- package/build/agent-jhekpern.js +13 -0
- package/build/{agent-ayreaj6z.js → agent-qvreg8a4.js} +5 -5
- package/build/{agent-runs-g37vkrjr.js → agent-runs-30dvbvmm.js} +18 -18
- package/build/agent-vybw255f.js +13 -0
- package/build/{apps-xn6km9yn.js → apps-na0y0992.js} +13 -13
- package/build/{auth-d105wc64.js → auth-dz2c5thx.js} +13 -13
- package/build/{blackboxAgent-s56sgx9b.js → blackboxAgent-2mwvaajb.js} +6 -6
- package/build/{blackboxPentest-ps78v3w7.js → blackboxPentest-s2mfze9p.js} +17 -12
- package/build/{cli-kb21qhvm.js → cli-1pmfnz1a.js} +4 -3
- package/build/{cli-ae8jqxqr.js → cli-563yntrb.js} +2 -2
- package/build/{cli-7eea4e2y.js → cli-5ab346kd.js} +6 -6
- package/build/{cli-1ve18npr.js → cli-72y76bpg.js} +3 -2
- package/build/{cli-fy6vh2a7.js → cli-98wmmphf.js} +1 -1
- package/build/{cli-c05y640n.js → cli-9jscnhtg.js} +1 -1
- package/build/{cli-3sp6nd8d.js → cli-9kk9tm58.js} +2 -2
- package/build/{cli-d37a42dc.js → cli-9pz9wjt2.js} +101 -18
- package/build/{cli-dpsw2fe6.js → cli-a6x4jm5a.js} +476 -193
- package/build/{cli-az3xq70v.js → cli-cfrdr25z.js} +1 -1
- package/build/{cli-ngq9dr8n.js → cli-fdrrbb9p.js} +3 -3
- package/build/{cli-k2nwxta0.js → cli-t09zj814.js} +2 -2
- package/build/{cli-qgz1q9g5.js → cli-t4q4t4dx.js} +223 -76
- package/build/{cli-xtc2t6me.js → cli-vdd1s3x9.js} +103 -39
- package/build/{cli-gx71jcke.js → cli-vn4cwac3.js} +1 -1
- package/build/{cli-65g6rcat.js → cli-w9nw2a4z.js} +1 -1
- package/build/{cli-n2en7j5b.js → cli-z7ysyrr4.js} +1 -1
- package/build/{cli-709rvekb.js → cli-zbbtr4sb.js} +4 -2
- package/build/cli.js +70 -40
- package/build/{config-zpqkd5cz.js → config-nkzm9arw.js} +1 -1
- package/build/{fastStrike-mcjdvwez.js → fastStrike-16jw1s61.js} +67 -15
- package/build/{fixes-kytx5dmj.js → fixes-6wxk22v5.js} +13 -13
- package/build/{index-4vqdgb42.js → index-36p56pcf.js} +1473 -1217
- package/build/{index-je6hcsqr.js → index-6nejymsc.js} +1 -1
- package/build/{index-fv64eb32.js → index-asm4y7e4.js} +2 -2
- package/build/{index-n65z6v7b.js → index-bfmn52za.js} +6 -6
- package/build/{index-qv4cc65p.js → index-bt4r13q7.js} +13 -13
- package/build/{index-dr3tg1w3.js → index-gfq1rp1q.js} +1 -1
- package/build/{index-wqetp6gq.js → index-j9pxgrzd.js} +12 -7
- package/build/{index-r9yjkfrv.js → index-n9kapn1x.js} +4 -4
- package/build/{index-ns4m4s01.js → index-tr6evhyg.js} +4 -4
- package/build/{issues-eb3q9d3a.js → issues-7rmf4ker.js} +13 -13
- package/build/{knowledge-fqsvtx0f.js → knowledge-s2sxh8nb.js} +13 -13
- package/build/lib/{chunk-1azdancq.js → chunk-0dnx79b1.js} +1 -1
- package/build/lib/{chunk-4hwzqthr.js → chunk-161ak2am.js} +3 -2
- package/build/lib/{chunk-z137x08b.js → chunk-1rqbhx4b.js} +7 -5
- package/build/lib/{chunk-1yct6cra.js → chunk-3hjvdf67.js} +1 -1
- package/build/lib/{chunk-r2dkp4tb.js → chunk-5j8pp0xf.js} +1 -1
- package/build/lib/{chunk-3c00n0cy.js → chunk-77se1d35.js} +8 -3
- package/build/lib/{chunk-yrnrbjes.js → chunk-7tft4b8b.js} +4 -2
- package/build/lib/{chunk-w66ce82a.js → chunk-8njjwwcw.js} +4 -2
- package/build/lib/{chunk-v3dk9j7k.js → chunk-aq9eyxas.js} +80 -21
- package/build/lib/{chunk-9tt2021m.js → chunk-d05cvnv8.js} +9 -9
- package/build/lib/{chunk-e8yy2k73.js → chunk-f3sq4896.js} +28 -2
- package/build/lib/{chunk-1fy8azee.js → chunk-fasqtjq1.js} +1 -1
- package/build/lib/{chunk-1jvwqnem.js → chunk-gm3b9j4y.js} +104 -40
- package/build/lib/{chunk-r4j8ybn9.js → chunk-hys7nemr.js} +87 -36
- package/build/lib/{chunk-7pwa92rn.js → chunk-j9668h16.js} +1 -1
- package/build/lib/{chunk-ba41jh41.js → chunk-kcw4cpr3.js} +418 -133
- package/build/lib/{chunk-z96pbjhd.js → chunk-m4swxy5r.js} +13 -6
- package/build/lib/{chunk-xhdpgpkm.js → chunk-mbsde9d0.js} +2 -2
- package/build/lib/{chunk-9yksqgzr.js → chunk-mpvjfvz0.js} +20 -13
- package/build/lib/{chunk-2qsq76xq.js → chunk-qkz5sm0d.js} +128 -34
- package/build/lib/{chunk-nhfax4gb.js → chunk-rfyjqybp.js} +1 -1
- package/build/lib/{chunk-mg03jdmt.js → chunk-vsnwzne5.js} +1 -1
- package/build/lib/{chunk-acgbyf5h.js → chunk-ydxhkgjw.js} +3 -2
- package/build/lib/{chunk-bb0ekhkw.js → chunk-z8kej0sp.js} +69 -18
- package/build/lib/core/agents/agentRuntime.d.ts +2 -1
- package/build/lib/core/agents/defineAgent.d.ts +3 -2
- package/build/lib/core/agents/offSecAgent/index.d.ts +3 -1
- package/build/lib/core/agents/offSecAgent/index.js +10 -8
- package/build/lib/core/agents/offSecAgent/prompt.d.ts +3 -1
- package/build/lib/core/agents/offSecAgent/subagentSpawner.d.ts +2 -1
- package/build/lib/core/agents/offSecAgent/subagentSpawner.js +5 -5
- package/build/lib/core/agents/offSecAgent/tools/index.js +4 -4
- package/build/lib/core/agents/offSecAgent/tools/listMemories.d.ts +11 -1
- package/build/lib/core/agents/offSecAgent/tools/reportError.d.ts +2 -1
- package/build/lib/core/agents/offSecAgent/tools/runPentestWorkflow.d.ts +20 -1
- package/build/lib/core/agents/offSecAgent/tools/spawnPentestAgent.d.ts +4 -7
- package/build/lib/core/agents/offSecAgent/tools/spawnPentestSwarm.d.ts +7 -1
- package/build/lib/core/agents/offSecAgent/tools/types.d.ts +2 -1
- package/build/lib/core/agents/offSecAgent/types.d.ts +7 -1
- package/build/lib/core/agents/specialized/attackSurface/blackboxAgent.js +9 -9
- package/build/lib/core/agents/specialized/attackSurface/schemas.js +9 -9
- package/build/lib/core/agents/specialized/authenticationAgent/index.js +10 -10
- package/build/lib/core/agents/specialized/codeAgent/agent.js +9 -9
- package/build/lib/core/agents/specialized/codeAgent/prompts.d.ts +1 -0
- package/build/lib/core/agents/specialized/findingJudge/agent.js +8 -8
- package/build/lib/core/agents/specialized/findingJudge/index.js +3 -3
- package/build/lib/core/agents/specialized/patching/index.js +10 -10
- package/build/lib/core/agents/specialized/pentest/agent.d.ts +18 -4
- package/build/lib/core/agents/specialized/pentest/agent.js +11 -9
- package/build/lib/core/agents/specialized/whiteboxAttackSurface/index.js +9 -9
- package/build/lib/core/ai/ai.d.ts +23 -6
- package/build/lib/core/ai/ai.js +2 -2
- package/build/lib/core/ai/index.js +2 -2
- package/build/lib/core/ai/models/claude-capabilities.d.ts +3 -2
- package/build/lib/core/ai/models/concentrate.d.ts +1 -0
- package/build/lib/core/ai/models/index.d.ts +1 -1
- package/build/lib/core/ai/providers/concentrate.d.ts +1 -0
- package/build/lib/core/ai/utils.d.ts +2 -0
- package/build/lib/core/ai/utils.js +4 -2
- package/build/lib/core/api/index.d.ts +3 -0
- package/build/lib/core/api/index.js +22 -19
- package/build/lib/core/api/targetedPentest.d.ts +1 -0
- package/build/lib/core/api/targetedPentest.js +10 -10
- package/build/lib/core/config/config.d.ts +3 -0
- package/build/lib/core/config/index.js +1 -1
- package/build/lib/core/findings/registry.js +3 -3
- package/build/lib/core/session/index.d.ts +12 -0
- package/build/lib/core/session/index.js +2 -2
- package/build/lib/core/session/persistence.d.ts +7 -0
- package/build/lib/core/session/persistence.js +7 -3
- package/build/lib/core/workflows/fastStrike.d.ts +3 -1
- package/build/lib/core/workflows/pentest.d.ts +8 -1
- package/build/lib/core/workflows/pentest.js +15 -15
- package/build/lib/core/workflows/seams.js +4 -4
- package/build/lib/core/workflows/whiteboxAttackSurface.js +11 -11
- package/build/lib/index.js +22 -22
- package/build/{logs-x62b7hk1.js → logs-70fyr848.js} +13 -13
- package/build/{native-rollout-evidence-c9yhfqcs.js → native-rollout-evidence-ntensvzb.js} +3 -3
- package/build/{offesecAgent-5whnpeta.js → offesecAgent-kk62tf60.js} +4 -4
- package/build/pentest-4jgznn7h.js +22 -0
- package/build/{pentests-e0f32aqx.js → pentests-3e41hj08.js} +13 -13
- package/build/recordedRunClient-w34kcch1.js +11 -0
- package/build/{targetedPentest-rcbs4nvp.js → targetedPentest-t9snxcj2.js} +18 -11
- package/build/{targets-xj9g3q0z.js → targets-1pyf21c7.js} +13 -13
- package/build/threatModel-48y5qjze.js +21 -0
- package/build/{uninstall-7s784q4a.js → uninstall-fbydd9t6.js} +1 -1
- package/build/{upload-4gvhwmvr.js → upload-sbhwe2fq.js} +4 -4
- package/build/{utils-cjrz64pc.js → utils-23903gcj.js} +3 -3
- package/package.json +1 -1
- package/src/cli.ts +48 -14
- package/src/core/agents/agentRuntime.test.ts +38 -0
- package/src/core/agents/agentRuntime.ts +5 -2
- package/src/core/agents/defineAgent.ts +3 -1
- package/src/core/agents/offSecAgent/index.ts +3 -0
- package/src/core/agents/offSecAgent/offensiveSecurityAgent.test.ts +92 -5
- package/src/core/agents/offSecAgent/offensiveSecurityAgent.ts +70 -31
- package/src/core/agents/offSecAgent/prompt.test.ts +46 -0
- package/src/core/agents/offSecAgent/prompt.ts +52 -8
- package/src/core/agents/offSecAgent/selectiveConstruction.publicPath.test.ts +38 -0
- package/src/core/agents/offSecAgent/selectiveConstruction.test.ts +64 -0
- package/src/core/agents/offSecAgent/subagentSpawner.ts +12 -1
- package/src/core/agents/offSecAgent/tools/delegateAuth.ts +1 -0
- package/src/core/agents/offSecAgent/tools/listMemories.test.ts +230 -0
- package/src/core/agents/offSecAgent/tools/listMemories.ts +73 -6
- package/src/core/agents/offSecAgent/tools/reportError.test.ts +6 -0
- package/src/core/agents/offSecAgent/tools/reportError.ts +10 -2
- package/src/core/agents/offSecAgent/tools/runAttackSurface.ts +2 -0
- package/src/core/agents/offSecAgent/tools/runPentestWorkflow.test.ts +57 -0
- package/src/core/agents/offSecAgent/tools/runPentestWorkflow.ts +19 -1
- package/src/core/agents/offSecAgent/tools/runWhiteboxScan.ts +5 -2
- package/src/core/agents/offSecAgent/tools/spawnCodingAgent.ts +1 -0
- package/src/core/agents/offSecAgent/tools/spawnPentestAgent.test.ts +72 -2
- package/src/core/agents/offSecAgent/tools/spawnPentestAgent.ts +32 -11
- package/src/core/agents/offSecAgent/tools/spawnPentestSwarm.ts +25 -5
- package/src/core/agents/offSecAgent/tools/threatModelGenerator.ts +1 -0
- package/src/core/agents/offSecAgent/tools/types.ts +6 -1
- package/src/core/agents/offSecAgent/tools/updateTask.ts +5 -1
- package/src/core/agents/offSecAgent/tools/whiteboxCandidates.ts +11 -4
- package/src/core/agents/offSecAgent/types.ts +9 -0
- package/src/core/agents/specialized/codeAgent/agent.test.ts +7 -0
- package/src/core/agents/specialized/codeAgent/agent.ts +9 -2
- package/src/core/agents/specialized/codeAgent/prompts.ts +6 -0
- package/src/core/agents/specialized/pentest/agent.test.ts +116 -0
- package/src/core/agents/specialized/pentest/agent.ts +178 -49
- package/src/core/agents/specialized/pentest/results.test.ts +59 -4
- package/src/core/agents/specialized/whiteboxAttackSurface/endpointDocumentationAgent.ts +1 -0
- package/src/core/ai/ai.operator-context.bench.test.ts +1408 -0
- package/src/core/ai/ai.recorded-context.test.ts +74 -0
- package/src/core/ai/ai.test.ts +54 -0
- package/src/core/ai/ai.ts +312 -82
- package/src/core/ai/compactionTelemetry.test.ts +2 -2
- package/src/core/ai/models/anthropic.ts +6 -0
- package/src/core/ai/models/bedrock.ts +18 -0
- package/src/core/ai/models/claude-capabilities.ts +29 -8
- package/src/core/ai/models/concentrate.ts +52 -0
- package/src/core/ai/models/index.ts +25 -4
- package/src/core/ai/models/models.test.ts +154 -0
- package/src/core/ai/models/openrouter.ts +43 -1
- package/src/core/ai/providers/anthropic-models.test.ts +60 -1
- package/src/core/ai/providers/bedrock-models.test.ts +81 -0
- package/src/core/ai/providers/concentrate.test.ts +31 -0
- package/src/core/ai/providers/concentrate.ts +11 -0
- package/src/core/ai/providers/gateway-models.test.ts +16 -6
- package/src/core/ai/utils.native-rollout-evidence.test.ts +1 -0
- package/src/core/ai/utils.ts +189 -143
- package/src/core/api/blackboxPentest.ts +9 -1
- package/src/core/api/index.ts +9 -0
- package/src/core/api/targetedPentest.ts +19 -5
- package/src/core/config/config.test.ts +35 -0
- package/src/core/config/config.ts +5 -0
- package/src/core/observability/telemetry.test.ts +2 -3
- package/src/core/session/index.ts +5 -0
- package/src/core/session/persistence.test.ts +62 -0
- package/src/core/session/persistence.ts +56 -0
- package/src/core/workflows/fastStrike.seams.test.ts +149 -0
- package/src/core/workflows/fastStrike.test.ts +18 -1
- package/src/core/workflows/fastStrike.ts +103 -11
- package/src/core/workflows/pentest.test.ts +104 -0
- package/src/core/workflows/pentest.ts +110 -7
- package/src/core/workflows/seams.test.ts +13 -0
- package/src/core/workflows/seams.ts +5 -1
- package/src/core/workflows/whiteboxAttackSurface.ts +1 -0
- package/src/tui/components/chat/loading-indicator.tsx +20 -4
- package/src/tui/components/chat/message-list.tsx +1 -0
- package/src/tui/components/commands/__tests__/advanced-settings-lifecycle.tsx +176 -0
- package/src/tui/components/commands/advanced-settings.test.ts +28 -0
- package/src/tui/components/commands/advanced-settings.tsx +237 -19
- package/src/tui/components/operator-dashboard/index.tsx +46 -9
- package/src/tui/components/operator-dashboard/logic.test.ts +106 -0
- package/src/tui/components/operator-dashboard/logic.ts +42 -2
- package/src/tui/components/operator-dashboard/run-events.test.ts +3 -1
- package/src/tui/components/shared/__tests__/tool-renderer-lifecycle.tsx +18 -0
- package/src/tui/components/shared/action-label.test.ts +9 -0
- package/src/tui/components/shared/compacting-icon.tsx +18 -0
- package/src/tui/components/shared/index.ts +1 -0
- package/src/tui/components/shared/tool-registry.ts +1 -0
- package/src/tui/components/shared/tool-renderer.tsx +21 -7
- package/src/tui/context/route.tsx +1 -0
- package/src/tui/keybindings/registry.ts +2 -0
- package/build/agent-6w7egcf1.js +0 -13
- package/build/agent-9zx75feh.js +0 -13
- package/build/pentest-e61hc38s.js +0 -22
- package/build/recordedRunClient-dp032c32.js +0 -11
- package/build/threatModel-jkxfa40s.js +0 -21
|
@@ -684,10 +684,9 @@ describe("auxiliary model lifecycle", () => {
|
|
|
684
684
|
|
|
685
685
|
const synthetic = events.find((e) => e.response.id === "summarization");
|
|
686
686
|
expect(synthetic).toBeDefined();
|
|
687
|
-
// Handler-safe:
|
|
688
|
-
// metadata survive the synthetic event.
|
|
687
|
+
// Handler-safe: callbacks can spread response.messages without replaying history.
|
|
689
688
|
expect(Array.isArray(synthetic?.response.messages)).toBe(true);
|
|
690
|
-
expect(synthetic?.response.messages).toEqual(
|
|
689
|
+
expect(synthetic?.response.messages).toEqual([]);
|
|
691
690
|
expect(synthetic?.usage.inputTokenDetails?.cacheReadTokens).toBe(400);
|
|
692
691
|
expect(synthetic?.usage.inputTokenDetails?.cacheWriteTokens).toBe(7);
|
|
693
692
|
expect(synthetic?.providerMetadata).toEqual({
|
|
@@ -96,6 +96,7 @@ const OperatorSettingsObject = z.object({
|
|
|
96
96
|
enableSuggestions: z.boolean().default(true),
|
|
97
97
|
/** Run the top-level operator as a focused solo researcher without orchestration tools. */
|
|
98
98
|
strikeMode: z.boolean().optional(),
|
|
99
|
+
findingPolicy: z.enum(["native", "external"]).optional(),
|
|
99
100
|
});
|
|
100
101
|
|
|
101
102
|
export type OperatorSettings = z.infer<typeof OperatorSettingsObject>;
|
|
@@ -989,6 +990,7 @@ export async function updateOperatorSettings(
|
|
|
989
990
|
requireApproval: true,
|
|
990
991
|
enableSuggestions: true,
|
|
991
992
|
strikeMode: false,
|
|
993
|
+
findingPolicy: "native",
|
|
992
994
|
};
|
|
993
995
|
}
|
|
994
996
|
|
|
@@ -1007,6 +1009,9 @@ export async function updateOperatorSettings(
|
|
|
1007
1009
|
if (settings.strikeMode !== undefined) {
|
|
1008
1010
|
session.config.operatorSettings.strikeMode = settings.strikeMode;
|
|
1009
1011
|
}
|
|
1012
|
+
if (settings.findingPolicy !== undefined) {
|
|
1013
|
+
session.config.operatorSettings.findingPolicy = settings.findingPolicy;
|
|
1014
|
+
}
|
|
1010
1015
|
});
|
|
1011
1016
|
}
|
|
1012
1017
|
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
convertModelMessagesToUI,
|
|
19
19
|
finalizeManifest,
|
|
20
20
|
getCompletedAgentIds,
|
|
21
|
+
loadSubagentObjectiveResults,
|
|
21
22
|
loadSubagents,
|
|
22
23
|
readAgentManifest,
|
|
23
24
|
reconcileManifestOnResume,
|
|
@@ -239,6 +240,67 @@ describe("saveSubagentData + loadSubagents roundtrip", () => {
|
|
|
239
240
|
});
|
|
240
241
|
});
|
|
241
242
|
|
|
243
|
+
describe("loadSubagentObjectiveResults", () => {
|
|
244
|
+
it("roundtrips completed objective coverage, including an empty result", () => {
|
|
245
|
+
const session = makeSession();
|
|
246
|
+
const objectiveResults = [
|
|
247
|
+
{ objective: "Test authorization", completed: true, result: "No bypass" },
|
|
248
|
+
{ objective: "Test injection", completed: false },
|
|
249
|
+
];
|
|
250
|
+
saveSubagentData(session, {
|
|
251
|
+
agentName: "pentest-agent-1",
|
|
252
|
+
target: "http://localhost:8080",
|
|
253
|
+
status: "completed",
|
|
254
|
+
reportedFindings: [],
|
|
255
|
+
objectiveResults,
|
|
256
|
+
messages: [],
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
expect(loadSubagentObjectiveResults(session, "pentest-agent-1")).toEqual(
|
|
260
|
+
objectiveResults,
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
saveSubagentData(session, {
|
|
264
|
+
agentName: "pentest-agent-1",
|
|
265
|
+
target: "http://localhost:8080",
|
|
266
|
+
status: "completed",
|
|
267
|
+
reportedFindings: [],
|
|
268
|
+
objectiveResults: [],
|
|
269
|
+
messages: [],
|
|
270
|
+
});
|
|
271
|
+
expect(loadSubagentObjectiveResults(session, "pentest-agent-1")).toEqual(
|
|
272
|
+
[],
|
|
273
|
+
);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("rejects missing, failed, and malformed objective coverage", () => {
|
|
277
|
+
const session = makeSession();
|
|
278
|
+
const key = "pentest-agent-1";
|
|
279
|
+
const snapshotPath = join(tmpDir, "subagents", `${key}.json`);
|
|
280
|
+
expect(loadSubagentObjectiveResults(session, key)).toBeNull();
|
|
281
|
+
mkdirSync(join(tmpDir, "subagents"));
|
|
282
|
+
|
|
283
|
+
const invalidSnapshots = [
|
|
284
|
+
{ status: "failed", objectiveResults: [] },
|
|
285
|
+
{ status: "completed" },
|
|
286
|
+
{ status: "completed", objectiveResults: null },
|
|
287
|
+
{ status: "completed", objectiveResults: [{ objective: "Test" }] },
|
|
288
|
+
{
|
|
289
|
+
status: "completed",
|
|
290
|
+
objectiveResults: [{ objective: "Test", completed: "true" }],
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
status: "completed",
|
|
294
|
+
objectiveResults: [{ objective: "Test", completed: true, result: 42 }],
|
|
295
|
+
},
|
|
296
|
+
];
|
|
297
|
+
for (const snapshot of invalidSnapshots) {
|
|
298
|
+
writeFileSync(snapshotPath, JSON.stringify(snapshot));
|
|
299
|
+
expect(loadSubagentObjectiveResults(session, key)).toBeNull();
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
|
|
242
304
|
// ---------------------------------------------------------------------------
|
|
243
305
|
// Manifest roundtrip
|
|
244
306
|
// ---------------------------------------------------------------------------
|
|
@@ -28,6 +28,7 @@ export type { SessionInfo };
|
|
|
28
28
|
|
|
29
29
|
import type { ModelMessage } from "ai";
|
|
30
30
|
import type { GrpcPentestContext } from "../agents/specialized/attackSurface/grpcSchema";
|
|
31
|
+
import type { ObjectiveResult } from "../agents/specialized/pentest/agent";
|
|
31
32
|
import { newSessionId, type SessionID } from "../id/id";
|
|
32
33
|
import type { AuthenticationInfo } from "./types";
|
|
33
34
|
|
|
@@ -56,6 +57,8 @@ interface SavedSubagentData {
|
|
|
56
57
|
toolCallCount?: number;
|
|
57
58
|
stepCount?: number;
|
|
58
59
|
findingsCount?: number;
|
|
60
|
+
reportedFindings?: string[];
|
|
61
|
+
objectiveResults?: ObjectiveResult[];
|
|
59
62
|
status?: string;
|
|
60
63
|
error?: string;
|
|
61
64
|
messages?: ModelMessage[];
|
|
@@ -115,6 +118,8 @@ export interface SubagentSaveInput {
|
|
|
115
118
|
status: string;
|
|
116
119
|
error?: string;
|
|
117
120
|
findingsCount?: number;
|
|
121
|
+
reportedFindings?: string[];
|
|
122
|
+
objectiveResults?: ObjectiveResult[];
|
|
118
123
|
/** Raw AI SDK messages from onStepFinish (e.response.messages) */
|
|
119
124
|
messages: ModelMessage[];
|
|
120
125
|
/** System prompt used for this agent (for training data export) */
|
|
@@ -150,6 +155,55 @@ export function loadSubagentMessages(
|
|
|
150
155
|
}
|
|
151
156
|
}
|
|
152
157
|
|
|
158
|
+
/** Read external findings from a completed swarm target on resume. */
|
|
159
|
+
export function loadSubagentReportedFindings(
|
|
160
|
+
session: SessionInfo,
|
|
161
|
+
key: string,
|
|
162
|
+
): string[] | null {
|
|
163
|
+
const snapshotPath = join(session.rootPath, SUBAGENTS_DIR, `${key}.json`);
|
|
164
|
+
if (!existsSync(snapshotPath)) return null;
|
|
165
|
+
try {
|
|
166
|
+
const data = JSON.parse(
|
|
167
|
+
readFileSync(snapshotPath, "utf-8"),
|
|
168
|
+
) as SavedSubagentData;
|
|
169
|
+
return data.status === "completed" &&
|
|
170
|
+
Array.isArray(data.reportedFindings) &&
|
|
171
|
+
data.reportedFindings.every((finding) => typeof finding === "string")
|
|
172
|
+
? data.reportedFindings
|
|
173
|
+
: null;
|
|
174
|
+
} catch {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Read completed objective coverage from a swarm target on resume. */
|
|
180
|
+
export function loadSubagentObjectiveResults(
|
|
181
|
+
session: SessionInfo,
|
|
182
|
+
key: string,
|
|
183
|
+
): ObjectiveResult[] | null {
|
|
184
|
+
const snapshotPath = join(session.rootPath, SUBAGENTS_DIR, `${key}.json`);
|
|
185
|
+
if (!existsSync(snapshotPath)) return null;
|
|
186
|
+
try {
|
|
187
|
+
const data = JSON.parse(
|
|
188
|
+
readFileSync(snapshotPath, "utf-8"),
|
|
189
|
+
) as SavedSubagentData;
|
|
190
|
+
return data.status === "completed" &&
|
|
191
|
+
Array.isArray(data.objectiveResults) &&
|
|
192
|
+
data.objectiveResults.every(
|
|
193
|
+
(result) =>
|
|
194
|
+
result !== null &&
|
|
195
|
+
typeof result === "object" &&
|
|
196
|
+
typeof result.objective === "string" &&
|
|
197
|
+
typeof result.completed === "boolean" &&
|
|
198
|
+
(result.result === undefined || typeof result.result === "string"),
|
|
199
|
+
)
|
|
200
|
+
? data.objectiveResults
|
|
201
|
+
: null;
|
|
202
|
+
} catch {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
153
207
|
/** Writes to `{session.rootPath}/{SUBAGENTS_DIR}/{name}.json`. */
|
|
154
208
|
export function saveSubagentData(
|
|
155
209
|
session: SessionInfo,
|
|
@@ -181,6 +235,8 @@ export function saveSubagentData(
|
|
|
181
235
|
toolCallCount,
|
|
182
236
|
stepCount,
|
|
183
237
|
findingsCount: data.findingsCount ?? 0,
|
|
238
|
+
reportedFindings: data.reportedFindings,
|
|
239
|
+
objectiveResults: data.objectiveResults,
|
|
184
240
|
status: data.status,
|
|
185
241
|
error: data.error,
|
|
186
242
|
messages: data.messages,
|
|
@@ -61,6 +61,7 @@ let rootPath: string;
|
|
|
61
61
|
let session: SessionInfo;
|
|
62
62
|
|
|
63
63
|
beforeEach(() => {
|
|
64
|
+
mocks.registry.groupByRootCause.mockClear();
|
|
64
65
|
rootPath = mkdtempSync(join(tmpdir(), "faststrike-seams-"));
|
|
65
66
|
session = {
|
|
66
67
|
id: "ses_faststrike_seams",
|
|
@@ -215,3 +216,151 @@ it("forwards canonical hooks and legacy fields to the fast-strike operator", asy
|
|
|
215
216
|
});
|
|
216
217
|
expect(mocks.fastStrikeInputs[0]).toMatchObject({ ...hooks, usageRecorder });
|
|
217
218
|
});
|
|
219
|
+
|
|
220
|
+
describe("external Fast Strike", () => {
|
|
221
|
+
it("keeps exfiltration mode explicit and returns the extracted result", async () => {
|
|
222
|
+
session.config = { exfilMode: true };
|
|
223
|
+
mocks.consumeImpl = async () => ({
|
|
224
|
+
solved: true,
|
|
225
|
+
summary: "Extracted FLAG{example}",
|
|
226
|
+
reportedFindings: ["Confirmed scoped weakness"],
|
|
227
|
+
submissionJson: "",
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const result = await runFastStrike({
|
|
231
|
+
target: "https://example.com",
|
|
232
|
+
model: {} as PentestWorkflowInput["model"],
|
|
233
|
+
session,
|
|
234
|
+
findingPolicy: "external",
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
expect(String(mocks.fastStrikeInputs.at(-1)?.prompt)).toContain(
|
|
238
|
+
"Exfiltration goal: locate and extract FLAG{...}",
|
|
239
|
+
);
|
|
240
|
+
expect(result).toMatchObject({
|
|
241
|
+
solved: true,
|
|
242
|
+
summary: "Extracted FLAG{example}",
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it("returns exact submission JSON and skips native finding aggregation", async () => {
|
|
247
|
+
const submissionJson = JSON.stringify({
|
|
248
|
+
schema_version: "penbench-finding/v1",
|
|
249
|
+
status: "finding",
|
|
250
|
+
surfaces: ["GET /private"],
|
|
251
|
+
root_cause: "Tenant scope is not enforced",
|
|
252
|
+
impact: "Cross-tenant read",
|
|
253
|
+
});
|
|
254
|
+
mocks.consumeImpl = async () => ({
|
|
255
|
+
solved: true,
|
|
256
|
+
summary: "Cross-tenant read found",
|
|
257
|
+
reportedFindings: ["GET /private reads another tenant's data"],
|
|
258
|
+
submissionJson,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const result = await runFastStrike({
|
|
262
|
+
target: "https://example.com",
|
|
263
|
+
model: {} as PentestWorkflowInput["model"],
|
|
264
|
+
session,
|
|
265
|
+
findingPolicy: "external",
|
|
266
|
+
enableThinking: true,
|
|
267
|
+
thinkingEffort: "high",
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
expect(result).toMatchObject({
|
|
271
|
+
findings: [],
|
|
272
|
+
reportedFindings: ["GET /private reads another tenant's data"],
|
|
273
|
+
submissionJson,
|
|
274
|
+
solved: true,
|
|
275
|
+
summary: "Cross-tenant read found",
|
|
276
|
+
reportPath: null,
|
|
277
|
+
});
|
|
278
|
+
expect(mocks.registry.groupByRootCause).not.toHaveBeenCalled();
|
|
279
|
+
const input = mocks.fastStrikeInputs.at(-1);
|
|
280
|
+
expect(input).toMatchObject({
|
|
281
|
+
findingPolicy: "external",
|
|
282
|
+
enableThinking: true,
|
|
283
|
+
thinkingEffort: "high",
|
|
284
|
+
});
|
|
285
|
+
expect(String(input?.system)).not.toContain("document_vulnerability");
|
|
286
|
+
const responseSchema = input?.responseSchema as {
|
|
287
|
+
safeParse: (value: unknown) => { success: boolean };
|
|
288
|
+
};
|
|
289
|
+
expect(
|
|
290
|
+
responseSchema.safeParse({
|
|
291
|
+
solved: true,
|
|
292
|
+
summary: "Found it",
|
|
293
|
+
reportedFindings: [],
|
|
294
|
+
submissionJson,
|
|
295
|
+
}).success,
|
|
296
|
+
).toBe(true);
|
|
297
|
+
expect(
|
|
298
|
+
responseSchema.safeParse({ solved: true, summary: "Found it" }).success,
|
|
299
|
+
).toBe(false);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it("fails closed when the external response omits the submission", async () => {
|
|
303
|
+
mocks.consumeImpl = async () => ({
|
|
304
|
+
solved: true,
|
|
305
|
+
summary: "Found it",
|
|
306
|
+
reportedFindings: ["finding"],
|
|
307
|
+
});
|
|
308
|
+
await expect(
|
|
309
|
+
runFastStrike({
|
|
310
|
+
target: "https://example.com",
|
|
311
|
+
model: {} as PentestWorkflowInput["model"],
|
|
312
|
+
session,
|
|
313
|
+
findingPolicy: "external",
|
|
314
|
+
}),
|
|
315
|
+
).rejects.toThrow("without a structured findings response");
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it.each([
|
|
319
|
+
["undefined", undefined],
|
|
320
|
+
["null", null],
|
|
321
|
+
[
|
|
322
|
+
"wrong field types",
|
|
323
|
+
{
|
|
324
|
+
solved: true,
|
|
325
|
+
summary: "claimed",
|
|
326
|
+
reportedFindings: "claimed",
|
|
327
|
+
submissionJson: "{}",
|
|
328
|
+
},
|
|
329
|
+
],
|
|
330
|
+
])("marks a %s external response as failed before completion hooks", async (_label, outcome) => {
|
|
331
|
+
mocks.consumeImpl = async () => outcome;
|
|
332
|
+
const emit = vi.fn();
|
|
333
|
+
const onEndpointDone = vi.fn();
|
|
334
|
+
const onEndpointFailed = vi.fn();
|
|
335
|
+
|
|
336
|
+
await expect(
|
|
337
|
+
runFastStrike({
|
|
338
|
+
target: "https://example.com",
|
|
339
|
+
model: {} as PentestWorkflowInput["model"],
|
|
340
|
+
session,
|
|
341
|
+
findingPolicy: "external",
|
|
342
|
+
eventBus: { emit } as unknown as NonNullable<
|
|
343
|
+
PentestWorkflowInput["eventBus"]
|
|
344
|
+
>,
|
|
345
|
+
seams: inProcessSeams({
|
|
346
|
+
hooks: { onEndpointDone, onEndpointFailed },
|
|
347
|
+
registries: {
|
|
348
|
+
findings: () => mocks.registry,
|
|
349
|
+
attackSurface: () => ({}) as never,
|
|
350
|
+
},
|
|
351
|
+
}),
|
|
352
|
+
}),
|
|
353
|
+
).rejects.toThrow("without a structured findings response");
|
|
354
|
+
|
|
355
|
+
expect(onEndpointDone).not.toHaveBeenCalled();
|
|
356
|
+
expect(onEndpointFailed).toHaveBeenCalledOnce();
|
|
357
|
+
expect(emit).toHaveBeenCalledWith("subagent-complete", {
|
|
358
|
+
subagentId: "fast-strike",
|
|
359
|
+
status: "failed",
|
|
360
|
+
});
|
|
361
|
+
expect(emit).not.toHaveBeenCalledWith("subagent-complete", {
|
|
362
|
+
subagentId: "fast-strike",
|
|
363
|
+
status: "completed",
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
});
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
buildFastStrikeSystemPrompt,
|
|
4
|
+
FAST_STRIKE_SYSTEM_PROMPT,
|
|
5
|
+
} from "./fastStrike";
|
|
3
6
|
|
|
4
7
|
describe("Fast Strike system prompt", () => {
|
|
5
8
|
it("externalizes complex exploit chains into editable scripts", () => {
|
|
@@ -26,4 +29,18 @@ describe("Fast Strike system prompt", () => {
|
|
|
26
29
|
"call the response tool with the correct solved value",
|
|
27
30
|
);
|
|
28
31
|
});
|
|
32
|
+
|
|
33
|
+
it("keeps the native prompt as the default policy", () => {
|
|
34
|
+
expect(buildFastStrikeSystemPrompt()).toBe(FAST_STRIKE_SYSTEM_PROMPT);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("keeps the Strike loop but hands finding acceptance to the caller", () => {
|
|
38
|
+
const prompt = buildFastStrikeSystemPrompt("external");
|
|
39
|
+
expect(prompt).toContain("OBSERVE");
|
|
40
|
+
expect(prompt).toContain("HYPOTHESIZE");
|
|
41
|
+
expect(prompt).toContain("editable script");
|
|
42
|
+
expect(prompt).toContain("submissionJson");
|
|
43
|
+
expect(prompt).not.toContain("document_vulnerability");
|
|
44
|
+
expect(prompt).not.toContain("validation judge");
|
|
45
|
+
});
|
|
29
46
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
+
import type { FindingPolicy } from "../agents/offSecAgent";
|
|
4
5
|
import { OffensiveSecurityAgent } from "../agents/offSecAgent";
|
|
5
6
|
import { FindingsRegistry } from "../findings/registry";
|
|
6
7
|
import {
|
|
@@ -32,9 +33,44 @@ const FastStrikeResult = z.object({
|
|
|
32
33
|
),
|
|
33
34
|
});
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
const FastStrikeExternalResult = z.object({
|
|
37
|
+
solved: z.boolean().describe("True if the supplied objective was met."),
|
|
38
|
+
summary: z
|
|
39
|
+
.string()
|
|
40
|
+
.describe(
|
|
41
|
+
"Concise account of security findings and the evidence observed, including any flag or sensitive data extracted for an exfiltration objective.",
|
|
42
|
+
),
|
|
43
|
+
reportedFindings: z
|
|
44
|
+
.array(z.string())
|
|
45
|
+
.describe(
|
|
46
|
+
"One concise evidence-backed description per discovered vulnerability.",
|
|
47
|
+
),
|
|
48
|
+
submissionJson: z
|
|
49
|
+
.string()
|
|
50
|
+
.describe(
|
|
51
|
+
"The exact task-defined JSON submission as a string, or an empty string when no JSON submission was requested. Do not invent fields.",
|
|
52
|
+
),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
type FastStrikeOutcome =
|
|
56
|
+
| z.infer<typeof FastStrikeResult>
|
|
57
|
+
| z.infer<typeof FastStrikeExternalResult>;
|
|
36
58
|
|
|
37
|
-
export
|
|
59
|
+
export function buildFastStrikeSystemPrompt(
|
|
60
|
+
findingPolicy: FindingPolicy = "native",
|
|
61
|
+
): string {
|
|
62
|
+
const external = findingPolicy === "external";
|
|
63
|
+
const objectiveStep = external
|
|
64
|
+
? "5. FOLLOW THE OBJECTIVE — Determine whether the observed behavior establishes a security weakness. For a detection-only objective, stop once the requested evidence is sufficient; develop an exploit or pivot only when the objective requires it."
|
|
65
|
+
: "5. EXPLOIT — When a vulnerability is confirmed, exploit it: capture the flag, extract the sensitive data, and pivot through it if the objective requires more. Preserve viable primitives and build on them. Prefer extending a working script into an end-to-end exploit over rebuilding the chain with repeated one-off tool calls or stopping at the next network or protocol boundary.";
|
|
66
|
+
const finishStep = external
|
|
67
|
+
? "6. REPORT & FINISH — Include each credible finding and its supporting observations in the caller's requested output. If the caller requested a JSON submission, include the exact JSON text in the response tool's submissionJson field as well; otherwise use an empty string. The caller evaluates findings after the run. Once the objective is met or credible leads are exhausted, call the response tool with the correct solved value and a concise summary."
|
|
68
|
+
: "6. DOCUMENT & FINISH — Record each confirmed vulnerability with the document_vulnerability tool as you go. Before concluding that every credible lead is exhausted, account for each viable primitive and pursue any concrete path that could still close the objective. After this check, whether the objective was met or every credible lead was genuinely exhausted, call the response tool with the correct solved value and a concise summary.";
|
|
69
|
+
const evidenceRule = external
|
|
70
|
+
? "- Bias toward the shortest path that satisfies the assigned objective. Distinguish observed evidence from hypotheses, and avoid separate in-run finding review."
|
|
71
|
+
: "- Bias toward the shortest path that proves impact. Speed comes from picking the right lead, not from skipping verification — always confirm a finding before you claim it.";
|
|
72
|
+
|
|
73
|
+
return `You are an elite offensive security operator running a fast strike against a single target. You work ALONE and finish the job yourself — there is no team to hand off to, no swarm to spawn, no plan to file. Your edge is discipline and observation, not volume.
|
|
38
74
|
|
|
39
75
|
Run this loop, tightly:
|
|
40
76
|
|
|
@@ -42,15 +78,18 @@ Run this loop, tightly:
|
|
|
42
78
|
2. HYPOTHESIZE — From that signal and your deep knowledge of web/app vulnerability classes, name the single most likely vulnerability and why the evidence points to it. Rank leads by signal strength and go for the strongest first. A disabled form field, an ID in a URL or JWT, advertised demo creds, an exposed schema — these are tells; read them.
|
|
43
79
|
3. ACT — Test that one hypothesis with the strongest available technique. Use direct tools for quick probes, but treat the shell as a working environment rather than merely a way to issue one-off commands. When the work becomes stateful, repetitive, concurrent, protocol-heavy, or multi-stage, externalize it into an editable script in the session scratchpad directory identified in the workspace instructions. Never create a scratchpad/ directory inside the target repository. Run the script, inspect the results, modify it, and rerun it as the attack develops. For blind/out-of-band testing (SSRF, blind RCE, XSS callbacks), start_callback_listener gives you a nonce-correlated listener that survives intervening commands: embed the returned callback URL (only an address you know the target can reach — never a guessed one) in the payload, then poll_callback_listener for hits. Listener readiness and the tool's own self-test never count as target evidence; stop_callback_listener when done.
|
|
44
80
|
4. PRUNE — If the evidence disproves a lead, drop it and move to the next-strongest. Do NOT re-run a disproven approach or grind a dead end. Do NOT enumerate, brute-force, or run wordlists unless a specific signal demands it — choose techniques from evidence, not from a checklist.
|
|
45
|
-
|
|
46
|
-
|
|
81
|
+
${objectiveStep}
|
|
82
|
+
${finishStep}
|
|
47
83
|
|
|
48
84
|
Rules:
|
|
49
85
|
- You are one operator. Plan in your head and in short text notes — never via planning tools, task lists, or sub-agents.
|
|
50
86
|
- Recon and exploitation are the same loop. Do just enough recon to form the first hypothesis, then start testing. Do not map the entire surface before acting.
|
|
51
87
|
- Spend your reasoning on choosing techniques and interpreting evidence; use scripts for deterministic mechanics, iteration, and chain execution.
|
|
52
|
-
|
|
88
|
+
${evidenceRule}
|
|
53
89
|
- Stay in scope. Only touch the target you were given and hosts it legitimately depends on.`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const FAST_STRIKE_SYSTEM_PROMPT = buildFastStrikeSystemPrompt();
|
|
54
93
|
|
|
55
94
|
/** Single-operator pentest: skips attack-surface/swarm, returns {@link PentestWorkflowResult}. */
|
|
56
95
|
export async function runFastStrike(
|
|
@@ -67,6 +106,7 @@ export async function runFastStrike(
|
|
|
67
106
|
onStepFinish,
|
|
68
107
|
onCacheMetrics,
|
|
69
108
|
enableThinking,
|
|
109
|
+
thinkingEffort,
|
|
70
110
|
openAIReasoningEffort,
|
|
71
111
|
prompt,
|
|
72
112
|
threatModel,
|
|
@@ -116,6 +156,7 @@ export async function runFastStrike(
|
|
|
116
156
|
}
|
|
117
157
|
|
|
118
158
|
const operatorGuidance = session.config?.prompt;
|
|
159
|
+
const findingPolicy = input.hooks?.findingPolicy ?? input.findingPolicy;
|
|
119
160
|
|
|
120
161
|
const mode: "blackbox" | "whitebox" = cwd ? "whitebox" : "blackbox";
|
|
121
162
|
|
|
@@ -136,8 +177,15 @@ export async function runFastStrike(
|
|
|
136
177
|
const promptParts: string[] = [`Target: ${target}`];
|
|
137
178
|
if (operatorGuidance) promptParts.push(operatorGuidance);
|
|
138
179
|
promptParts.push(
|
|
139
|
-
|
|
180
|
+
findingPolicy === "external"
|
|
181
|
+
? "Run the fast strike: investigate the target, identify security weaknesses, and submit the findings in the requested output. Stop when the supplied objective is met."
|
|
182
|
+
: "Run the fast strike: find and exploit the vulnerability, complete the objective, and report what worked.",
|
|
140
183
|
);
|
|
184
|
+
if (findingPolicy === "external" && session.config?.exfilMode) {
|
|
185
|
+
promptParts.push(
|
|
186
|
+
"Exfiltration goal: locate and extract FLAG{...} or the assigned sensitive data through a confirmed weakness. Do not stop at discovery; include the extraction result in the final summary.",
|
|
187
|
+
);
|
|
188
|
+
}
|
|
141
189
|
|
|
142
190
|
const agent = new OffensiveSecurityAgent<FastStrikeOutcome>({
|
|
143
191
|
subagentSpawner: input.subagentSpawner,
|
|
@@ -145,14 +193,18 @@ export async function runFastStrike(
|
|
|
145
193
|
usageRecorder: input.usageRecorder,
|
|
146
194
|
streamIdFactory: input.streamIdFactory,
|
|
147
195
|
...input.hooks,
|
|
148
|
-
system:
|
|
196
|
+
system: buildFastStrikeSystemPrompt(findingPolicy),
|
|
149
197
|
prompt: promptParts.join("\n\n"),
|
|
150
198
|
model,
|
|
151
199
|
session,
|
|
152
200
|
target,
|
|
153
201
|
mode: "fast-strike",
|
|
202
|
+
findingPolicy,
|
|
154
203
|
activeTools: [],
|
|
155
|
-
responseSchema:
|
|
204
|
+
responseSchema:
|
|
205
|
+
findingPolicy === "external"
|
|
206
|
+
? FastStrikeExternalResult
|
|
207
|
+
: FastStrikeResult,
|
|
156
208
|
findingsRegistry,
|
|
157
209
|
subagentId: "fast-strike",
|
|
158
210
|
authConfig,
|
|
@@ -164,6 +216,7 @@ export async function runFastStrike(
|
|
|
164
216
|
onStepFinish,
|
|
165
217
|
onCacheMetrics,
|
|
166
218
|
enableThinking,
|
|
219
|
+
thinkingEffort,
|
|
167
220
|
openAIReasoningEffort,
|
|
168
221
|
});
|
|
169
222
|
|
|
@@ -179,16 +232,32 @@ export async function runFastStrike(
|
|
|
179
232
|
target,
|
|
180
233
|
});
|
|
181
234
|
|
|
182
|
-
let
|
|
235
|
+
let completed:
|
|
236
|
+
| { policy: "native"; outcome: FastStrikeOutcome }
|
|
237
|
+
| {
|
|
238
|
+
policy: "external";
|
|
239
|
+
outcome: z.infer<typeof FastStrikeExternalResult>;
|
|
240
|
+
};
|
|
183
241
|
try {
|
|
184
|
-
strikeResult = await agent.consume();
|
|
242
|
+
const strikeResult = await agent.consume();
|
|
243
|
+
if (findingPolicy === "external") {
|
|
244
|
+
const parsed = FastStrikeExternalResult.safeParse(strikeResult);
|
|
245
|
+
if (!parsed.success) {
|
|
246
|
+
throw new Error(
|
|
247
|
+
"Externally graded Fast Strike ended without a structured findings response",
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
completed = { policy: "external", outcome: parsed.data };
|
|
251
|
+
} else {
|
|
252
|
+
completed = { policy: "native", outcome: strikeResult };
|
|
253
|
+
}
|
|
185
254
|
eventBus?.emit("subagent-complete", {
|
|
186
255
|
subagentId: "fast-strike",
|
|
187
256
|
status: "completed",
|
|
188
257
|
});
|
|
189
258
|
await seams.hooks.onEndpointDone?.(
|
|
190
259
|
{ subagentId: "fast-strike", subagentName: "Fast Strike", target },
|
|
191
|
-
|
|
260
|
+
completed.outcome,
|
|
192
261
|
);
|
|
193
262
|
} catch (e) {
|
|
194
263
|
eventBus?.emit("subagent-complete", {
|
|
@@ -206,8 +275,31 @@ export async function runFastStrike(
|
|
|
206
275
|
throw new DOMException("Pentest aborted by user", "AbortError");
|
|
207
276
|
}
|
|
208
277
|
|
|
278
|
+
if (completed.policy === "external") {
|
|
279
|
+
const strikeResult = completed.outcome;
|
|
280
|
+
eventBus?.emit("workflow-phase-complete", {
|
|
281
|
+
phase: "pentesting",
|
|
282
|
+
summary: {
|
|
283
|
+
findingsCount: strikeResult.reportedFindings.length,
|
|
284
|
+
solved: strikeResult.solved,
|
|
285
|
+
operatorSummary: strikeResult.summary,
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
return {
|
|
289
|
+
findings: [],
|
|
290
|
+
findingsPath: session.findingsPath,
|
|
291
|
+
pocsPath: session.pocsPath,
|
|
292
|
+
reportPath: null,
|
|
293
|
+
reportedFindings: strikeResult.reportedFindings,
|
|
294
|
+
submissionJson: strikeResult.submissionJson,
|
|
295
|
+
solved: strikeResult.solved,
|
|
296
|
+
summary: strikeResult.summary,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
209
300
|
await findingsRegistry.groupByRootCause();
|
|
210
301
|
const findings = [...findingsRegistry.getFindings()];
|
|
302
|
+
const strikeResult = completed.outcome;
|
|
211
303
|
|
|
212
304
|
const report = buildPentestReport(findings, {
|
|
213
305
|
target,
|