@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.
Files changed (226) hide show
  1. package/build/{agent-yby11zaq.js → agent-68t2wzmc.js} +6 -6
  2. package/build/agent-jhekpern.js +13 -0
  3. package/build/{agent-ayreaj6z.js → agent-qvreg8a4.js} +5 -5
  4. package/build/{agent-runs-g37vkrjr.js → agent-runs-30dvbvmm.js} +18 -18
  5. package/build/agent-vybw255f.js +13 -0
  6. package/build/{apps-xn6km9yn.js → apps-na0y0992.js} +13 -13
  7. package/build/{auth-d105wc64.js → auth-dz2c5thx.js} +13 -13
  8. package/build/{blackboxAgent-s56sgx9b.js → blackboxAgent-2mwvaajb.js} +6 -6
  9. package/build/{blackboxPentest-ps78v3w7.js → blackboxPentest-s2mfze9p.js} +17 -12
  10. package/build/{cli-kb21qhvm.js → cli-1pmfnz1a.js} +4 -3
  11. package/build/{cli-ae8jqxqr.js → cli-563yntrb.js} +2 -2
  12. package/build/{cli-7eea4e2y.js → cli-5ab346kd.js} +6 -6
  13. package/build/{cli-1ve18npr.js → cli-72y76bpg.js} +3 -2
  14. package/build/{cli-fy6vh2a7.js → cli-98wmmphf.js} +1 -1
  15. package/build/{cli-c05y640n.js → cli-9jscnhtg.js} +1 -1
  16. package/build/{cli-3sp6nd8d.js → cli-9kk9tm58.js} +2 -2
  17. package/build/{cli-d37a42dc.js → cli-9pz9wjt2.js} +101 -18
  18. package/build/{cli-dpsw2fe6.js → cli-a6x4jm5a.js} +476 -193
  19. package/build/{cli-az3xq70v.js → cli-cfrdr25z.js} +1 -1
  20. package/build/{cli-ngq9dr8n.js → cli-fdrrbb9p.js} +3 -3
  21. package/build/{cli-k2nwxta0.js → cli-t09zj814.js} +2 -2
  22. package/build/{cli-qgz1q9g5.js → cli-t4q4t4dx.js} +223 -76
  23. package/build/{cli-xtc2t6me.js → cli-vdd1s3x9.js} +103 -39
  24. package/build/{cli-gx71jcke.js → cli-vn4cwac3.js} +1 -1
  25. package/build/{cli-65g6rcat.js → cli-w9nw2a4z.js} +1 -1
  26. package/build/{cli-n2en7j5b.js → cli-z7ysyrr4.js} +1 -1
  27. package/build/{cli-709rvekb.js → cli-zbbtr4sb.js} +4 -2
  28. package/build/cli.js +70 -40
  29. package/build/{config-zpqkd5cz.js → config-nkzm9arw.js} +1 -1
  30. package/build/{fastStrike-mcjdvwez.js → fastStrike-16jw1s61.js} +67 -15
  31. package/build/{fixes-kytx5dmj.js → fixes-6wxk22v5.js} +13 -13
  32. package/build/{index-4vqdgb42.js → index-36p56pcf.js} +1473 -1217
  33. package/build/{index-je6hcsqr.js → index-6nejymsc.js} +1 -1
  34. package/build/{index-fv64eb32.js → index-asm4y7e4.js} +2 -2
  35. package/build/{index-n65z6v7b.js → index-bfmn52za.js} +6 -6
  36. package/build/{index-qv4cc65p.js → index-bt4r13q7.js} +13 -13
  37. package/build/{index-dr3tg1w3.js → index-gfq1rp1q.js} +1 -1
  38. package/build/{index-wqetp6gq.js → index-j9pxgrzd.js} +12 -7
  39. package/build/{index-r9yjkfrv.js → index-n9kapn1x.js} +4 -4
  40. package/build/{index-ns4m4s01.js → index-tr6evhyg.js} +4 -4
  41. package/build/{issues-eb3q9d3a.js → issues-7rmf4ker.js} +13 -13
  42. package/build/{knowledge-fqsvtx0f.js → knowledge-s2sxh8nb.js} +13 -13
  43. package/build/lib/{chunk-1azdancq.js → chunk-0dnx79b1.js} +1 -1
  44. package/build/lib/{chunk-4hwzqthr.js → chunk-161ak2am.js} +3 -2
  45. package/build/lib/{chunk-z137x08b.js → chunk-1rqbhx4b.js} +7 -5
  46. package/build/lib/{chunk-1yct6cra.js → chunk-3hjvdf67.js} +1 -1
  47. package/build/lib/{chunk-r2dkp4tb.js → chunk-5j8pp0xf.js} +1 -1
  48. package/build/lib/{chunk-3c00n0cy.js → chunk-77se1d35.js} +8 -3
  49. package/build/lib/{chunk-yrnrbjes.js → chunk-7tft4b8b.js} +4 -2
  50. package/build/lib/{chunk-w66ce82a.js → chunk-8njjwwcw.js} +4 -2
  51. package/build/lib/{chunk-v3dk9j7k.js → chunk-aq9eyxas.js} +80 -21
  52. package/build/lib/{chunk-9tt2021m.js → chunk-d05cvnv8.js} +9 -9
  53. package/build/lib/{chunk-e8yy2k73.js → chunk-f3sq4896.js} +28 -2
  54. package/build/lib/{chunk-1fy8azee.js → chunk-fasqtjq1.js} +1 -1
  55. package/build/lib/{chunk-1jvwqnem.js → chunk-gm3b9j4y.js} +104 -40
  56. package/build/lib/{chunk-r4j8ybn9.js → chunk-hys7nemr.js} +87 -36
  57. package/build/lib/{chunk-7pwa92rn.js → chunk-j9668h16.js} +1 -1
  58. package/build/lib/{chunk-ba41jh41.js → chunk-kcw4cpr3.js} +418 -133
  59. package/build/lib/{chunk-z96pbjhd.js → chunk-m4swxy5r.js} +13 -6
  60. package/build/lib/{chunk-xhdpgpkm.js → chunk-mbsde9d0.js} +2 -2
  61. package/build/lib/{chunk-9yksqgzr.js → chunk-mpvjfvz0.js} +20 -13
  62. package/build/lib/{chunk-2qsq76xq.js → chunk-qkz5sm0d.js} +128 -34
  63. package/build/lib/{chunk-nhfax4gb.js → chunk-rfyjqybp.js} +1 -1
  64. package/build/lib/{chunk-mg03jdmt.js → chunk-vsnwzne5.js} +1 -1
  65. package/build/lib/{chunk-acgbyf5h.js → chunk-ydxhkgjw.js} +3 -2
  66. package/build/lib/{chunk-bb0ekhkw.js → chunk-z8kej0sp.js} +69 -18
  67. package/build/lib/core/agents/agentRuntime.d.ts +2 -1
  68. package/build/lib/core/agents/defineAgent.d.ts +3 -2
  69. package/build/lib/core/agents/offSecAgent/index.d.ts +3 -1
  70. package/build/lib/core/agents/offSecAgent/index.js +10 -8
  71. package/build/lib/core/agents/offSecAgent/prompt.d.ts +3 -1
  72. package/build/lib/core/agents/offSecAgent/subagentSpawner.d.ts +2 -1
  73. package/build/lib/core/agents/offSecAgent/subagentSpawner.js +5 -5
  74. package/build/lib/core/agents/offSecAgent/tools/index.js +4 -4
  75. package/build/lib/core/agents/offSecAgent/tools/listMemories.d.ts +11 -1
  76. package/build/lib/core/agents/offSecAgent/tools/reportError.d.ts +2 -1
  77. package/build/lib/core/agents/offSecAgent/tools/runPentestWorkflow.d.ts +20 -1
  78. package/build/lib/core/agents/offSecAgent/tools/spawnPentestAgent.d.ts +4 -7
  79. package/build/lib/core/agents/offSecAgent/tools/spawnPentestSwarm.d.ts +7 -1
  80. package/build/lib/core/agents/offSecAgent/tools/types.d.ts +2 -1
  81. package/build/lib/core/agents/offSecAgent/types.d.ts +7 -1
  82. package/build/lib/core/agents/specialized/attackSurface/blackboxAgent.js +9 -9
  83. package/build/lib/core/agents/specialized/attackSurface/schemas.js +9 -9
  84. package/build/lib/core/agents/specialized/authenticationAgent/index.js +10 -10
  85. package/build/lib/core/agents/specialized/codeAgent/agent.js +9 -9
  86. package/build/lib/core/agents/specialized/codeAgent/prompts.d.ts +1 -0
  87. package/build/lib/core/agents/specialized/findingJudge/agent.js +8 -8
  88. package/build/lib/core/agents/specialized/findingJudge/index.js +3 -3
  89. package/build/lib/core/agents/specialized/patching/index.js +10 -10
  90. package/build/lib/core/agents/specialized/pentest/agent.d.ts +18 -4
  91. package/build/lib/core/agents/specialized/pentest/agent.js +11 -9
  92. package/build/lib/core/agents/specialized/whiteboxAttackSurface/index.js +9 -9
  93. package/build/lib/core/ai/ai.d.ts +23 -6
  94. package/build/lib/core/ai/ai.js +2 -2
  95. package/build/lib/core/ai/index.js +2 -2
  96. package/build/lib/core/ai/models/claude-capabilities.d.ts +3 -2
  97. package/build/lib/core/ai/models/concentrate.d.ts +1 -0
  98. package/build/lib/core/ai/models/index.d.ts +1 -1
  99. package/build/lib/core/ai/providers/concentrate.d.ts +1 -0
  100. package/build/lib/core/ai/utils.d.ts +2 -0
  101. package/build/lib/core/ai/utils.js +4 -2
  102. package/build/lib/core/api/index.d.ts +3 -0
  103. package/build/lib/core/api/index.js +22 -19
  104. package/build/lib/core/api/targetedPentest.d.ts +1 -0
  105. package/build/lib/core/api/targetedPentest.js +10 -10
  106. package/build/lib/core/config/config.d.ts +3 -0
  107. package/build/lib/core/config/index.js +1 -1
  108. package/build/lib/core/findings/registry.js +3 -3
  109. package/build/lib/core/session/index.d.ts +12 -0
  110. package/build/lib/core/session/index.js +2 -2
  111. package/build/lib/core/session/persistence.d.ts +7 -0
  112. package/build/lib/core/session/persistence.js +7 -3
  113. package/build/lib/core/workflows/fastStrike.d.ts +3 -1
  114. package/build/lib/core/workflows/pentest.d.ts +8 -1
  115. package/build/lib/core/workflows/pentest.js +15 -15
  116. package/build/lib/core/workflows/seams.js +4 -4
  117. package/build/lib/core/workflows/whiteboxAttackSurface.js +11 -11
  118. package/build/lib/index.js +22 -22
  119. package/build/{logs-x62b7hk1.js → logs-70fyr848.js} +13 -13
  120. package/build/{native-rollout-evidence-c9yhfqcs.js → native-rollout-evidence-ntensvzb.js} +3 -3
  121. package/build/{offesecAgent-5whnpeta.js → offesecAgent-kk62tf60.js} +4 -4
  122. package/build/pentest-4jgznn7h.js +22 -0
  123. package/build/{pentests-e0f32aqx.js → pentests-3e41hj08.js} +13 -13
  124. package/build/recordedRunClient-w34kcch1.js +11 -0
  125. package/build/{targetedPentest-rcbs4nvp.js → targetedPentest-t9snxcj2.js} +18 -11
  126. package/build/{targets-xj9g3q0z.js → targets-1pyf21c7.js} +13 -13
  127. package/build/threatModel-48y5qjze.js +21 -0
  128. package/build/{uninstall-7s784q4a.js → uninstall-fbydd9t6.js} +1 -1
  129. package/build/{upload-4gvhwmvr.js → upload-sbhwe2fq.js} +4 -4
  130. package/build/{utils-cjrz64pc.js → utils-23903gcj.js} +3 -3
  131. package/package.json +1 -1
  132. package/src/cli.ts +48 -14
  133. package/src/core/agents/agentRuntime.test.ts +38 -0
  134. package/src/core/agents/agentRuntime.ts +5 -2
  135. package/src/core/agents/defineAgent.ts +3 -1
  136. package/src/core/agents/offSecAgent/index.ts +3 -0
  137. package/src/core/agents/offSecAgent/offensiveSecurityAgent.test.ts +92 -5
  138. package/src/core/agents/offSecAgent/offensiveSecurityAgent.ts +70 -31
  139. package/src/core/agents/offSecAgent/prompt.test.ts +46 -0
  140. package/src/core/agents/offSecAgent/prompt.ts +52 -8
  141. package/src/core/agents/offSecAgent/selectiveConstruction.publicPath.test.ts +38 -0
  142. package/src/core/agents/offSecAgent/selectiveConstruction.test.ts +64 -0
  143. package/src/core/agents/offSecAgent/subagentSpawner.ts +12 -1
  144. package/src/core/agents/offSecAgent/tools/delegateAuth.ts +1 -0
  145. package/src/core/agents/offSecAgent/tools/listMemories.test.ts +230 -0
  146. package/src/core/agents/offSecAgent/tools/listMemories.ts +73 -6
  147. package/src/core/agents/offSecAgent/tools/reportError.test.ts +6 -0
  148. package/src/core/agents/offSecAgent/tools/reportError.ts +10 -2
  149. package/src/core/agents/offSecAgent/tools/runAttackSurface.ts +2 -0
  150. package/src/core/agents/offSecAgent/tools/runPentestWorkflow.test.ts +57 -0
  151. package/src/core/agents/offSecAgent/tools/runPentestWorkflow.ts +19 -1
  152. package/src/core/agents/offSecAgent/tools/runWhiteboxScan.ts +5 -2
  153. package/src/core/agents/offSecAgent/tools/spawnCodingAgent.ts +1 -0
  154. package/src/core/agents/offSecAgent/tools/spawnPentestAgent.test.ts +72 -2
  155. package/src/core/agents/offSecAgent/tools/spawnPentestAgent.ts +32 -11
  156. package/src/core/agents/offSecAgent/tools/spawnPentestSwarm.ts +25 -5
  157. package/src/core/agents/offSecAgent/tools/threatModelGenerator.ts +1 -0
  158. package/src/core/agents/offSecAgent/tools/types.ts +6 -1
  159. package/src/core/agents/offSecAgent/tools/updateTask.ts +5 -1
  160. package/src/core/agents/offSecAgent/tools/whiteboxCandidates.ts +11 -4
  161. package/src/core/agents/offSecAgent/types.ts +9 -0
  162. package/src/core/agents/specialized/codeAgent/agent.test.ts +7 -0
  163. package/src/core/agents/specialized/codeAgent/agent.ts +9 -2
  164. package/src/core/agents/specialized/codeAgent/prompts.ts +6 -0
  165. package/src/core/agents/specialized/pentest/agent.test.ts +116 -0
  166. package/src/core/agents/specialized/pentest/agent.ts +178 -49
  167. package/src/core/agents/specialized/pentest/results.test.ts +59 -4
  168. package/src/core/agents/specialized/whiteboxAttackSurface/endpointDocumentationAgent.ts +1 -0
  169. package/src/core/ai/ai.operator-context.bench.test.ts +1408 -0
  170. package/src/core/ai/ai.recorded-context.test.ts +74 -0
  171. package/src/core/ai/ai.test.ts +54 -0
  172. package/src/core/ai/ai.ts +312 -82
  173. package/src/core/ai/compactionTelemetry.test.ts +2 -2
  174. package/src/core/ai/models/anthropic.ts +6 -0
  175. package/src/core/ai/models/bedrock.ts +18 -0
  176. package/src/core/ai/models/claude-capabilities.ts +29 -8
  177. package/src/core/ai/models/concentrate.ts +52 -0
  178. package/src/core/ai/models/index.ts +25 -4
  179. package/src/core/ai/models/models.test.ts +154 -0
  180. package/src/core/ai/models/openrouter.ts +43 -1
  181. package/src/core/ai/providers/anthropic-models.test.ts +60 -1
  182. package/src/core/ai/providers/bedrock-models.test.ts +81 -0
  183. package/src/core/ai/providers/concentrate.test.ts +31 -0
  184. package/src/core/ai/providers/concentrate.ts +11 -0
  185. package/src/core/ai/providers/gateway-models.test.ts +16 -6
  186. package/src/core/ai/utils.native-rollout-evidence.test.ts +1 -0
  187. package/src/core/ai/utils.ts +189 -143
  188. package/src/core/api/blackboxPentest.ts +9 -1
  189. package/src/core/api/index.ts +9 -0
  190. package/src/core/api/targetedPentest.ts +19 -5
  191. package/src/core/config/config.test.ts +35 -0
  192. package/src/core/config/config.ts +5 -0
  193. package/src/core/observability/telemetry.test.ts +2 -3
  194. package/src/core/session/index.ts +5 -0
  195. package/src/core/session/persistence.test.ts +62 -0
  196. package/src/core/session/persistence.ts +56 -0
  197. package/src/core/workflows/fastStrike.seams.test.ts +149 -0
  198. package/src/core/workflows/fastStrike.test.ts +18 -1
  199. package/src/core/workflows/fastStrike.ts +103 -11
  200. package/src/core/workflows/pentest.test.ts +104 -0
  201. package/src/core/workflows/pentest.ts +110 -7
  202. package/src/core/workflows/seams.test.ts +13 -0
  203. package/src/core/workflows/seams.ts +5 -1
  204. package/src/core/workflows/whiteboxAttackSurface.ts +1 -0
  205. package/src/tui/components/chat/loading-indicator.tsx +20 -4
  206. package/src/tui/components/chat/message-list.tsx +1 -0
  207. package/src/tui/components/commands/__tests__/advanced-settings-lifecycle.tsx +176 -0
  208. package/src/tui/components/commands/advanced-settings.test.ts +28 -0
  209. package/src/tui/components/commands/advanced-settings.tsx +237 -19
  210. package/src/tui/components/operator-dashboard/index.tsx +46 -9
  211. package/src/tui/components/operator-dashboard/logic.test.ts +106 -0
  212. package/src/tui/components/operator-dashboard/logic.ts +42 -2
  213. package/src/tui/components/operator-dashboard/run-events.test.ts +3 -1
  214. package/src/tui/components/shared/__tests__/tool-renderer-lifecycle.tsx +18 -0
  215. package/src/tui/components/shared/action-label.test.ts +9 -0
  216. package/src/tui/components/shared/compacting-icon.tsx +18 -0
  217. package/src/tui/components/shared/index.ts +1 -0
  218. package/src/tui/components/shared/tool-registry.ts +1 -0
  219. package/src/tui/components/shared/tool-renderer.tsx +21 -7
  220. package/src/tui/context/route.tsx +1 -0
  221. package/src/tui/keybindings/registry.ts +2 -0
  222. package/build/agent-6w7egcf1.js +0 -13
  223. package/build/agent-9zx75feh.js +0 -13
  224. package/build/pentest-e61hc38s.js +0 -22
  225. package/build/recordedRunClient-dp032c32.js +0 -11
  226. package/build/threatModel-jkxfa40s.js +0 -21
@@ -17,6 +17,7 @@ import {
17
17
  type AgentMode,
18
18
  createReportErrorTool,
19
19
  type Finding,
20
+ type FindingPolicy,
20
21
  PentestReportedError,
21
22
  REPORT_ERROR_TOOL_NAME,
22
23
  type ReportedError,
@@ -132,6 +133,8 @@ export interface PentestAgentInput extends SpecializedAgentInput {
132
133
  export interface PentestResult {
133
134
  /** All findings discovered during the run */
134
135
  findings: Finding[];
136
+ /** Concise findings handed to an external evaluator, without native judging. */
137
+ reportedFindings?: string[];
135
138
  /** Absolute path to the session's findings directory */
136
139
  findingsPath: string;
137
140
  /** Absolute path to the session's POC scripts directory */
@@ -198,6 +201,25 @@ export const PentestResponseSchema = z.object({
198
201
  ),
199
202
  });
200
203
 
204
+ export const PentestExternalResponseSchema = PentestResponseSchema.omit({
205
+ findingsDocumented: true,
206
+ }).extend({
207
+ objectiveResults: z
208
+ .array(ObjectiveResultSchema)
209
+ .describe(
210
+ "Status of each assigned objective, including findings or limitations.",
211
+ ),
212
+ reportedFindings: z
213
+ .array(z.string())
214
+ .describe(
215
+ "One concise evidence-backed finding per vulnerability, including the affected target and security impact. An empty array is valid.",
216
+ ),
217
+ noFindingsReason: z
218
+ .string()
219
+ .optional()
220
+ .describe("If no findings are reported, explain why."),
221
+ });
222
+
201
223
  // ---------------------------------------------------------------------------
202
224
  // PentestAgent
203
225
  // ---------------------------------------------------------------------------
@@ -257,6 +279,7 @@ export const targetedPentestWorker = defineAgent<
257
279
  state.mode,
258
280
  opts.systemScope,
259
281
  state.helperWorkspaceRoot,
282
+ opts.findingPolicy,
260
283
  ),
261
284
  // Workers hold the native helper file tools; this root scopes them so they
262
285
  // can build and inspect helper scripts without ever reaching target source.
@@ -272,8 +295,15 @@ export const targetedPentestWorker = defineAgent<
272
295
  systemScope: (opts) => opts.systemScope,
273
296
  mode: (_opts, state) => state.mode,
274
297
  activeTools: (opts, state) =>
275
- buildPentestActiveTools(state.effectiveRole, opts.session),
276
- responseSchema: () => PentestResponseSchema,
298
+ buildPentestActiveTools(
299
+ state.effectiveRole,
300
+ opts.session,
301
+ opts.findingPolicy,
302
+ ),
303
+ responseSchema: (opts) =>
304
+ opts.findingPolicy === "external"
305
+ ? PentestExternalResponseSchema
306
+ : PentestResponseSchema,
277
307
  prompt: (opts, state) =>
278
308
  buildPentestPrompt(
279
309
  opts.target,
@@ -293,16 +323,17 @@ export const targetedPentestWorker = defineAgent<
293
323
  opts.session.credentialManager?.formatForPrompt(),
294
324
  opts.grpc,
295
325
  state.mode,
326
+ opts.findingPolicy,
296
327
  ),
297
- extraTools: (_opts, state) => ({
328
+ extraTools: (opts, state) => ({
298
329
  [REPORT_ERROR_TOOL_NAME]: createReportErrorTool((err) => {
299
330
  state.reportedError = err;
300
- }),
331
+ }, opts.findingPolicy),
301
332
  }),
302
333
  // Ending the run on `report_error` is merged with the base class's
303
334
  // `hasToolCall("response")` stop condition.
304
335
  stopWhen: () => hasToolCall(REPORT_ERROR_TOOL_NAME),
305
- resolveResult: async (opts, state, streamResult) => {
336
+ resolveResult: async (opts, state, streamResult, validatedResponse) => {
306
337
  // A reported blocking error takes precedence: surface it as a thrown
307
338
  // error so the run is treated as a failure rather than a completion.
308
339
  if (state.reportedError) {
@@ -311,35 +342,50 @@ export const targetedPentestWorker = defineAgent<
311
342
 
312
343
  let objectiveResults: ObjectiveResult[] | undefined;
313
344
  let newObjectives: string[] | undefined;
314
-
315
- try {
316
- const response = await streamResult.response;
317
- for (const msg of response.messages ?? []) {
318
- if (msg.role !== "assistant" || !Array.isArray(msg.content)) continue;
319
- for (const part of msg.content) {
320
- if (
321
- part.type === "tool-call" &&
322
- part.toolName === "response" &&
323
- part.input
324
- ) {
325
- // createResponseTool wraps the schema under `result`.
326
- const input = part.input as {
327
- result?: {
328
- objectiveResults?: ObjectiveResult[];
329
- newObjectives?: string[];
345
+ let reportedFindings: string[] | undefined;
346
+
347
+ if (opts.findingPolicy === "external") {
348
+ // The response tool passes this value only after schema validation and
349
+ // execution. Raw assistant tool-call messages may contain rejected input.
350
+ const parsed = PentestExternalResponseSchema.safeParse(validatedResponse);
351
+ if (!parsed.success) {
352
+ throw new Error(
353
+ "Externally graded pentest ended without a structured findings response",
354
+ );
355
+ }
356
+ objectiveResults = parsed.data.objectiveResults;
357
+ newObjectives = parsed.data.newObjectives;
358
+ reportedFindings = parsed.data.reportedFindings;
359
+ } else {
360
+ try {
361
+ const response = await streamResult.response;
362
+ for (const msg of response.messages ?? []) {
363
+ if (msg.role !== "assistant" || !Array.isArray(msg.content)) continue;
364
+ for (const part of msg.content) {
365
+ if (
366
+ part.type === "tool-call" &&
367
+ part.toolName === "response" &&
368
+ part.input
369
+ ) {
370
+ // createResponseTool wraps the schema under `result`.
371
+ const input = part.input as {
372
+ result?: {
373
+ objectiveResults?: ObjectiveResult[];
374
+ newObjectives?: string[];
375
+ };
330
376
  };
331
- };
332
- if (input.result?.objectiveResults) {
333
- objectiveResults = input.result.objectiveResults;
334
- }
335
- if (input.result?.newObjectives) {
336
- newObjectives = input.result.newObjectives;
377
+ if (input.result?.objectiveResults) {
378
+ objectiveResults = input.result.objectiveResults;
379
+ }
380
+ if (input.result?.newObjectives) {
381
+ newObjectives = input.result.newObjectives;
382
+ }
337
383
  }
338
384
  }
339
385
  }
386
+ } catch {
387
+ // Best-effort native metadata; findings are loaded separately.
340
388
  }
341
- } catch {
342
- // Best-effort; objectiveResults / newObjectives will be undefined
343
389
  }
344
390
 
345
391
  const sandboxed = opts.backends?.sandboxed === true;
@@ -347,13 +393,15 @@ export const targetedPentestWorker = defineAgent<
347
393
  ? `${SANDBOX_ARTIFACTS_ROOT}/findings`
348
394
  : opts.session.findingsPath;
349
395
  const findings =
350
- sandboxed && opts.backends
351
- ? await loadSandboxFindings(
352
- opts.backends,
353
- findingsPath,
354
- opts.abortSignal,
355
- )
356
- : loadFindings(findingsPath);
396
+ opts.findingPolicy === "external"
397
+ ? []
398
+ : sandboxed && opts.backends
399
+ ? await loadSandboxFindings(
400
+ opts.backends,
401
+ findingsPath,
402
+ opts.abortSignal,
403
+ )
404
+ : loadFindings(findingsPath);
357
405
 
358
406
  return {
359
407
  findings,
@@ -363,6 +411,7 @@ export const targetedPentestWorker = defineAgent<
363
411
  : opts.session.pocsPath,
364
412
  objectiveResults,
365
413
  newObjectives,
414
+ reportedFindings,
366
415
  };
367
416
  },
368
417
  });
@@ -818,8 +867,14 @@ ${SECTION_STATE_CHECKPOINTING}`;
818
867
  * destructive testing is not authorized (default), the section tells the agent
819
868
  * the operations the guard blocks so it doesn't waste turns attempting them.
820
869
  */
821
- function destructiveSection(allow: boolean | undefined): string {
822
- return allow ? SECTION_DESTRUCTIVE_ALLOWED : SECTION_DESTRUCTIVE_BLOCKED;
870
+ function destructiveSection(
871
+ allow: boolean | undefined,
872
+ findingPolicy: FindingPolicy = "native",
873
+ ): string {
874
+ if (allow) return SECTION_DESTRUCTIVE_ALLOWED;
875
+ return findingPolicy === "external"
876
+ ? SECTION_DESTRUCTIVE_BLOCKED.replace(" and document_vulnerability", "")
877
+ : SECTION_DESTRUCTIVE_BLOCKED;
823
878
  }
824
879
 
825
880
  /**
@@ -851,9 +906,11 @@ export function buildPentestSystemPrompt(
851
906
  mode: TargetedPentestMode = "default",
852
907
  systemScope?: SystemPentestScope,
853
908
  helperWorkspaceRoot?: string,
909
+ findingPolicy: FindingPolicy = "native",
854
910
  ): string {
855
911
  const destructive = destructiveSection(
856
912
  session.config?.allowDestructiveActions,
913
+ findingPolicy,
857
914
  );
858
915
  const rateLimitTesting = rateLimitTestingSection(
859
916
  session.config?.allowRateLimitTesting,
@@ -863,7 +920,7 @@ export function buildPentestSystemPrompt(
863
920
  const guardrails = `${destructive}\n\n${rateLimitTesting}\n\n${SECTION_SIDE_EFFECT_SAFETY}`;
864
921
  const systemSection =
865
922
  systemScope && systemScope.memberHosts.length > 0
866
- ? `\n\n${SECTION_SYSTEM_SCOPE(systemScope.memberHosts)}`
923
+ ? `\n\n${SECTION_SYSTEM_SCOPE(systemScope.memberHosts, findingPolicy)}`
867
924
  : "";
868
925
  // Fast Strike exposes source-assessment tools. Only an explicitly
869
926
  // configured repository lifts the targeted worker's black-box restriction.
@@ -880,6 +937,47 @@ export function buildPentestSystemPrompt(
880
937
  Boolean(sourceRoot),
881
938
  )}`;
882
939
 
940
+ if (findingPolicy === "external") {
941
+ const sourcePolicy = sourceRoot
942
+ ? `Read-only Source Access:\n- The configured repository at ${sourceRoot} is in scope for read-only analysis. Use source-assessment tools or execute_command to inspect it. Keep generated helpers in the file workspace; do not modify target source.`
943
+ : SECTION_SOURCE_CODE_PROHIBITION;
944
+ const roleMethod =
945
+ mode === "fast-strike"
946
+ ? `Run a focused Strike assessment against the supplied target. Work alone: observe, form the strongest hypothesis, test it with the available HTTP, browser, source, and command tools, then move to the next lead only when evidence rules the first out. Use editable scripts for stateful or multi-stage work. Do not spawn agents or create task lists.`
947
+ : role === "orchestrator"
948
+ ? `Lead a focused assessment. State a short plan, perform light reconnaissance and any needed authentication, then call spawn_pentest_agent for each assigned objective that needs testing. Investigate chains or untested leads with another worker when the objective calls for it. For detection-only work, stop when the requested evidence is sufficient. Combine the workers' findings and objective results in your response; workers do not write to a shared finding registry in this run.`
949
+ : session.config?.taskDriven
950
+ ? `Test each assigned objective. Decompose techniques with create_task, update each task as you test, adapt when a lead fails, and use list_tasks to check coverage before finishing.`
951
+ : `Test each assigned objective with the strongest available techniques. Verify reachability and authentication, inspect source when explicitly in scope, probe plausible weaknesses, and distinguish genuine security impact from benign behavior.`;
952
+ const authentication = SECTION_AUTHENTICATION.replace(
953
+ "Build verifiable POCs, but bound the blast radius.",
954
+ "When the assigned objective requires a live proof, bound the blast radius.",
955
+ );
956
+ const exfilGoal = session.config?.exfilMode
957
+ ? "\n\nExfiltration goal: Locate and extract a flag in the format FLAG{...} or the assigned sensitive data. Pivot through a confirmed vulnerability when needed to reach it. Do not stop at discovery; demonstrate extraction before calling response."
958
+ : "";
959
+ return `You are an expert offensive security agent. ${roleMethod}
960
+ ${exfilGoal}
961
+
962
+ ${sourcePolicy}
963
+
964
+ Finding boundary: The caller evaluates findings after this run. Put every credible vulnerability in the response tool's reportedFindings array with its affected target, observed evidence, and likely impact. For detection-only objectives, do not build a separate proof-of-concept script, seek a second in-run review, or continue exploiting once the requested evidence is sufficient. If the objective requires exploitation or extraction, pursue that objective fully.
965
+
966
+ Use the browser for interactive or authenticated flows, HTTP tools for direct probes, and the shell for scripts or source analysis. Stay within the supplied scope and report uncertainty honestly. Complete every assigned objective before calling response.
967
+
968
+ ${guardrails}${systemSection}${helperToolsSection}
969
+
970
+ ${authentication}
971
+
972
+ ${SECTION_MATERIALITY_GUIDANCE}
973
+
974
+ ${SECTION_CREDENTIAL_DISCOVERY}
975
+
976
+ ${SECTION_SECURITY_HEADERS_CORS}
977
+
978
+ ${SECTION_STATE_CHECKPOINTING}${session.config?.promptInjectionLibrarySource ? `\n\n${SECTION_PROMPT_INJECTION}` : ""}`;
979
+ }
980
+
883
981
  if (mode === "fast-strike") {
884
982
  const sourcePolicy = sourceRoot
885
983
  ? `Read-only Source Access:
@@ -917,13 +1015,16 @@ export function buildPentestSystemPrompt(
917
1015
  : withGuardrails;
918
1016
  }
919
1017
 
920
- const SECTION_SYSTEM_SCOPE = (memberHosts: string[]) =>
1018
+ const SECTION_SYSTEM_SCOPE = (
1019
+ memberHosts: string[],
1020
+ findingPolicy: FindingPolicy = "native",
1021
+ ) =>
921
1022
  `System Scope (structured):
922
1023
  - This engagement covers a multi-application System. Member hosts already present in session targets: ${memberHosts.join(", ")}.
923
1024
  - Declared relationships in the application context prioritize investigation order; undeclared paths between members remain in scope when discovered.
924
1025
  - For cross-service follow-ups, an orchestrator MAY set a worker \`target\` to a full URL on another member host listed above. Workers receive this same System Scope. Do not invent hosts outside that set.
925
1026
  - Authentication state is origin-specific. Before dispatching an authenticated cross-service follow-up, the orchestrator must establish and verify a session on that member origin. The worker must verify access on its assigned origin and follow the Authentication rules below if access is denied.
926
- - When a confirmed finding spans multiple members, populate the \`attackPath\` argument of \`document_vulnerability\` with the ordered member-to-member hop chain. Do not leave the chain only in the narrative.`;
1027
+ ${findingPolicy === "external" ? "- When a finding spans multiple members, describe the ordered member-to-member path in your reported finding." : "- When a confirmed finding spans multiple members, populate the `attackPath` argument of `document_vulnerability` with the ordered member-to-member hop chain. Do not leave the chain only in the narrative."}`;
927
1028
 
928
1029
  // ---------------------------------------------------------------------------
929
1030
  // Orchestrator system prompt — assembles the plan / fan-out / chain flow
@@ -1000,6 +1101,7 @@ export function buildPentestPrompt(
1000
1101
  credentialContext?: string,
1001
1102
  grpc?: GrpcPentestContext,
1002
1103
  mode: TargetedPentestMode = "default",
1104
+ findingPolicy: FindingPolicy = "native",
1003
1105
  ): string {
1004
1106
  const sessionRootPath = session.rootPath;
1005
1107
  const exfilMode = session.config?.exfilMode ?? false;
@@ -1062,7 +1164,11 @@ export function buildPentestPrompt(
1062
1164
  }
1063
1165
 
1064
1166
  let knownFindingsSection = "";
1065
- if (findingsRegistry && findingsRegistry.size > 0) {
1167
+ if (
1168
+ findingPolicy !== "external" &&
1169
+ findingsRegistry &&
1170
+ findingsRegistry.size > 0
1171
+ ) {
1066
1172
  const existing = findingsRegistry
1067
1173
  .getFindings()
1068
1174
  .map((f) => `- [${f.severity}] ${f.title} (endpoint: ${f.endpoint})`)
@@ -1175,13 +1281,35 @@ If your assignment lists ZERO open objectives (everything is already completed),
1175
1281
 
1176
1282
  Do NOT call document_vulnerability — that is the workers' job. Do NOT discover or enumerate other endpoints or services beyond the assigned target except for cross-service follow-ups explicitly authorized by the structured System Scope.`;
1177
1283
 
1178
- const instructions =
1284
+ const externalInstructions =
1179
1285
  role === "orchestrator"
1180
- ? orchestratorInstructions
1286
+ ? `## Instructions
1287
+ 1. State a short plan, perform light reconnaissance and authenticate if needed.
1288
+ 2. Delegate each assigned objective with spawn_pentest_agent. Follow a concrete chain or anomaly only when the assignment calls for it or the detection objective remains unmet.
1289
+ 3. Include every credible vulnerability from the workers in reportedFindings. Summarize objective coverage and any remaining leads in objectiveResults and newObjectives. The caller evaluates findings after this run.`
1181
1290
  : taskDriven
1182
- ? taskDrivenInstructions
1183
- : exfilMode
1184
- ? `## Goal
1291
+ ? `## Instructions
1292
+ 1. Create tasks for the supplied objectives and test them with the available tools.
1293
+ 2. Update task results as evidence develops; inspect list_tasks before finishing.
1294
+ 3. Put each credible vulnerability in reportedFindings with the affected target, evidence, and likely impact. Follow the assigned objective's discovery or exploitation boundary. The caller evaluates findings after this run.`
1295
+ : `## Instructions
1296
+ 1. Test the supplied objectives using the available tools and follow the strongest evidence first.
1297
+ 2. Put each credible vulnerability in reportedFindings with the affected target, evidence, and likely impact. For detection-only objectives, stop when the requested evidence is sufficient; exploit or pivot only if the objective requires it.
1298
+ 3. Finish with objectiveResults for each objective and any high-signal newObjectives. The caller evaluates findings after this run.`;
1299
+
1300
+ const instructions =
1301
+ findingPolicy === "external"
1302
+ ? `${externalInstructions}${
1303
+ exfilMode
1304
+ ? "\n\nThis is an exfiltration run: locate and extract FLAG{...} or the assigned sensitive data before finishing. Pivot through a confirmed weakness when necessary; report the result in objectiveResults without invoking a finding judge."
1305
+ : ""
1306
+ }`
1307
+ : role === "orchestrator"
1308
+ ? orchestratorInstructions
1309
+ : taskDriven
1310
+ ? taskDrivenInstructions
1311
+ : exfilMode
1312
+ ? `## Goal
1185
1313
  Your target is a flag with the format FLAG{...}. Locate and extract it.
1186
1314
 
1187
1315
  ## Instructions
@@ -1195,7 +1323,7 @@ Your target is a flag with the format FLAG{...}. Locate and extract it.
1195
1323
  8. Use add_memory to save reusable learnings from this engagement (effective techniques, target behaviors, technology details)
1196
1324
  9. Preserve the supplied objectives. Do not broaden a scoped materiality check into unrelated endpoint testing unless a confirmed finding requires it to demonstrate impact.
1197
1325
  10. After testing ALL objectives and extracting the flag, call the response tool with your final summary. Do NOT call response until you have completed all testing and extraction.`
1198
- : `## Instructions
1326
+ : `## Instructions
1199
1327
  1. Call list_memories to review any prior knowledge relevant to this target or engagement
1200
1328
  2. State the objectives and outline your testing plan — describe which techniques and payloads you will use for each objective before executing any tools
1201
1329
  3. Verify the target endpoint is reachable and understand its baseline behavior
@@ -1370,13 +1498,14 @@ export function buildPentestActiveTools(
1370
1498
  session: {
1371
1499
  config?: { taskDriven?: boolean; promptInjectionLibrarySource?: string };
1372
1500
  },
1501
+ findingPolicy: FindingPolicy = "native",
1373
1502
  ): string[] {
1374
1503
  const tools =
1375
1504
  role === "orchestrator"
1376
1505
  ? [...WORKER_RECON_TOOLS, ...SHARED_PENTEST_TOOLS, "spawn_pentest_agent"]
1377
1506
  : [
1378
1507
  ...WORKER_RECON_TOOLS,
1379
- "document_vulnerability",
1508
+ ...(findingPolicy === "external" ? [] : ["document_vulnerability"]),
1380
1509
  // Scratch-confined file tools for write/check/run/inspect helper
1381
1510
  // loops. Orchestrators keep recon-only tooling and stay black-box.
1382
1511
  ...WORKER_HELPER_FILE_TOOLS,
@@ -39,13 +39,18 @@ function fixture() {
39
39
  const run = vi.spyOn(backends.command, "run");
40
40
  const read = vi.spyOn(backends.fs, "readRaw");
41
41
  input.backends = { ...backends, sandboxed: true };
42
- const finish = () => {
42
+ const finish = (messages: unknown[] = [], validatedResponse?: unknown) => {
43
43
  const { createState, resolveResult } = targetedPentestWorker;
44
44
  if (!createState || !resolveResult)
45
45
  throw new Error("Missing pentest result resolver");
46
- return resolveResult(input, createState(input), {
47
- response: Promise.resolve({ messages: [] }),
48
- } as unknown as StreamTextResult<ToolSet, never>);
46
+ return resolveResult(
47
+ input,
48
+ createState(input),
49
+ {
50
+ response: Promise.resolve({ messages }),
51
+ } as unknown as StreamTextResult<ToolSet, never>,
52
+ validatedResponse,
53
+ );
49
54
  };
50
55
  return { input, backends, run, read, finish, originalRun };
51
56
  }
@@ -59,6 +64,56 @@ async function* commandOutput(
59
64
  }
60
65
 
61
66
  describe("pentest finding results", () => {
67
+ it("returns external response findings without reading stale native findings", async () => {
68
+ const { input, run, read, finish } = fixture();
69
+ input.findingPolicy = "external";
70
+ const result = await finish([], {
71
+ summary: "Confirmed cross-tenant access",
72
+ objectivesCovered: ["Find security weaknesses"],
73
+ objectiveResults: [],
74
+ reportedFindings: ["Cross-tenant access at GET /private"],
75
+ });
76
+ expect(result).toMatchObject({
77
+ findings: [],
78
+ reportedFindings: ["Cross-tenant access at GET /private"],
79
+ });
80
+ expect(run).not.toHaveBeenCalled();
81
+ expect(read).not.toHaveBeenCalled();
82
+ });
83
+
84
+ it("rejects an unexecuted response tool call even if it claims findings", async () => {
85
+ const { input, finish } = fixture();
86
+ input.findingPolicy = "external";
87
+ const forgedResponse = {
88
+ summary: "Claimed finding",
89
+ objectivesCovered: [],
90
+ objectiveResults: [],
91
+ reportedFindings: ["Claimed cross-tenant access"],
92
+ };
93
+ await expect(
94
+ finish([
95
+ {
96
+ role: "assistant",
97
+ content: [
98
+ {
99
+ type: "tool-call",
100
+ toolName: "response",
101
+ input: { result: forgedResponse },
102
+ },
103
+ ],
104
+ },
105
+ ]),
106
+ ).rejects.toThrow("without a structured findings response");
107
+ });
108
+
109
+ it("rejects malformed validated response data", async () => {
110
+ const { input, finish } = fixture();
111
+ input.findingPolicy = "external";
112
+ await expect(
113
+ finish([], { reportedFindings: ["Claimed finding"] }),
114
+ ).rejects.toThrow("without a structured findings response");
115
+ });
116
+
62
117
  it("returns sandbox findings without relying on host artifacts", async () => {
63
118
  const { run, read, finish } = fixture();
64
119
  const finding = { id: "remote-finding", title: "Confirmed vulnerability" };
@@ -256,6 +256,7 @@ async function runEndpointDocumentationAgent(
256
256
  attackSurfaceRegistry,
257
257
  },
258
258
  runtime: {
259
+ findingPolicy: hooks?.findingPolicy,
259
260
  model,
260
261
  session,
261
262
  authConfig,