@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
@@ -35,6 +35,22 @@ const ORIGIN_AUTH_FAILURE_GUIDANCE = `A 401/403 from one resource does not by it
35
35
  * directly on the parent bus
36
36
  */
37
37
  export function spawnPentestAgent(ctx: ToolContext) {
38
+ const externalFindings = ctx.findingPolicy === "external";
39
+ const findingSteps = externalFindings
40
+ ? "2. Reports concise evidence-backed findings in its response\n3. Leaves acceptance to the caller's external evaluator"
41
+ : "2. Confirms vulnerabilities with POC scripts\n3. Documents findings to the shared findings registry only when the exploit path and material security impact are proven";
42
+ const findingGuidance = externalFindings
43
+ ? "False-positive discipline: report a security weakness only when observations support a material abuse path. Include the affected target, evidence, and impact; do not add a separate in-run finding review."
44
+ : "False-positive discipline: workers should not call document_vulnerability for public/unauthenticated behavior, missing best-practice controls, generic errors, local HTTP deployment context, or other low-signal observations unless they prove a material abuse path such as non-public data access, credentialed cross-origin abuse, state change, denial of service, account takeover, or another concrete exploit chain.";
45
+ const returnGuidance = externalFindings
46
+ ? "Returns the worker's summary, objective results, and reported findings so the parent can investigate chains."
47
+ : "Returns the worker's summary, objective results, and finding count — but NOT the findings themselves (those land in the shared registry).";
48
+ const exploratoryGuidance = externalFindings
49
+ ? "Spawn a follow-up exploratory worker only when the assigned objective requires broader coverage or a credible untested lead remains. For detection-only work, finish once the requested evidence is sufficient."
50
+ : 'After fanning out one worker per objective, call this tool ONE MORE TIME for a final "exploratory" worker. Pass it the URLs of any suspicious behaviors discovered by earlier workers and ask it to chain findings together and probe for additional, unspecified vulnerabilities.';
51
+ const objectiveGuidance = externalFindings
52
+ ? "Pass one focused objective per worker. A follow-up objective may cover a concrete chain or untested lead when the assignment calls for it."
53
+ : "For per-objective fan-out, pass exactly one objective. For the final exploratory pass, pass a single composite objective describing chaining + exploration.";
38
54
  const targetScopeGuidance =
39
55
  ctx.systemScope && ctx.systemScope.memberHosts.length > 0
40
56
  ? `For a cross-service follow-up, you may instead use a full URL on one of these authorized System Scope members: ${ctx.systemScope.memberHosts.join(", ")}.`
@@ -51,18 +67,17 @@ Authentication state is origin-specific. Before spawning a cross-service worker
51
67
 
52
68
  Each spawn produces an independent TargetedPentestAgent that:
53
69
  1. Tests the assigned objective(s) deeply (execute_command, http_request, browser tools)
54
- 2. Confirms vulnerabilities with POC scripts
55
- 3. Documents findings to the shared findings registry only when the exploit path and material security impact are proven
70
+ ${findingSteps}
56
71
 
57
72
  Cloned browser session — the worker gets its OWN isolated Chromium instance, but it is seeded at spawn time with a snapshot of your current cookies and localStorage. If you've already authenticated in your browser (or recon set up useful state), the worker starts already authenticated for the same origins. Critically, the worker's browser is a COPY: its navigations, form fills, \`localStorage.setItem\`, alerts, and any other browser-side mutations stay inside the worker's own browser and do NOT leak back to you or to sibling workers. If you want state to be visible to the next worker, set it up in YOUR browser before spawning.
58
73
 
59
74
  The worker has no further spawn capability — it cannot recursively spawn other pentest agents.
60
75
 
61
- After fanning out one worker per objective, call this tool ONE MORE TIME for a final "exploratory" worker. Pass it the URLs of any suspicious behaviors discovered by earlier workers and ask it to chain findings together and probe for additional, unspecified vulnerabilities.
76
+ ${exploratoryGuidance}
62
77
 
63
- False-positive discipline: workers should not call document_vulnerability for public/unauthenticated behavior, missing best-practice controls, generic errors, local HTTP deployment context, or other low-signal observations unless they prove a material abuse path such as non-public data access, credentialed cross-origin abuse, state change, denial of service, account takeover, or another concrete exploit chain.
78
+ ${findingGuidance}
64
79
 
65
- Returns the worker's summary, objective results, and finding count — but NOT the findings themselves (those land in the shared registry).`,
80
+ ${returnGuidance}`,
66
81
  inputSchema: z.object({
67
82
  name: z
68
83
  .string()
@@ -77,9 +92,7 @@ Returns the worker's summary, objective results, and finding count — but NOT t
77
92
  objectives: z
78
93
  .array(z.string())
79
94
  .min(1)
80
- .describe(
81
- "Testing objectives for this worker. For per-objective fan-out, pass exactly one objective. For the final exploratory pass, pass a single composite objective describing chaining + exploration.",
82
- ),
95
+ .describe(`Testing objectives for this worker. ${objectiveGuidance}`),
83
96
  context: z
84
97
  .string()
85
98
  .optional()
@@ -151,7 +164,7 @@ Returns the worker's summary, objective results, and finding count — but NOT t
151
164
  const spawner = ctx.subagentSpawner;
152
165
  let subagentId = "";
153
166
  try {
154
- const { findings, objectiveResults } =
167
+ const { findings, reportedFindings, objectiveResults } =
155
168
  await spawner.spawn<PentestResult>({
156
169
  spec: {
157
170
  type: "pentest",
@@ -170,6 +183,7 @@ Returns the worker's summary, objective results, and finding count — but NOT t
170
183
  browserSession: sharedBrowserSession,
171
184
  },
172
185
  runtime: {
186
+ findingPolicy: ctx.findingPolicy,
173
187
  session: ctx.session,
174
188
  model: ctx.model,
175
189
  authConfig: ctx.authConfig,
@@ -203,9 +217,16 @@ Returns the worker's summary, objective results, and finding count — but NOT t
203
217
  return {
204
218
  success: true,
205
219
  subagentId,
206
- findingsCount: findings.length,
220
+ findingsCount: externalFindings
221
+ ? (reportedFindings?.length ?? 0)
222
+ : findings.length,
223
+ ...(externalFindings
224
+ ? { reportedFindings: reportedFindings ?? [] }
225
+ : {}),
207
226
  objectiveResults,
208
- message: `Worker '${name}' complete. Documented ${findings.length} finding(s).`,
227
+ message: externalFindings
228
+ ? `Worker '${name}' complete. Reported ${reportedFindings?.length ?? 0} finding(s) for external evaluation.`
229
+ : `Worker '${name}' complete. Documented ${findings.length} finding(s).`,
209
230
  };
210
231
  } catch (error) {
211
232
  const message = error instanceof Error ? error.message : String(error);
@@ -21,15 +21,21 @@ const log = scopedLogger(() => createLogger("spawn-pentest-swarm"));
21
21
  * after recon, it calls this tool to fan-out testing to sub-agents.
22
22
  */
23
23
  export function spawnPentestSwarm(ctx: ToolContext) {
24
+ const externalFindings = ctx.findingPolicy === "external";
25
+ const findingSteps = externalFindings
26
+ ? "2. Surface concise evidence-backed findings in its response\n3. Leave finding acceptance to the caller's evaluator"
27
+ : "2. Create POC scripts for confirmed vulnerabilities\n3. Document findings with evidence and remediation";
28
+ const findingDestination = externalFindings
29
+ ? "Reported findings are returned to this caller; no native finding review runs."
30
+ : "All findings are written to the shared session directory.";
24
31
  return tool({
25
32
  description: `Launch a swarm of targeted pentest sub-agents to test multiple targets in parallel.
26
33
 
27
34
  Call this AFTER completing attack surface discovery. Each entry in 'targets' spawns an independent TargetedPentestAgent that will:
28
35
  1. Test the specified target for the given objectives
29
- 2. Create POC scripts for confirmed vulnerabilities
30
- 3. Document findings with evidence and remediation
36
+ ${findingSteps}
31
37
 
32
- All findings are written to the shared session directory.
38
+ ${findingDestination}
33
39
 
34
40
  Pass every target you want tested — the swarm handles concurrency automatically.`,
35
41
  inputSchema: z.object({
@@ -92,6 +98,7 @@ Pass every target you want tested — the swarm handles concurrency automaticall
92
98
  );
93
99
 
94
100
  const swarmResults = await runPentestSwarm({
101
+ findingPolicy: ctx.findingPolicy,
95
102
  targets,
96
103
  model: ctx.model!,
97
104
  session: ctx.session,
@@ -107,6 +114,7 @@ Pass every target you want tested — the swarm handles concurrency automaticall
107
114
  openAIReasoningEffort: ctx.openAIReasoningEffort,
108
115
  display: ctx.display,
109
116
  hooks: {
117
+ findingPolicy: ctx.findingPolicy,
110
118
  backends: ctx.backends,
111
119
  subagentSpawner: ctx.subagentSpawner,
112
120
  languageModelMiddleware: ctx.languageModelMiddleware,
@@ -120,7 +128,11 @@ Pass every target you want tested — the swarm handles concurrency automaticall
120
128
 
121
129
  // Aggregate
122
130
  const totalFindings = swarmResults.reduce(
123
- (sum, r) => sum + (r?.findings.length ?? 0),
131
+ (sum, r) =>
132
+ sum +
133
+ (externalFindings
134
+ ? (r?.reportedFindings?.length ?? 0)
135
+ : (r?.findings.length ?? 0)),
124
136
  0,
125
137
  );
126
138
  const failedCount = swarmResults.filter((r) => r == null).length;
@@ -132,7 +144,15 @@ Pass every target you want tested — the swarm handles concurrency automaticall
132
144
  failedTargets: failedCount,
133
145
  results: targets.map((t, i) => ({
134
146
  target: t.target,
135
- findingsCount: swarmResults[i]?.findings.length ?? 0,
147
+ findingsCount: externalFindings
148
+ ? (swarmResults[i]?.reportedFindings?.length ?? 0)
149
+ : (swarmResults[i]?.findings.length ?? 0),
150
+ ...(externalFindings
151
+ ? {
152
+ reportedFindings: swarmResults[i]?.reportedFindings ?? [],
153
+ objectiveResults: swarmResults[i]?.objectiveResults ?? [],
154
+ }
155
+ : {}),
136
156
  error: swarmResults[i] == null ? "Agent failed" : undefined,
137
157
  })),
138
158
  message: `Swarm complete. ${totalFindings} findings across ${total} targets. ${
@@ -373,6 +373,7 @@ export async function generateThreatModelForEndpoint(
373
373
  excludeTools: ["document_endpoint", "document_app"],
374
374
  },
375
375
  runtime: {
376
+ findingPolicy: ctx.findingPolicy,
376
377
  session: ctx.session,
377
378
  model,
378
379
  authConfig: ctx.authConfig,
@@ -22,7 +22,11 @@ import type { ToolBackends } from "../../../tools/backends/types";
22
22
  import type { GrpcPentestContext } from "../../specialized/attackSurface/grpcSchema";
23
23
  import type { SubagentSpawner } from "../subagentSpawner";
24
24
  import type { StepTraceWriter } from "../trace";
25
- import type { StreamIdFactory, SystemPentestScope } from "../types";
25
+ import type {
26
+ FindingPolicy,
27
+ StreamIdFactory,
28
+ SystemPentestScope,
29
+ } from "../types";
26
30
  import type { CallbackListenerRegistry } from "./callbackListener";
27
31
  import type { EmailAdapterResolver } from "./email/adapters";
28
32
  import type { PerCommandShell } from "./perCommandShell";
@@ -38,6 +42,7 @@ import type { SmsInbox } from "./smsInbox";
38
42
  * session or agent internals directly.
39
43
  */
40
44
  export type ToolContext = {
45
+ findingPolicy?: FindingPolicy;
41
46
  attackSurfaceArtifactsPath?: string;
42
47
 
43
48
  /** Session providing paths for findings, POCs, logs, scratchpad, etc. */
@@ -34,12 +34,16 @@ type UpdateTaskResult = {
34
34
  };
35
35
 
36
36
  export function updateTask(ctx: ToolContext) {
37
+ const completedGuidance =
38
+ ctx.findingPolicy === "external"
39
+ ? "vulnerability identified with supporting evidence, OR conclusively determined the endpoint is not vulnerable / materiality is not met for this technique."
40
+ : "vulnerability found and documented via document_vulnerability, OR conclusively determined the endpoint is not vulnerable / materiality is not met for this technique. Documentation is NOT required to complete a non-vulnerable task.";
37
41
  return tool({
38
42
  description: `Update a task's status and record results.
39
43
 
40
44
  Status transitions:
41
45
  - "in_progress" — Set when you START testing a task. Pick one pending task at a time.
42
- - "completed" — Set when testing is done: vulnerability found and documented via document_vulnerability, OR conclusively determined the endpoint is not vulnerable / materiality is not met for this technique. Documentation is NOT required to complete a non-vulnerable task.
46
+ - "completed" — Set when testing is done: ${completedGuidance}
43
47
  - "failed" — Set when the technique didn't work: rate limited, blocked by WAF, dead end, or approach not applicable. Then create a NEW task with an alternative technique.
44
48
 
45
49
  Always include a result or observation explaining the outcome.`,
@@ -47,12 +47,15 @@ const CandidateStateSchema = z.enum([
47
47
  ]);
48
48
 
49
49
  export function createWhiteboxCandidate(ctx: ToolContext) {
50
+ const candidateGuidance =
51
+ ctx.findingPolicy === "external"
52
+ ? "for the caller's final evaluation. Candidates keep hypotheses, source-to-sink traces, scanner artifacts, fuzzing logs, and verification status separate from reported findings."
53
+ : "for document_vulnerability. Candidates keep hypotheses, source-to-sink traces,\nscanner artifacts, fuzzing logs, and verification status out of official findings.";
50
54
  return tool({
51
55
  description: `Create a whitebox vulnerability candidate.
52
56
 
53
57
  Use this for plausible source-code findings that are not yet confirmed enough
54
- for document_vulnerability. Candidates keep hypotheses, source-to-sink traces,
55
- scanner artifacts, fuzzing logs, and verification status out of official findings.`,
58
+ ${candidateGuidance}`,
56
59
  inputSchema: z.object({
57
60
  title: z.string(),
58
61
  vulnerabilityClass: z.string(),
@@ -81,7 +84,9 @@ scanner artifacts, fuzzing logs, and verification status out of official finding
81
84
  artifactPaths: candidate.artifacts.map((artifact) => artifact.path),
82
85
  nextActions: [
83
86
  "Move candidate to investigating after reading source context.",
84
- "Attempt a reproducer or dynamic verification before document_vulnerability.",
87
+ ctx.findingPolicy === "external"
88
+ ? "Gather the evidence required by the caller before reporting this candidate."
89
+ : "Attempt a reproducer or dynamic verification before document_vulnerability.",
85
90
  ],
86
91
  truncated: false,
87
92
  };
@@ -132,7 +137,9 @@ State machine: hypothesis -> investigating -> repro_attempted -> confirmed (requ
132
137
  nextActions:
133
138
  candidate.state === "confirmed"
134
139
  ? [
135
- "Use document_vulnerability only if the proof demonstrates exploitability.",
140
+ ctx.findingPolicy === "external"
141
+ ? "Report only the evidence-backed conclusion to the caller."
142
+ : "Use document_vulnerability only if the proof demonstrates exploitability.",
136
143
  ]
137
144
  : [
138
145
  "Continue tracing or verification based on the candidate state.",
@@ -107,6 +107,9 @@ export type Finding = Omit<
107
107
  /** Agent operating mode that controls which tools are available. */
108
108
  export type AgentMode = "default" | "plan" | "fast-strike";
109
109
 
110
+ /** Selects Apex's native finding judge or a caller-owned evaluation boundary. */
111
+ export type FindingPolicy = "native" | "external";
112
+
110
113
  /** Structured authorization envelope for a multi-application System pentest. */
111
114
  export type SystemPentestScope = {
112
115
  systemId: string;
@@ -135,6 +138,9 @@ export type StreamIdFactory = (context: StreamIdFactoryContext) => string;
135
138
  * by forgetting to list it in a hand-written constructor.
136
139
  */
137
140
  export interface AgentHooks {
141
+ /** External grading omits Apex's finding tool and leaves acceptance to the caller. */
142
+ findingPolicy?: FindingPolicy;
143
+
138
144
  /** Opt-in shared backend directory for incremental recon endpoint artifacts. */
139
145
  attackSurfaceArtifactsPath?: string;
140
146
 
@@ -351,11 +357,14 @@ export type OffensiveSecurityAgentInput<TResult = void> = AgentHooks & {
351
357
  *
352
358
  * Receives the completed `StreamTextResult` — at this point all lazy
353
359
  * properties (`.steps`, `.text`, `.response`, etc.) are resolved.
360
+ * When the response tool executes, the optional second argument is its
361
+ * schema-validated result. It is absent if no response was submitted.
354
362
  *
355
363
  * If omitted, `consume()` returns `void`.
356
364
  */
357
365
  resolveResult?: (
358
366
  streamResult: StreamTextResult<ToolSet, never>,
367
+ validatedResponse?: unknown,
359
368
  ) => TResult | Promise<TResult>;
360
369
 
361
370
  /** The subagent ID if this agent is a subagent */
@@ -27,6 +27,7 @@ function makeAgent(overrides: Record<string, unknown> = {}) {
27
27
  return superInputs[superInputs.length - 1] as {
28
28
  activeTools: string[];
29
29
  fileWorkspaceRoot?: string;
30
+ system?: string;
30
31
  };
31
32
  }
32
33
 
@@ -86,4 +87,10 @@ describe("CodeAgent stays a read-focused analysis agent", () => {
86
87
  expect(input.activeTools).toContain("response");
87
88
  expect(input.activeTools).toContain("read_file");
88
89
  });
90
+
91
+ it("does not tell an externally graded code child to use the native finding tool", () => {
92
+ const input = makeAgent({ findingPolicy: "external" });
93
+ expect(input.system).toContain("sink-first work");
94
+ expect(input.system).not.toContain("document_vulnerability");
95
+ });
89
96
  });
@@ -3,7 +3,10 @@ import type { z } from "zod";
3
3
  import { AgentRuntime } from "../../agentRuntime";
4
4
  import { type AgentDefinition, defineAgent } from "../../defineAgent";
5
5
  import type { SpecializedAgentInput } from "../../offSecAgent";
6
- import { CODE_AGENT_SYSTEM_PROMPT } from "./prompts";
6
+ import {
7
+ CODE_AGENT_EXTERNAL_SYSTEM_PROMPT,
8
+ CODE_AGENT_SYSTEM_PROMPT,
9
+ } from "./prompts";
7
10
 
8
11
  const CODE_AGENT_TOOLS: string[] = [
9
12
  "read_file",
@@ -91,7 +94,11 @@ export function codeAgentDefinition<TResult = void>(): AgentDefinition<
91
94
  return defineAgent<CodeAgentInput<TResult>, TResult>({
92
95
  name: "code-agent",
93
96
  role: "worker",
94
- system: (opts) => opts.system ?? CODE_AGENT_SYSTEM_PROMPT,
97
+ system: (opts) =>
98
+ opts.system ??
99
+ (opts.findingPolicy === "external"
100
+ ? CODE_AGENT_EXTERNAL_SYSTEM_PROMPT
101
+ : CODE_AGENT_SYSTEM_PROMPT),
95
102
  activeTools: (opts) => {
96
103
  let activeTools = [...CODE_AGENT_TOOLS];
97
104
  if (opts.excludeTools?.length) {
@@ -46,3 +46,9 @@ Prefer catalog + code_query for sink-first work; use candidates to separate unve
46
46
  4. **Stay focused** — stick to your objective. Don't wander into unrelated areas of the codebase.
47
47
  5. **Be thorough** — don't stop at the first match. Make sure you've covered everything relevant to the objective.
48
48
  `;
49
+
50
+ export const CODE_AGENT_EXTERNAL_SYSTEM_PROMPT =
51
+ CODE_AGENT_SYSTEM_PROMPT.replace(
52
+ "use candidates to separate unverified ideas from `document_vulnerability`",
53
+ "use candidates to separate unverified ideas from evidence-backed findings returned to the caller",
54
+ );
@@ -25,6 +25,7 @@ import {
25
25
  buildPentestActiveTools,
26
26
  buildPentestPrompt,
27
27
  buildPentestSystemPrompt,
28
+ PentestExternalResponseSchema,
28
29
  PentestResponseSchema,
29
30
  resolvePentestAgentRole,
30
31
  resolvePentestBrowserEngine,
@@ -38,6 +39,121 @@ const session = {
38
39
  };
39
40
 
40
41
  describe("TargetedPentestAgent prompt contracts", () => {
42
+ it.each([
43
+ ["worker", "default"],
44
+ ["orchestrator", "default"],
45
+ ["worker", "fast-strike"],
46
+ ] as const)("keeps %s %s discovery tools and removes native finding guidance under external grading", (role, mode) => {
47
+ const tools = buildPentestActiveTools(role, session, "external");
48
+ const system = buildPentestSystemPrompt(
49
+ session,
50
+ role,
51
+ mode,
52
+ undefined,
53
+ undefined,
54
+ "external",
55
+ );
56
+ const assignment = buildPentestPrompt(
57
+ "https://example.com/api",
58
+ ["Find security weaknesses"],
59
+ session,
60
+ undefined,
61
+ undefined,
62
+ undefined,
63
+ undefined,
64
+ role,
65
+ undefined,
66
+ undefined,
67
+ mode,
68
+ "external",
69
+ );
70
+
71
+ expect(tools).toContain("http_request");
72
+ expect(tools).toContain("browser_navigate");
73
+ expect(tools).not.toContain("document_vulnerability");
74
+ expect(system + assignment).toContain("reportedFindings");
75
+ expect(system + assignment).not.toContain("document_vulnerability");
76
+ expect(system + assignment).not.toContain("validation judge");
77
+ expect(system + assignment).not.toContain("ONE FINAL TIME");
78
+ });
79
+
80
+ it("requires structured external findings without a native documentation count", () => {
81
+ const response = {
82
+ summary: "No issue found",
83
+ objectivesCovered: ["Find security weaknesses"],
84
+ objectiveResults: [
85
+ { objective: "Find security weaknesses", completed: true },
86
+ ],
87
+ reportedFindings: [],
88
+ };
89
+ expect(PentestExternalResponseSchema.safeParse(response).success).toBe(
90
+ true,
91
+ );
92
+ expect(
93
+ PentestExternalResponseSchema.safeParse({
94
+ ...response,
95
+ reportedFindings: undefined,
96
+ }).success,
97
+ ).toBe(false);
98
+ expect(PentestResponseSchema.safeParse(response).success).toBe(false);
99
+ });
100
+
101
+ it.each([
102
+ ["worker", "default"],
103
+ ["worker", "fast-strike"],
104
+ ["orchestrator", "default"],
105
+ ] as const)("preserves the exfiltration objective for external %s %s runs", (role, mode) => {
106
+ const exfilSession = {
107
+ ...session,
108
+ config: { exfilMode: true },
109
+ };
110
+ const system = buildPentestSystemPrompt(
111
+ exfilSession,
112
+ role,
113
+ mode,
114
+ undefined,
115
+ undefined,
116
+ "external",
117
+ );
118
+ const assignment = buildPentestPrompt(
119
+ "https://example.com/api",
120
+ ["Test the assigned target"],
121
+ exfilSession,
122
+ undefined,
123
+ undefined,
124
+ undefined,
125
+ undefined,
126
+ role,
127
+ undefined,
128
+ undefined,
129
+ mode,
130
+ "external",
131
+ );
132
+
133
+ expect(system).toContain("FLAG{...}");
134
+ expect(system).toContain("demonstrate extraction before calling response");
135
+ expect(assignment).toContain("locate and extract FLAG{...}");
136
+ expect(system + assignment).not.toContain("document_vulnerability");
137
+ });
138
+
139
+ it("keeps configured source inspection available to an external Fast Strike worker", () => {
140
+ const scopedSession = {
141
+ ...session,
142
+ config: { codebasePath: "/workspace/repository" },
143
+ };
144
+ const prompt = buildPentestSystemPrompt(
145
+ scopedSession,
146
+ "worker",
147
+ "fast-strike",
148
+ undefined,
149
+ undefined,
150
+ "external",
151
+ );
152
+ expect(prompt).toContain("/workspace/repository");
153
+ expect(prompt).toContain("Read-only Source Access");
154
+ expect(prompt).not.toContain("Source Code Prohibition");
155
+ });
156
+
41
157
  it("forces Fast Strike into the single-worker role", () => {
42
158
  expect(resolvePentestAgentRole("fast-strike", "orchestrator")).toBe(
43
159
  "worker",