@pensar/apex 2.6.1 → 2.7.0-canary.1572d9a4
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-ayreaj6z.js → agent-3g1b578v.js} +6 -5
- package/build/{agent-yby11zaq.js → agent-p8cezgbb.js} +7 -6
- package/build/{agent-runs-g37vkrjr.js → agent-runs-f86emmgg.js} +23 -20
- package/build/agent-vaq31je6.js +14 -0
- package/build/agent-vvdqga09.js +14 -0
- package/build/{apps-xn6km9yn.js → apps-3stwnmen.js} +16 -13
- package/build/{auth-d105wc64.js → auth-7md9kwda.js} +16 -13
- package/build/blackboxAgent-ct8bejef.js +14 -0
- package/build/{blackboxPentest-ps78v3w7.js → blackboxPentest-w2nr4md3.js} +21 -15
- package/build/{cli-c05y640n.js → cli-1j28cbhy.js} +1 -1
- package/build/{cli-d37a42dc.js → cli-2jz2jz06.js} +206 -90
- package/build/{cli-az3xq70v.js → cli-4a8dsz2k.js} +4 -4
- package/build/{cli-qgz1q9g5.js → cli-58781vcf.js} +1192 -1473
- package/build/{cli-gx71jcke.js → cli-7067zzeb.js} +1 -1
- package/build/{cli-3sp6nd8d.js → cli-81smmd5w.js} +5 -5
- package/build/{cli-k2nwxta0.js → cli-8b25dhky.js} +2 -2
- package/build/{cli-fy6vh2a7.js → cli-8sw5zpqz.js} +1 -1
- package/build/{cli-709rvekb.js → cli-cg3ypx5n.js} +14 -7
- package/build/{cli-dpsw2fe6.js → cli-e9v6zg6h.js} +40941 -40172
- package/build/cli-fjxmphg1.js +36 -0
- package/build/{cli-7eea4e2y.js → cli-gzb6qrzh.js} +31 -60
- package/build/{cli-65g6rcat.js → cli-khnf5xrf.js} +2 -1
- package/build/{cli-1ve18npr.js → cli-mdzt2e8j.js} +4 -2
- package/build/{cli-n2en7j5b.js → cli-n8mmw4c4.js} +1 -1
- package/build/cli-pndb7mh9.js +1193 -0
- package/build/cli-px6ehzqx.js +54 -0
- package/build/{cli-xtc2t6me.js → cli-qs4msn6x.js} +111 -47
- package/build/{cli-kb21qhvm.js → cli-rvybdhgn.js} +4 -3
- package/build/{cli-ae8jqxqr.js → cli-v739h7hb.js} +2 -2
- package/build/{cli-ngq9dr8n.js → cli-wm8ech0x.js} +3 -3
- package/build/cli.js +170 -51
- package/build/{config-zpqkd5cz.js → config-e5v1bkh1.js} +1 -1
- package/build/{fastStrike-mcjdvwez.js → fastStrike-efp0rjs3.js} +74 -17
- package/build/{fixes-kytx5dmj.js → fixes-926cw1ej.js} +16 -13
- package/build/{index-n65z6v7b.js → index-3t41p258.js} +7 -6
- package/build/{index-qv4cc65p.js → index-bv09af6g.js} +19 -15
- package/build/{index-je6hcsqr.js → index-bv6dtwse.js} +1 -1
- package/build/{index-4vqdgb42.js → index-jt52ffgn.js} +1657 -1400
- package/build/{index-ns4m4s01.js → index-np405b9e.js} +11 -7
- package/build/{index-r9yjkfrv.js → index-qys178kp.js} +5 -4
- package/build/{index-wqetp6gq.js → index-tpyzq2wq.js} +42 -11
- package/build/{index-fv64eb32.js → index-vrcdyebh.js} +2 -2
- package/build/{index-dr3tg1w3.js → index-z1g8vbtm.js} +1 -1
- package/build/{issues-eb3q9d3a.js → issues-sncyqh14.js} +16 -13
- package/build/{knowledge-fqsvtx0f.js → knowledge-esgvy5ws.js} +16 -13
- package/build/lance-sacfgs1z.js +232 -0
- package/build/lib/{chunk-9yksqgzr.js → chunk-4aevegnc.js} +77 -45
- package/build/lib/{chunk-9tt2021m.js → chunk-4kf4a15q.js} +13 -13
- package/build/lib/{chunk-fy3bbbqy.js → chunk-5avnedwm.js} +7 -1
- package/build/lib/{chunk-bb0ekhkw.js → chunk-5nz5tk3p.js} +76 -22
- package/build/lib/{chunk-4hwzqthr.js → chunk-7s8e5wrd.js} +3 -2
- package/build/lib/{chunk-yrnrbjes.js → chunk-85kpxwzs.js} +4 -2
- package/build/lib/{chunk-mg03jdmt.js → chunk-9jkzk9nc.js} +1 -1
- package/build/lib/{chunk-r4j8ybn9.js → chunk-9wwts6r6.js} +824 -77
- package/build/lib/{chunk-nhfax4gb.js → chunk-bxznzxdy.js} +5 -1
- package/build/lib/{chunk-r2dkp4tb.js → chunk-cqxm95md.js} +1 -1
- package/build/lib/{chunk-w66ce82a.js → chunk-e3agqspc.js} +12 -4
- package/build/lib/{chunk-ba41jh41.js → chunk-eh5yw385.js} +1137 -333
- package/build/lib/{chunk-1azdancq.js → chunk-ewc9r84m.js} +5 -1
- package/build/lib/{chunk-2njv7wz3.js → chunk-hbyxyxdc.js} +4 -0
- package/build/lib/{chunk-e8yy2k73.js → chunk-jhhwts6w.js} +28 -2
- package/build/lib/{chunk-1jvwqnem.js → chunk-kbxbd1kn.js} +108 -44
- package/build/lib/{chunk-7pwa92rn.js → chunk-mph5fvk7.js} +1 -1
- package/build/lib/chunk-psc23epq.js +107 -0
- package/build/lib/{chunk-acgbyf5h.js → chunk-qxad7qjd.js} +4 -2
- package/build/lib/{chunk-3c00n0cy.js → chunk-r0v3vvgm.js} +54 -33
- package/build/lib/{chunk-1yct6cra.js → chunk-rt13120a.js} +1 -1
- package/build/lib/{chunk-v3dk9j7k.js → chunk-sgya016v.js} +93 -22
- package/build/lib/{chunk-1fy8azee.js → chunk-txef92hq.js} +1 -1
- package/build/lib/{chunk-2qsq76xq.js → chunk-vcd90qrn.js} +189 -90
- package/build/lib/{chunk-7gjdygcx.js → chunk-vwr9w3ab.js} +2 -0
- package/build/lib/{chunk-z137x08b.js → chunk-wf6ak0m3.js} +92 -70
- package/build/lib/{chunk-xhdpgpkm.js → chunk-xft0ms9j.js} +2 -2
- package/build/lib/core/agents/agentRuntime.d.ts +3 -1
- package/build/lib/core/agents/defineAgent.d.ts +3 -2
- package/build/lib/core/agents/lance.d.ts +153 -0
- package/build/lib/core/agents/lanceJudge.d.ts +3 -0
- package/build/lib/core/agents/offSecAgent/index.d.ts +3 -1
- package/build/lib/core/agents/offSecAgent/index.js +16 -14
- package/build/lib/core/agents/offSecAgent/offensiveSecurityAgent.d.ts +4 -0
- package/build/lib/core/agents/offSecAgent/prompt.d.ts +3 -1
- package/build/lib/core/agents/offSecAgent/subagentSpawner.d.ts +18 -2
- package/build/lib/core/agents/offSecAgent/subagentSpawner.js +10 -8
- package/build/lib/core/agents/offSecAgent/toolExecutionContext.d.ts +3 -0
- package/build/lib/core/agents/offSecAgent/toolLifecycle.d.ts +1 -0
- package/build/lib/core/agents/offSecAgent/tools/index.js +10 -6
- 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 +6 -3
- package/build/lib/core/agents/offSecAgent/tools/whiteboxCandidates.d.ts +2 -2
- package/build/lib/core/agents/offSecAgent/types.d.ts +19 -3
- package/build/lib/core/agents/specialized/attackSurface/blackboxAgent.js +13 -13
- package/build/lib/core/agents/specialized/attackSurface/schemas.js +12 -12
- package/build/lib/core/agents/specialized/authenticationAgent/index.js +14 -14
- package/build/lib/core/agents/specialized/codeAgent/agent.js +13 -13
- package/build/lib/core/agents/specialized/codeAgent/prompts.d.ts +1 -0
- package/build/lib/core/agents/specialized/cvssScorer/index.d.ts +2 -1
- package/build/lib/core/agents/specialized/findingJudge/agent.js +11 -11
- package/build/lib/core/agents/specialized/findingJudge/index.d.ts +2 -1
- package/build/lib/core/agents/specialized/findingJudge/index.js +4 -4
- package/build/lib/core/agents/specialized/patching/index.js +14 -14
- package/build/lib/core/agents/specialized/pentest/agent.d.ts +18 -4
- package/build/lib/core/agents/specialized/pentest/agent.js +15 -13
- package/build/lib/core/agents/specialized/whiteboxAttackSurface/endpointDocumentationAgent.d.ts +2 -1
- package/build/lib/core/agents/specialized/whiteboxAttackSurface/index.js +13 -13
- package/build/lib/core/ai/ai.d.ts +29 -70
- package/build/lib/core/ai/ai.js +5 -3
- package/build/lib/core/ai/index.d.ts +1 -0
- package/build/lib/core/ai/index.js +16 -4
- 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/reasoning.d.ts +44 -0
- package/build/lib/core/ai/utils.d.ts +2 -0
- package/build/lib/core/ai/utils.js +5 -3
- package/build/lib/core/api/index.d.ts +6 -0
- package/build/lib/core/api/index.js +32 -23
- package/build/lib/core/api/lance.d.ts +6 -0
- package/build/lib/core/api/offesecAgent.d.ts +1 -0
- package/build/lib/core/api/reasoning.d.ts +12 -0
- package/build/lib/core/api/targetedPentest.d.ts +1 -0
- package/build/lib/core/api/targetedPentest.js +14 -14
- package/build/lib/core/cli/reasoning.d.ts +4 -0
- package/build/lib/core/config/config.d.ts +10 -2
- package/build/lib/core/config/customProviders.d.ts +11 -0
- package/build/lib/core/config/index.js +1 -1
- package/build/lib/core/eventBus.js +1 -1
- package/build/lib/core/findings/registry.d.ts +3 -1
- package/build/lib/core/findings/registry.js +4 -4
- package/build/lib/core/observability/index.js +1 -1
- package/build/lib/core/observability/telemetry.d.ts +4 -0
- package/build/lib/core/session/index.d.ts +47 -0
- package/build/lib/core/session/index.js +6 -4
- package/build/lib/core/session/persistence.d.ts +7 -0
- package/build/lib/core/session/persistence.js +8 -4
- package/build/lib/core/tools/backends/index.js +1 -1
- package/build/lib/core/tools/backends/resolve.d.ts +1 -0
- package/build/lib/core/workflows/fastStrike.d.ts +3 -1
- package/build/lib/core/workflows/pentest.d.ts +12 -2
- package/build/lib/core/workflows/pentest.js +19 -19
- package/build/lib/core/workflows/seams.js +5 -5
- package/build/lib/core/workflows/threatModel.d.ts +1 -0
- package/build/lib/core/workflows/whiteboxAttackSurface.d.ts +6 -4
- package/build/lib/core/workflows/whiteboxAttackSurface.js +15 -15
- package/build/lib/index.js +25 -25
- package/build/{logs-x62b7hk1.js → logs-bcbjpjp6.js} +16 -13
- package/build/{native-rollout-evidence-c9yhfqcs.js → native-rollout-evidence-rph1774m.js} +5 -5
- package/build/offesecAgent-81h1p6ee.js +32 -0
- package/build/pentest-2q20q6n1.js +23 -0
- package/build/{pentests-e0f32aqx.js → pentests-jgw031mc.js} +16 -13
- package/build/recordedRunClient-6axj6fba.js +11 -0
- package/build/subagentSpawner-a861gvt0.js +12 -0
- package/build/targetedPentest-6yxznt7s.js +91 -0
- package/build/{targets-xj9g3q0z.js → targets-bjah19dp.js} +16 -13
- package/build/threatModel-739kpexm.js +24 -0
- package/build/{uninstall-7s784q4a.js → uninstall-yxrn9edt.js} +1 -1
- package/build/{upload-4gvhwmvr.js → upload-34ewrjp5.js} +4 -4
- package/build/{utils-cjrz64pc.js → utils-fzm66efw.js} +5 -5
- package/package.json +2 -1
- package/src/cli/issues.test.ts +17 -1
- package/src/cli/lance-verifier.test.ts +57 -0
- package/src/cli/lance-verifier.ts +73 -0
- package/src/cli/lance.test.ts +71 -0
- package/src/cli/lance.ts +48 -0
- package/src/cli.ts +109 -19
- package/src/core/agents/agentRuntime.test.ts +42 -0
- package/src/core/agents/agentRuntime.ts +7 -2
- package/src/core/agents/defineAgent.ts +3 -1
- package/src/core/agents/lance.test.ts +421 -0
- package/src/core/agents/lance.ts +701 -0
- package/src/core/agents/lanceJudge.test.ts +166 -0
- package/src/core/agents/lanceJudge.ts +115 -0
- package/src/core/agents/offSecAgent/index.ts +3 -0
- package/src/core/agents/offSecAgent/interruptedStepFinalization.test.ts +51 -0
- package/src/core/agents/offSecAgent/lance.test.ts +188 -0
- package/src/core/agents/offSecAgent/offensiveSecurityAgent.test.ts +92 -5
- package/src/core/agents/offSecAgent/offensiveSecurityAgent.ts +244 -41
- 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.hooks.test.ts +11 -3
- package/src/core/agents/offSecAgent/subagentSpawner.reasoning.test.ts +167 -0
- package/src/core/agents/offSecAgent/subagentSpawner.ts +82 -28
- package/src/core/agents/offSecAgent/toolExecutionContext.test.ts +230 -0
- package/src/core/agents/offSecAgent/toolExecutionContext.ts +56 -0
- package/src/core/agents/offSecAgent/toolLifecycle.ts +2 -0
- package/src/core/agents/offSecAgent/tools/delegateAuth.ts +4 -2
- package/src/core/agents/offSecAgent/tools/documentFinding.test.ts +9 -4
- package/src/core/agents/offSecAgent/tools/documentFinding.ts +10 -9
- 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 +7 -4
- package/src/core/agents/offSecAgent/tools/runPentestWorkflow.test.ts +57 -0
- package/src/core/agents/offSecAgent/tools/runPentestWorkflow.ts +21 -1
- package/src/core/agents/offSecAgent/tools/runWhiteboxScan.ts +5 -2
- package/src/core/agents/offSecAgent/tools/spawnCodingAgent.ts +50 -67
- package/src/core/agents/offSecAgent/tools/spawnPentestAgent.test.ts +79 -3
- package/src/core/agents/offSecAgent/tools/spawnPentestAgent.ts +36 -16
- package/src/core/agents/offSecAgent/tools/spawnPentestSwarm.ts +28 -5
- package/src/core/agents/offSecAgent/tools/threatModelGenerator.ts +4 -3
- package/src/core/agents/offSecAgent/tools/types.ts +12 -2
- 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 +22 -1
- 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/cvssScorer/index.test.ts +2 -1
- package/src/core/agents/specialized/cvssScorer/index.ts +3 -0
- package/src/core/agents/specialized/findingJudge/index.ts +6 -0
- package/src/core/agents/specialized/pentest/agent.test.ts +155 -0
- package/src/core/agents/specialized/pentest/agent.ts +181 -52
- package/src/core/agents/specialized/pentest/results.test.ts +59 -4
- package/src/core/agents/specialized/whiteboxAttackSurface/endpointDocumentationAgent.concurrency.test.ts +59 -2
- package/src/core/agents/specialized/whiteboxAttackSurface/endpointDocumentationAgent.hooks.test.ts +127 -0
- package/src/core/agents/specialized/whiteboxAttackSurface/endpointDocumentationAgent.ts +74 -68
- package/src/core/ai/ai.durability-hooks.test.ts +25 -7
- package/src/core/ai/ai.lance.test.ts +581 -0
- package/src/core/ai/ai.operator-context.bench.test.ts +1390 -0
- package/src/core/ai/ai.recorded-context.test.ts +89 -8
- package/src/core/ai/ai.test.ts +81 -59
- package/src/core/ai/ai.ts +453 -327
- package/src/core/ai/compactionTelemetry.test.ts +69 -17
- package/src/core/ai/index.ts +11 -0
- 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 +179 -7
- package/src/core/ai/models/openrouter-reasoning.json +219 -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/custom.test.ts +74 -0
- package/src/core/ai/providers/custom.ts +3 -1
- package/src/core/ai/providers/gateway-models.test.ts +16 -6
- package/src/core/ai/providers/pensarFormatters.ts +8 -1
- package/src/core/ai/reasoning.test.ts +313 -0
- package/src/core/ai/reasoning.ts +500 -0
- package/src/core/ai/streamIdleTimeout.test.ts +21 -0
- package/src/core/ai/utils.native-rollout-evidence.test.ts +21 -8
- package/src/core/ai/utils.ts +269 -163
- package/src/core/api/attackSurface.ts +1 -0
- package/src/core/api/blackboxPentest.ts +9 -1
- package/src/core/api/index.ts +17 -0
- package/src/core/api/lance.ts +23 -0
- package/src/core/api/offesecAgent.test.ts +51 -0
- package/src/core/api/offesecAgent.ts +15 -1
- package/src/core/api/reasoning.test.ts +213 -0
- package/src/core/api/reasoning.ts +44 -0
- package/src/core/api/recordedRun.loop.test.ts +5 -1
- package/src/core/api/targetedPentest.test.ts +14 -2
- package/src/core/api/targetedPentest.ts +52 -5
- package/src/core/cli/reasoning.test.ts +149 -0
- package/src/core/cli/reasoning.ts +95 -0
- package/src/core/config/config.test.ts +35 -0
- package/src/core/config/config.ts +11 -0
- package/src/core/config/customProviders.ts +21 -2
- package/src/core/eventBus.ts +1 -0
- package/src/core/findings/registry.test.ts +9 -1
- package/src/core/findings/registry.ts +6 -0
- package/src/core/observability/telemetry.test.ts +131 -79
- package/src/core/observability/telemetry.ts +5 -0
- package/src/core/runtime/sqliteRunObservation.test.ts +1 -1
- package/src/core/session/index.ts +38 -0
- package/src/core/session/persistence.test.ts +62 -0
- package/src/core/session/persistence.ts +56 -0
- package/src/core/tools/backends/resolve.ts +7 -0
- package/src/core/tools/backends/whiteboxJobs.test.ts +9 -8
- 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 +106 -11
- package/src/core/workflows/pentest.test.ts +104 -0
- package/src/core/workflows/pentest.ts +128 -7
- package/src/core/workflows/seams.test.ts +13 -0
- package/src/core/workflows/seams.ts +5 -1
- package/src/core/workflows/threatModel.ts +2 -0
- package/src/core/workflows/whiteboxAttackSurface.test.ts +74 -2
- package/src/core/workflows/whiteboxAttackSurface.ts +102 -73
- package/src/tui/command-registry.test.ts +12 -0
- package/src/tui/command-registry.ts +10 -0
- package/src/tui/components/chat/config-view.tsx +6 -10
- package/src/tui/components/chat/home-view.tsx +2 -2
- package/src/tui/components/chat/input-area.tsx +2 -2
- 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 +164 -0
- package/src/tui/components/commands/advanced-settings.test.ts +34 -0
- package/src/tui/components/commands/advanced-settings.tsx +220 -31
- package/src/tui/components/commands/models-display.tsx +7 -1
- package/src/tui/components/model-picker/ModelPicker.tsx +65 -103
- package/src/tui/components/model-picker/ModelPickerDialog.tsx +8 -14
- package/src/tui/components/model-picker/__tests__/reasoning-control.tsx +162 -0
- package/src/tui/components/model-picker/reasoning-control.test.ts +15 -0
- package/src/tui/components/operator-dashboard/index.tsx +166 -50
- package/src/tui/components/operator-dashboard/logic.test.ts +144 -2
- package/src/tui/components/operator-dashboard/logic.ts +54 -3
- 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/agent.tsx +32 -79
- package/src/tui/context/route.tsx +2 -0
- package/src/tui/index.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/blackboxAgent-s56sgx9b.js +0 -13
- package/build/cli-g06s7edq.js +0 -11
- package/build/lib/chunk-z96pbjhd.js +0 -56
- package/build/offesecAgent-5whnpeta.js +0 -22
- package/build/pentest-e61hc38s.js +0 -22
- package/build/recordedRunClient-dp032c32.js +0 -11
- package/build/targetedPentest-rcbs4nvp.js +0 -57
- package/build/threatModel-jkxfa40s.js +0 -21
|
@@ -0,0 +1,1193 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createLogger,
|
|
3
|
+
init_structured,
|
|
4
|
+
scopedLogger,
|
|
5
|
+
init_lazyLogger,
|
|
6
|
+
getModelSettings,
|
|
7
|
+
generateObjectResponse,
|
|
8
|
+
init_ai2
|
|
9
|
+
} from "./cli-e9v6zg6h.js";
|
|
10
|
+
import {
|
|
11
|
+
newSessionId,
|
|
12
|
+
init_id,
|
|
13
|
+
runWithNativeRolloutSession
|
|
14
|
+
} from "./cli-se8ae8z3.js";
|
|
15
|
+
import {
|
|
16
|
+
init_zod
|
|
17
|
+
} from "./cli-3qznjsnr.js";
|
|
18
|
+
import {
|
|
19
|
+
string,
|
|
20
|
+
number,
|
|
21
|
+
boolean,
|
|
22
|
+
array,
|
|
23
|
+
object
|
|
24
|
+
} from "./cli-mz5pk30x.js";
|
|
25
|
+
import {
|
|
26
|
+
require_src6
|
|
27
|
+
} from "./cli-bb02pjt0.js";
|
|
28
|
+
import {
|
|
29
|
+
__toESM
|
|
30
|
+
} from "./cli-h6y3bt60.js";
|
|
31
|
+
|
|
32
|
+
// src/core/eventBus.ts
|
|
33
|
+
import { EventEmitter } from "node:events";
|
|
34
|
+
var CHILD_BUS_FORWARD_POLICY = {
|
|
35
|
+
"text-delta": "forward",
|
|
36
|
+
"reasoning-delta": "forward",
|
|
37
|
+
"reasoning-start": "forward",
|
|
38
|
+
"reasoning-end": "forward",
|
|
39
|
+
"tool-call-start": "forward",
|
|
40
|
+
"tool-call-delta": "forward",
|
|
41
|
+
"tool-call-complete": "forward",
|
|
42
|
+
"tool-result": "forward",
|
|
43
|
+
"subagent-spawn": "forward",
|
|
44
|
+
"subagent-complete": "forward",
|
|
45
|
+
"step-finish": "forward",
|
|
46
|
+
"command-output": "forward",
|
|
47
|
+
error: "forward",
|
|
48
|
+
"trace-record": "forward",
|
|
49
|
+
"workflow-phase-start": "parent-only",
|
|
50
|
+
"workflow-phase-complete": "parent-only",
|
|
51
|
+
"app-analysis-progress": "parent-only"
|
|
52
|
+
};
|
|
53
|
+
var CHILD_BUS_FORWARDED_EVENTS = Object.keys(CHILD_BUS_FORWARD_POLICY).filter((key) => CHILD_BUS_FORWARD_POLICY[key] === "forward");
|
|
54
|
+
|
|
55
|
+
class AgentEventBus {
|
|
56
|
+
emitter = new EventEmitter;
|
|
57
|
+
constructor() {
|
|
58
|
+
this.emitter.setMaxListeners(50);
|
|
59
|
+
}
|
|
60
|
+
on(event, handler) {
|
|
61
|
+
this.emitter.on(event, handler);
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
64
|
+
once(event, handler) {
|
|
65
|
+
this.emitter.once(event, handler);
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
off(event, handler) {
|
|
69
|
+
this.emitter.off(event, handler);
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
emit(event, payload) {
|
|
73
|
+
return this.emitter.emit(event, payload);
|
|
74
|
+
}
|
|
75
|
+
removeAllListeners(event) {
|
|
76
|
+
if (event) {
|
|
77
|
+
this.emitter.removeAllListeners(event);
|
|
78
|
+
} else {
|
|
79
|
+
this.emitter.removeAllListeners();
|
|
80
|
+
}
|
|
81
|
+
return this;
|
|
82
|
+
}
|
|
83
|
+
static attachChild(child, parent, childSessionId) {
|
|
84
|
+
if (!parent)
|
|
85
|
+
return;
|
|
86
|
+
for (const key of CHILD_BUS_FORWARDED_EVENTS) {
|
|
87
|
+
const forwardKey = key;
|
|
88
|
+
child.on(forwardKey, (payload) => {
|
|
89
|
+
const p = payload;
|
|
90
|
+
const isLifecycle = forwardKey === "subagent-spawn" || forwardKey === "subagent-complete";
|
|
91
|
+
if (isLifecycle) {
|
|
92
|
+
const next = { ...p };
|
|
93
|
+
if (!p.parentSubagentId)
|
|
94
|
+
next.parentSubagentId = childSessionId;
|
|
95
|
+
if (!p.parentSessionId)
|
|
96
|
+
next.parentSessionId = childSessionId;
|
|
97
|
+
parent.emit(forwardKey, next);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const next = { ...p };
|
|
101
|
+
if (!p.subagentId)
|
|
102
|
+
next.subagentId = childSessionId;
|
|
103
|
+
if (!p.sessionId)
|
|
104
|
+
next.sessionId = childSessionId;
|
|
105
|
+
parent.emit(forwardKey, next);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
emitStreamPart(chunk, ids) {
|
|
110
|
+
const ctx = typeof ids === "string" ? { subagentId: ids } : ids ?? {};
|
|
111
|
+
const subagentId = ctx.subagentId;
|
|
112
|
+
const sessionId = ctx.sessionId;
|
|
113
|
+
const messageId = ctx.messageId;
|
|
114
|
+
switch (chunk.type) {
|
|
115
|
+
case "text-delta":
|
|
116
|
+
this.emit("text-delta", {
|
|
117
|
+
text: chunk.text,
|
|
118
|
+
subagentId,
|
|
119
|
+
sessionId,
|
|
120
|
+
messageId,
|
|
121
|
+
partId: ctx.textPartId
|
|
122
|
+
});
|
|
123
|
+
break;
|
|
124
|
+
case "reasoning-start":
|
|
125
|
+
this.emit("reasoning-start", { subagentId, sessionId, messageId });
|
|
126
|
+
break;
|
|
127
|
+
case "reasoning-delta":
|
|
128
|
+
this.emit("reasoning-delta", {
|
|
129
|
+
text: chunk.text,
|
|
130
|
+
subagentId,
|
|
131
|
+
sessionId,
|
|
132
|
+
messageId
|
|
133
|
+
});
|
|
134
|
+
break;
|
|
135
|
+
case "reasoning-end":
|
|
136
|
+
this.emit("reasoning-end", { subagentId, sessionId, messageId });
|
|
137
|
+
break;
|
|
138
|
+
case "tool-input-start":
|
|
139
|
+
this.emit("tool-call-start", {
|
|
140
|
+
toolCallId: chunk.id,
|
|
141
|
+
toolName: chunk.toolName,
|
|
142
|
+
subagentId,
|
|
143
|
+
sessionId,
|
|
144
|
+
messageId,
|
|
145
|
+
partId: ctx.toolPartId?.(chunk.id)
|
|
146
|
+
});
|
|
147
|
+
break;
|
|
148
|
+
case "tool-input-delta":
|
|
149
|
+
this.emit("tool-call-delta", {
|
|
150
|
+
toolCallId: chunk.id,
|
|
151
|
+
argsTextDelta: chunk.delta,
|
|
152
|
+
subagentId,
|
|
153
|
+
sessionId,
|
|
154
|
+
messageId,
|
|
155
|
+
partId: ctx.toolPartId?.(chunk.id)
|
|
156
|
+
});
|
|
157
|
+
break;
|
|
158
|
+
case "tool-call": {
|
|
159
|
+
const tc = chunk;
|
|
160
|
+
this.emit("tool-call-complete", {
|
|
161
|
+
toolCallId: tc.toolCallId,
|
|
162
|
+
toolName: tc.toolName,
|
|
163
|
+
args: tc.args ?? tc.input,
|
|
164
|
+
subagentId,
|
|
165
|
+
sessionId,
|
|
166
|
+
messageId,
|
|
167
|
+
partId: ctx.toolPartId?.(tc.toolCallId)
|
|
168
|
+
});
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
case "tool-result": {
|
|
172
|
+
if (chunk.preliminary)
|
|
173
|
+
break;
|
|
174
|
+
const tr = chunk;
|
|
175
|
+
this.emit("tool-result", {
|
|
176
|
+
toolCallId: tr.toolCallId,
|
|
177
|
+
toolName: tr.toolName,
|
|
178
|
+
result: tr.result ?? tr.output,
|
|
179
|
+
subagentId,
|
|
180
|
+
sessionId,
|
|
181
|
+
messageId,
|
|
182
|
+
partId: ctx.toolPartId?.(tr.toolCallId)
|
|
183
|
+
});
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
case "error":
|
|
187
|
+
this.emit("error", {
|
|
188
|
+
error: chunk.error,
|
|
189
|
+
subagentId
|
|
190
|
+
});
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// src/core/agents/offSecAgent/subagentSpawner.ts
|
|
197
|
+
init_ai2();
|
|
198
|
+
|
|
199
|
+
// src/core/utils/concurrency.ts
|
|
200
|
+
var import_api = __toESM(require_src6(), 1);
|
|
201
|
+
async function runWithBoundedConcurrency(items, concurrency, fn, abortSignal) {
|
|
202
|
+
const results = new Array(items.length).fill(null);
|
|
203
|
+
const parentContext = import_api.context.active();
|
|
204
|
+
let nextIdx = 0;
|
|
205
|
+
let completed = 0;
|
|
206
|
+
await new Promise((resolve) => {
|
|
207
|
+
if (items.length === 0) {
|
|
208
|
+
resolve();
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
let active = 0;
|
|
212
|
+
function canLaunchMore() {
|
|
213
|
+
return active < concurrency && nextIdx < items.length && !abortSignal?.aborted;
|
|
214
|
+
}
|
|
215
|
+
function next() {
|
|
216
|
+
if (completed >= nextIdx && !canLaunchMore()) {
|
|
217
|
+
resolve();
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
while (canLaunchMore()) {
|
|
221
|
+
const idx = nextIdx++;
|
|
222
|
+
active++;
|
|
223
|
+
import_api.context.with(parentContext, () => fn(items[idx], idx)).then((r) => {
|
|
224
|
+
results[idx] = r;
|
|
225
|
+
}).catch(() => {
|
|
226
|
+
results[idx] = null;
|
|
227
|
+
}).finally(() => {
|
|
228
|
+
active--;
|
|
229
|
+
completed++;
|
|
230
|
+
next();
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
next();
|
|
235
|
+
});
|
|
236
|
+
return results;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/core/findings/attackSurfaceRegistry.ts
|
|
240
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
241
|
+
import { join } from "node:path";
|
|
242
|
+
function normalizeAssetUrl(url) {
|
|
243
|
+
let u = url.trim().toLowerCase();
|
|
244
|
+
const hashIdx = u.indexOf("#");
|
|
245
|
+
if (hashIdx !== -1)
|
|
246
|
+
u = u.substring(0, hashIdx);
|
|
247
|
+
const qIdx = u.indexOf("?");
|
|
248
|
+
if (qIdx !== -1)
|
|
249
|
+
u = u.substring(0, qIdx);
|
|
250
|
+
u = u.replace(/^https?:\/\//, "");
|
|
251
|
+
u = u.replace(/^www\./, "");
|
|
252
|
+
if (u.length > 1 && u.endsWith("/")) {
|
|
253
|
+
u = u.replace(/\/+$/, "");
|
|
254
|
+
}
|
|
255
|
+
return u;
|
|
256
|
+
}
|
|
257
|
+
function normalizeAssetName(name) {
|
|
258
|
+
return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
259
|
+
}
|
|
260
|
+
function generateAssetFingerprint(asset) {
|
|
261
|
+
const normalizedName = normalizeAssetName(asset.assetName);
|
|
262
|
+
const assetType = asset.assetType.toLowerCase();
|
|
263
|
+
const url = asset.details?.url;
|
|
264
|
+
const appPrefix = asset.appName ? `${normalizeAssetName(asset.appName)}||` : "";
|
|
265
|
+
return {
|
|
266
|
+
urlKey: url ? `${appPrefix}${normalizeAssetUrl(url)}||${assetType}` : null,
|
|
267
|
+
nameKey: `${appPrefix}${normalizedName}||${assetType}`
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
class AttackSurfaceRegistry {
|
|
272
|
+
urlKeys = new Map;
|
|
273
|
+
nameKeys = new Map;
|
|
274
|
+
assets = [];
|
|
275
|
+
mutex = Promise.resolve();
|
|
276
|
+
get size() {
|
|
277
|
+
return this.assets.length;
|
|
278
|
+
}
|
|
279
|
+
getAssets() {
|
|
280
|
+
return [...this.assets];
|
|
281
|
+
}
|
|
282
|
+
static fromDirectory(assetsPath) {
|
|
283
|
+
const registry = new AttackSurfaceRegistry;
|
|
284
|
+
if (!existsSync(assetsPath))
|
|
285
|
+
return registry;
|
|
286
|
+
const files = readdirSync(assetsPath).filter((f) => f.endsWith(".json"));
|
|
287
|
+
for (const file of files) {
|
|
288
|
+
try {
|
|
289
|
+
const raw = readFileSync(join(assetsPath, file), "utf-8");
|
|
290
|
+
const asset = JSON.parse(raw);
|
|
291
|
+
if (asset && typeof asset.assetName === "string" && typeof asset.assetType === "string") {
|
|
292
|
+
registry.indexAsset(asset);
|
|
293
|
+
}
|
|
294
|
+
} catch {}
|
|
295
|
+
}
|
|
296
|
+
return registry;
|
|
297
|
+
}
|
|
298
|
+
static fromAssets(assets) {
|
|
299
|
+
const registry = new AttackSurfaceRegistry;
|
|
300
|
+
for (const a of assets) {
|
|
301
|
+
registry.indexAsset(a);
|
|
302
|
+
}
|
|
303
|
+
return registry;
|
|
304
|
+
}
|
|
305
|
+
isDuplicate(asset) {
|
|
306
|
+
const { urlKey, nameKey } = generateAssetFingerprint(asset);
|
|
307
|
+
if (urlKey) {
|
|
308
|
+
const urlMatch = this.urlKeys.get(urlKey);
|
|
309
|
+
if (urlMatch) {
|
|
310
|
+
return {
|
|
311
|
+
duplicate: true,
|
|
312
|
+
matchedAsset: urlMatch,
|
|
313
|
+
matchType: "exact-url"
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
const nameMatch = this.nameKeys.get(nameKey);
|
|
318
|
+
if (nameMatch) {
|
|
319
|
+
return {
|
|
320
|
+
duplicate: true,
|
|
321
|
+
matchedAsset: nameMatch,
|
|
322
|
+
matchType: "exact-name"
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
return { duplicate: false };
|
|
326
|
+
}
|
|
327
|
+
async register(asset) {
|
|
328
|
+
return new Promise((resolve) => {
|
|
329
|
+
this.mutex = this.mutex.then(() => {
|
|
330
|
+
const check = this.isDuplicate(asset);
|
|
331
|
+
if (check.duplicate) {
|
|
332
|
+
resolve(check);
|
|
333
|
+
} else {
|
|
334
|
+
this.indexAsset(asset);
|
|
335
|
+
resolve({ duplicate: false });
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
async unregister(asset) {
|
|
341
|
+
return new Promise((resolve) => {
|
|
342
|
+
this.mutex = this.mutex.then(() => {
|
|
343
|
+
this.removeAsset(asset);
|
|
344
|
+
resolve();
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
removeAsset(asset) {
|
|
349
|
+
const { urlKey, nameKey } = generateAssetFingerprint(asset);
|
|
350
|
+
if (urlKey && this.urlKeys.get(urlKey) === asset) {
|
|
351
|
+
this.urlKeys.delete(urlKey);
|
|
352
|
+
}
|
|
353
|
+
if (this.nameKeys.get(nameKey) === asset) {
|
|
354
|
+
this.nameKeys.delete(nameKey);
|
|
355
|
+
}
|
|
356
|
+
const idx = this.assets.indexOf(asset);
|
|
357
|
+
if (idx !== -1) {
|
|
358
|
+
this.assets.splice(idx, 1);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
indexAsset(asset) {
|
|
362
|
+
const { urlKey, nameKey } = generateAssetFingerprint(asset);
|
|
363
|
+
if (urlKey && !this.urlKeys.has(urlKey)) {
|
|
364
|
+
this.urlKeys.set(urlKey, asset);
|
|
365
|
+
}
|
|
366
|
+
if (!this.nameKeys.has(nameKey)) {
|
|
367
|
+
this.nameKeys.set(nameKey, asset);
|
|
368
|
+
}
|
|
369
|
+
this.assets.push(asset);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// src/core/findings/registry.ts
|
|
374
|
+
init_zod();
|
|
375
|
+
init_ai2();
|
|
376
|
+
init_structured();
|
|
377
|
+
init_lazyLogger();
|
|
378
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
379
|
+
import { join as join2 } from "node:path";
|
|
380
|
+
var log = scopedLogger(() => createLogger("findings:registry"));
|
|
381
|
+
var VULN_CLASS_PATTERNS = [
|
|
382
|
+
[/sql\s*injection/i, "sql-injection"],
|
|
383
|
+
[/command\s*injection/i, "command-injection"],
|
|
384
|
+
[/remote\s*code\s*execution|(\b)rce(\b)/i, "rce"],
|
|
385
|
+
[/server[\s-]*side\s*request\s*forgery|(\b)ssrf(\b)/i, "ssrf"],
|
|
386
|
+
[/cross[\s-]*site\s*request\s*forgery|(\b)csrf(\b)/i, "csrf"],
|
|
387
|
+
[
|
|
388
|
+
/path\s*traversal|directory\s*traversal|local\s*file\s*inclusion|\blfi\b/i,
|
|
389
|
+
"path-traversal"
|
|
390
|
+
],
|
|
391
|
+
[
|
|
392
|
+
/\bmissing\s*auth|\bno\s*auth|unauthenticated\s+access/i,
|
|
393
|
+
"missing-authentication"
|
|
394
|
+
],
|
|
395
|
+
[/\bidor\b|insecure\s*direct\s*object/i, "idor"],
|
|
396
|
+
[/\bxss\b|cross[\s-]*site\s*scripting/i, "xss"],
|
|
397
|
+
[/missing\s*content\s*security\s*policy|missing\s*csp\b/i, "missing-csp"],
|
|
398
|
+
[/\bcors\b|cross[\s-]*origin/i, "cors"],
|
|
399
|
+
[/authentication\s*bypass|auth\s*bypass/i, "auth-bypass"],
|
|
400
|
+
[/privilege\s*escalation/i, "privilege-escalation"],
|
|
401
|
+
[
|
|
402
|
+
/information\s*disclosure|sensitive\s*data\s*exposure/i,
|
|
403
|
+
"information-disclosure"
|
|
404
|
+
],
|
|
405
|
+
[/open\s*redirect/i, "open-redirect"],
|
|
406
|
+
[
|
|
407
|
+
/missing.*rate[\s-]*limit|rate[\s-]*limit.*missing|no\s*rate[\s-]*limit/i,
|
|
408
|
+
"missing-rate-limiting"
|
|
409
|
+
],
|
|
410
|
+
[
|
|
411
|
+
/user[\s-]*enumeration|account[\s-]*enumeration|email[\s-]*enumeration/i,
|
|
412
|
+
"user-enumeration"
|
|
413
|
+
],
|
|
414
|
+
[
|
|
415
|
+
/hardcoded[\s-]*credential|hardcoded[\s-]*password|hardcoded[\s-]*key|exposed[\s-]*api[\s-]*key/i,
|
|
416
|
+
"hardcoded-credentials"
|
|
417
|
+
],
|
|
418
|
+
[/captcha[\s-]*bypass|missing[\s-]*captcha/i, "captcha-bypass"],
|
|
419
|
+
[/missing.*header|security\s*header/i, "missing-security-header"]
|
|
420
|
+
];
|
|
421
|
+
var URL_PATH_PATTERN = /(?:https?:\/\/[^\s]+|\/[a-z0-9_\-/{}.:]+|\b(?:endpoint|url|path|route)\b)/gi;
|
|
422
|
+
function extractVulnClass(title) {
|
|
423
|
+
const t = title.trim();
|
|
424
|
+
for (const [pattern, cls] of VULN_CLASS_PATTERNS) {
|
|
425
|
+
if (pattern.test(t))
|
|
426
|
+
return cls;
|
|
427
|
+
}
|
|
428
|
+
return t.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").substring(0, 60);
|
|
429
|
+
}
|
|
430
|
+
function classifyFromContent(finding) {
|
|
431
|
+
const titleClass = extractVulnClass(finding.title);
|
|
432
|
+
if (titleClass === "idor") {
|
|
433
|
+
const corpus = `${finding.description ?? ""} ${finding.evidence ?? ""}`;
|
|
434
|
+
const missingAuthPattern = /no\s+authentication|unauthenticated|without\s+authentication|PR:N|PR=N/i;
|
|
435
|
+
if (missingAuthPattern.test(corpus)) {
|
|
436
|
+
return "missing-authentication";
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return titleClass;
|
|
440
|
+
}
|
|
441
|
+
function extractTitleStem(title) {
|
|
442
|
+
return title.replace(URL_PATH_PATTERN, "").replace(/[^a-zA-Z0-9 ]/g, " ").replace(/\s+/g, " ").trim().toLowerCase();
|
|
443
|
+
}
|
|
444
|
+
function normalizeEndpoint(endpoint) {
|
|
445
|
+
let e = endpoint.trim().toLowerCase();
|
|
446
|
+
const hashIdx = e.indexOf("#");
|
|
447
|
+
if (hashIdx !== -1)
|
|
448
|
+
e = e.substring(0, hashIdx);
|
|
449
|
+
const qIdx = e.indexOf("?");
|
|
450
|
+
if (qIdx !== -1)
|
|
451
|
+
e = e.substring(0, qIdx);
|
|
452
|
+
if (e.length > 1 && e.endsWith("/")) {
|
|
453
|
+
e = e.replace(/\/+$/, "");
|
|
454
|
+
}
|
|
455
|
+
return e;
|
|
456
|
+
}
|
|
457
|
+
function generateFingerprint(finding) {
|
|
458
|
+
const vulnClass = classifyFromContent(finding);
|
|
459
|
+
const stem = extractTitleStem(finding.title);
|
|
460
|
+
const endpoint = normalizeEndpoint(finding.endpoint);
|
|
461
|
+
return {
|
|
462
|
+
exactKey: `${endpoint}||${vulnClass}`,
|
|
463
|
+
appWideKey: stem
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
var SemanticDedupResultSchema = object({
|
|
467
|
+
isDuplicate: boolean().describe("Whether the new finding is a duplicate of an existing one"),
|
|
468
|
+
matchedIndex: number().optional().describe("1-based index of the matched existing finding, if duplicate"),
|
|
469
|
+
reasoning: string().describe("Brief explanation of why this is or is not a duplicate")
|
|
470
|
+
});
|
|
471
|
+
var SEMANTIC_DEDUP_SYSTEM = `You are a security finding deduplication specialist. Your task is to determine whether a new vulnerability finding is a duplicate of any existing findings.
|
|
472
|
+
|
|
473
|
+
Two findings are duplicates if they describe the SAME underlying vulnerability, even if:
|
|
474
|
+
- They use different phrasing or terminology
|
|
475
|
+
- They affect different endpoints but share the same root cause
|
|
476
|
+
- One is more specific/detailed than the other
|
|
477
|
+
|
|
478
|
+
Two findings are NOT duplicates if they describe genuinely different vulnerabilities, even if:
|
|
479
|
+
- They affect the same endpoint
|
|
480
|
+
- They are the same class of vulnerability but with different root causes (e.g., reflected XSS vs stored XSS)
|
|
481
|
+
|
|
482
|
+
Be conservative: when in doubt, mark as NOT a duplicate. It is better to allow a borderline finding through than to suppress a genuinely new one.`;
|
|
483
|
+
var MAX_SEMANTIC_DEDUP_FINDINGS = 25;
|
|
484
|
+
function buildSemanticDedupPrompt(newFinding, existingFindings) {
|
|
485
|
+
const capped = existingFindings.slice(-MAX_SEMANTIC_DEDUP_FINDINGS);
|
|
486
|
+
const offset = existingFindings.length - capped.length;
|
|
487
|
+
const existingList = capped.map((f, i) => `${offset + i + 1}. [${f.severity}] "${f.title}" — endpoint: ${f.endpoint}
|
|
488
|
+
Description: ${f.description.substring(0, 200)}`).join(`
|
|
489
|
+
|
|
490
|
+
`);
|
|
491
|
+
return `## New Finding
|
|
492
|
+
Title: "${newFinding.title}"
|
|
493
|
+
Endpoint: ${newFinding.endpoint}
|
|
494
|
+
Severity: ${newFinding.severity}
|
|
495
|
+
Description: ${newFinding.description.substring(0, 300)}
|
|
496
|
+
|
|
497
|
+
## Existing Findings (${existingFindings.length} total${offset > 0 ? `, showing last ${capped.length}` : ""})
|
|
498
|
+
${existingList}
|
|
499
|
+
|
|
500
|
+
Is the new finding a duplicate of any existing finding?`;
|
|
501
|
+
}
|
|
502
|
+
var SEVERITY_RANK = {
|
|
503
|
+
CRITICAL: 4,
|
|
504
|
+
HIGH: 3,
|
|
505
|
+
MEDIUM: 2,
|
|
506
|
+
LOW: 1
|
|
507
|
+
};
|
|
508
|
+
function maxSeverity(severities) {
|
|
509
|
+
return severities.reduce((acc, s) => SEVERITY_RANK[s] > SEVERITY_RANK[acc] ? s : acc, "LOW");
|
|
510
|
+
}
|
|
511
|
+
var RootCauseGroupResultSchema = object({
|
|
512
|
+
groups: array(object({
|
|
513
|
+
groupId: string().describe("Short kebab-case identifier for the group"),
|
|
514
|
+
findingIndices: array(number()).describe("0-based indices of findings that share this root cause"),
|
|
515
|
+
rootCause: string().describe("Brief description of the shared underlying misconfiguration or weakness")
|
|
516
|
+
}))
|
|
517
|
+
});
|
|
518
|
+
var ROOT_CAUSE_GROUPING_SYSTEM = `You are a security finding analyst. Your task is to identify findings that share a common root cause.
|
|
519
|
+
|
|
520
|
+
These are NOT duplicates — they are distinct findings that stem from the same underlying misconfiguration, weakness, or architectural issue. Examples:
|
|
521
|
+
- "Email Enumeration via Forgot Password" and "Username Enumeration via Cognito Lockout Response" both stem from Cognito leaking user-existence information
|
|
522
|
+
- "Missing Rate Limiting on Email API" and "Unauthenticated Email Sending via SES API" on the same endpoint — the unauthenticated access is the root cause that enables both
|
|
523
|
+
- Multiple IDOR findings on different endpoints that all stem from missing authorization middleware
|
|
524
|
+
|
|
525
|
+
Group findings when:
|
|
526
|
+
1. One finding enables or is prerequisite to another
|
|
527
|
+
2. Both stem from the same underlying misconfiguration (e.g. same missing middleware, same leaky service)
|
|
528
|
+
3. Fixing the root cause would resolve or mitigate all findings in the group
|
|
529
|
+
|
|
530
|
+
Do NOT group findings that merely share a vulnerability class (e.g. two unrelated XSS on different endpoints with different root causes).
|
|
531
|
+
|
|
532
|
+
Return an empty groups array if no findings share a root cause.`;
|
|
533
|
+
function buildRootCauseGroupingPrompt(findings) {
|
|
534
|
+
const list = findings.map((f, i) => `${i}. [${f.severity}] "${f.title}" — endpoint: ${f.endpoint}
|
|
535
|
+
Description: ${f.description.substring(0, 200)}`).join(`
|
|
536
|
+
|
|
537
|
+
`);
|
|
538
|
+
return `## Findings (${findings.length} total)
|
|
539
|
+
${list}
|
|
540
|
+
|
|
541
|
+
Which findings share a common root cause? Return groups using 0-based indices.`;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
class FindingsRegistry {
|
|
545
|
+
exactKeys = new Map;
|
|
546
|
+
appWideKeys = new Map;
|
|
547
|
+
findings = [];
|
|
548
|
+
model;
|
|
549
|
+
reasoning;
|
|
550
|
+
authConfig;
|
|
551
|
+
abortSignal;
|
|
552
|
+
sessionId;
|
|
553
|
+
languageModelMiddleware;
|
|
554
|
+
usageRecorder;
|
|
555
|
+
mutex = Promise.resolve();
|
|
556
|
+
constructor(opts) {
|
|
557
|
+
this.model = opts?.model;
|
|
558
|
+
this.reasoning = opts?.reasoning;
|
|
559
|
+
this.authConfig = opts?.authConfig;
|
|
560
|
+
this.abortSignal = opts?.abortSignal;
|
|
561
|
+
this.sessionId = opts?.sessionId;
|
|
562
|
+
this.languageModelMiddleware = opts?.languageModelMiddleware;
|
|
563
|
+
this.usageRecorder = opts?.usageRecorder;
|
|
564
|
+
}
|
|
565
|
+
get size() {
|
|
566
|
+
return this.findings.length;
|
|
567
|
+
}
|
|
568
|
+
getFindings() {
|
|
569
|
+
return [...this.findings];
|
|
570
|
+
}
|
|
571
|
+
static fromDirectory(findingsPath, opts) {
|
|
572
|
+
const registry = new FindingsRegistry(opts);
|
|
573
|
+
if (!existsSync2(findingsPath))
|
|
574
|
+
return registry;
|
|
575
|
+
const files = readdirSync2(findingsPath).filter((f) => f.endsWith(".json"));
|
|
576
|
+
log.debug(`fromDirectory: path=${findingsPath}, jsonFiles=${files.length}`);
|
|
577
|
+
for (const file of files) {
|
|
578
|
+
try {
|
|
579
|
+
const raw = readFileSync2(join2(findingsPath, file), "utf-8");
|
|
580
|
+
const finding = JSON.parse(raw);
|
|
581
|
+
if (finding && typeof finding.title === "string" && typeof finding.endpoint === "string") {
|
|
582
|
+
registry.indexFinding(finding);
|
|
583
|
+
log.debug(`fromDirectory: indexed "${finding.title}" endpoint="${finding.endpoint}" (file=${file})`);
|
|
584
|
+
} else {
|
|
585
|
+
log.debug(`fromDirectory: skipped (missing title/endpoint) file=${file}`);
|
|
586
|
+
}
|
|
587
|
+
} catch {
|
|
588
|
+
log.debug(`fromDirectory: skipped (malformed) file=${file}`);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
log.debug(`fromDirectory: registry initialized with ${registry.size} findings indexed`);
|
|
592
|
+
return registry;
|
|
593
|
+
}
|
|
594
|
+
static fromFindings(findings, opts) {
|
|
595
|
+
const registry = new FindingsRegistry(opts);
|
|
596
|
+
for (const f of findings) {
|
|
597
|
+
registry.indexFinding(f);
|
|
598
|
+
}
|
|
599
|
+
return registry;
|
|
600
|
+
}
|
|
601
|
+
isDuplicate(finding) {
|
|
602
|
+
const { exactKey, appWideKey } = generateFingerprint(finding);
|
|
603
|
+
const exactMatch = this.exactKeys.get(exactKey);
|
|
604
|
+
if (exactMatch) {
|
|
605
|
+
return {
|
|
606
|
+
duplicate: true,
|
|
607
|
+
matchedFinding: exactMatch,
|
|
608
|
+
matchType: "exact"
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
const appWideMatch = this.appWideKeys.get(appWideKey);
|
|
612
|
+
if (appWideMatch) {
|
|
613
|
+
return {
|
|
614
|
+
duplicate: true,
|
|
615
|
+
matchedFinding: appWideMatch,
|
|
616
|
+
matchType: "application-wide"
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
return { duplicate: false };
|
|
620
|
+
}
|
|
621
|
+
async register(finding) {
|
|
622
|
+
log.debug(`register: checking "${finding.title}" endpoint="${finding.endpoint}"`);
|
|
623
|
+
const fastResult = await new Promise((resolve) => {
|
|
624
|
+
this.mutex = this.mutex.then(() => {
|
|
625
|
+
const check = this.isDuplicate(finding);
|
|
626
|
+
if (check.duplicate) {
|
|
627
|
+
log.debug(`register: DUPLICATE (${check.matchType}) "${finding.title}" matched="${check.matchedFinding?.title}"`);
|
|
628
|
+
resolve(check);
|
|
629
|
+
} else if (!this.model || this.findings.length === 0) {
|
|
630
|
+
this.indexFinding(finding);
|
|
631
|
+
log.debug(`register: UNIQUE (Tier 1+2, no LLM needed) "${finding.title}" — registry size=${this.size}`);
|
|
632
|
+
resolve({ duplicate: false });
|
|
633
|
+
} else {
|
|
634
|
+
log.debug(`register: Tier 1+2 pass — proceeding to Tier 3 LLM check for "${finding.title}"`);
|
|
635
|
+
resolve(null);
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
});
|
|
639
|
+
if (fastResult !== null)
|
|
640
|
+
return fastResult;
|
|
641
|
+
const snapshot = this.getFindings();
|
|
642
|
+
let semanticResult = { duplicate: false };
|
|
643
|
+
try {
|
|
644
|
+
semanticResult = await this.semanticDedup(finding, snapshot);
|
|
645
|
+
} catch {
|
|
646
|
+
log.warn(`register: Tier 3 LLM error for "${finding.title}" — falling back to Tier 1+2 only`);
|
|
647
|
+
}
|
|
648
|
+
if (semanticResult.duplicate) {
|
|
649
|
+
log.debug(`register: DUPLICATE (semantic/Tier 3) "${finding.title}" matched="${semanticResult.matchedFinding?.title}"`);
|
|
650
|
+
return semanticResult;
|
|
651
|
+
}
|
|
652
|
+
return new Promise((resolve) => {
|
|
653
|
+
this.mutex = this.mutex.then(() => {
|
|
654
|
+
const recheck = this.isDuplicate(finding);
|
|
655
|
+
if (recheck.duplicate) {
|
|
656
|
+
log.debug(`register: DUPLICATE (race re-check, ${recheck.matchType}) "${finding.title}" matched="${recheck.matchedFinding?.title}"`);
|
|
657
|
+
resolve(recheck);
|
|
658
|
+
} else {
|
|
659
|
+
this.indexFinding(finding);
|
|
660
|
+
log.debug(`register: UNIQUE (all tiers passed) "${finding.title}" — registry size=${this.size}`);
|
|
661
|
+
resolve({ duplicate: false });
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
async semanticDedup(finding, existingFindings) {
|
|
667
|
+
if (!this.model || existingFindings.length === 0) {
|
|
668
|
+
return { duplicate: false };
|
|
669
|
+
}
|
|
670
|
+
const prompt = buildSemanticDedupPrompt(finding, existingFindings);
|
|
671
|
+
const result = await generateObjectResponse({
|
|
672
|
+
model: this.model,
|
|
673
|
+
reasoning: this.reasoning,
|
|
674
|
+
schema: SemanticDedupResultSchema,
|
|
675
|
+
prompt,
|
|
676
|
+
system: SEMANTIC_DEDUP_SYSTEM,
|
|
677
|
+
authConfig: this.authConfig,
|
|
678
|
+
abortSignal: this.abortSignal,
|
|
679
|
+
sessionId: this.sessionId,
|
|
680
|
+
languageModelMiddleware: this.languageModelMiddleware,
|
|
681
|
+
usageRecorder: this.usageRecorder,
|
|
682
|
+
operation: "apex.finding.deduplicate"
|
|
683
|
+
});
|
|
684
|
+
if (result.isDuplicate && result.matchedIndex != null) {
|
|
685
|
+
const idx = result.matchedIndex - 1;
|
|
686
|
+
const matched = existingFindings[idx];
|
|
687
|
+
if (matched) {
|
|
688
|
+
return {
|
|
689
|
+
duplicate: true,
|
|
690
|
+
matchedFinding: matched,
|
|
691
|
+
matchType: "semantic"
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return { duplicate: false };
|
|
696
|
+
}
|
|
697
|
+
async unregister(finding) {
|
|
698
|
+
return new Promise((resolve) => {
|
|
699
|
+
this.mutex = this.mutex.then(() => {
|
|
700
|
+
this.removeFinding(finding);
|
|
701
|
+
resolve();
|
|
702
|
+
});
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
async groupByRootCause() {
|
|
706
|
+
if (!this.model || this.findings.length < 2) {
|
|
707
|
+
return [];
|
|
708
|
+
}
|
|
709
|
+
const snapshot = [...this.findings];
|
|
710
|
+
const prompt = buildRootCauseGroupingPrompt(snapshot);
|
|
711
|
+
let result;
|
|
712
|
+
try {
|
|
713
|
+
result = await generateObjectResponse({
|
|
714
|
+
model: this.model,
|
|
715
|
+
reasoning: this.reasoning,
|
|
716
|
+
schema: RootCauseGroupResultSchema,
|
|
717
|
+
prompt,
|
|
718
|
+
system: ROOT_CAUSE_GROUPING_SYSTEM,
|
|
719
|
+
authConfig: this.authConfig,
|
|
720
|
+
abortSignal: this.abortSignal,
|
|
721
|
+
sessionId: this.sessionId,
|
|
722
|
+
languageModelMiddleware: this.languageModelMiddleware,
|
|
723
|
+
usageRecorder: this.usageRecorder,
|
|
724
|
+
operation: "apex.finding.root-cause"
|
|
725
|
+
});
|
|
726
|
+
} catch {
|
|
727
|
+
log.warn("groupByRootCause: LLM error — skipping root-cause grouping");
|
|
728
|
+
return [];
|
|
729
|
+
}
|
|
730
|
+
const sanitised = [];
|
|
731
|
+
await new Promise((resolve) => {
|
|
732
|
+
this.mutex = this.mutex.then(() => {
|
|
733
|
+
const baseSeverities = snapshot.map((f) => f.severity);
|
|
734
|
+
for (const group of result.groups) {
|
|
735
|
+
const validIndices = group.findingIndices.filter((i) => i >= 0 && i < snapshot.length);
|
|
736
|
+
if (validIndices.length < 2)
|
|
737
|
+
continue;
|
|
738
|
+
const normalizedSeverity = maxSeverity(validIndices.map((i) => baseSeverities[i]));
|
|
739
|
+
const leadFindingIndex = validIndices.reduce((best, i) => {
|
|
740
|
+
const aRank = SEVERITY_RANK[baseSeverities[i]];
|
|
741
|
+
const bRank = SEVERITY_RANK[baseSeverities[best]];
|
|
742
|
+
if (aRank !== bRank) {
|
|
743
|
+
return aRank > bRank ? i : best;
|
|
744
|
+
}
|
|
745
|
+
return (snapshot[i].description?.length ?? 0) > (snapshot[best].description?.length ?? 0) ? i : best;
|
|
746
|
+
}, validIndices[0]);
|
|
747
|
+
sanitised.push({
|
|
748
|
+
groupId: group.groupId,
|
|
749
|
+
findingIndices: validIndices,
|
|
750
|
+
rootCause: group.rootCause,
|
|
751
|
+
normalizedSeverity,
|
|
752
|
+
leadFindingIndex
|
|
753
|
+
});
|
|
754
|
+
for (const idx of validIndices) {
|
|
755
|
+
const finding = snapshot[idx];
|
|
756
|
+
finding.rootCauseGroup = group.groupId;
|
|
757
|
+
finding.severity = normalizedSeverity;
|
|
758
|
+
finding.rootCauseLead = idx === leadFindingIndex;
|
|
759
|
+
finding.relatedFindings = validIndices.filter((i) => i !== idx).map((i) => snapshot[i]?.title);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
resolve();
|
|
763
|
+
});
|
|
764
|
+
});
|
|
765
|
+
return sanitised;
|
|
766
|
+
}
|
|
767
|
+
removeFinding(finding) {
|
|
768
|
+
const { exactKey, appWideKey } = generateFingerprint(finding);
|
|
769
|
+
if (this.exactKeys.get(exactKey) === finding) {
|
|
770
|
+
this.exactKeys.delete(exactKey);
|
|
771
|
+
}
|
|
772
|
+
if (this.appWideKeys.get(appWideKey) === finding) {
|
|
773
|
+
this.appWideKeys.delete(appWideKey);
|
|
774
|
+
}
|
|
775
|
+
const idx = this.findings.indexOf(finding);
|
|
776
|
+
if (idx !== -1) {
|
|
777
|
+
this.findings.splice(idx, 1);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
indexFinding(finding) {
|
|
781
|
+
const { exactKey, appWideKey } = generateFingerprint(finding);
|
|
782
|
+
if (!this.exactKeys.has(exactKey)) {
|
|
783
|
+
this.exactKeys.set(exactKey, finding);
|
|
784
|
+
}
|
|
785
|
+
if (!this.appWideKeys.has(appWideKey)) {
|
|
786
|
+
this.appWideKeys.set(appWideKey, finding);
|
|
787
|
+
}
|
|
788
|
+
this.findings.push(finding);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// src/core/workflows/seams.ts
|
|
793
|
+
init_id();
|
|
794
|
+
function inProcessSpawnMany(items, worker, opts) {
|
|
795
|
+
return runWithBoundedConcurrency(items, opts.concurrency, worker, opts.abortSignal);
|
|
796
|
+
}
|
|
797
|
+
var inProcessConcurrencyRunner = {
|
|
798
|
+
spawnMany: inProcessSpawnMany
|
|
799
|
+
};
|
|
800
|
+
var inMemoryRegistryProvider = {
|
|
801
|
+
findings: () => new FindingsRegistry,
|
|
802
|
+
attackSurface: () => new AttackSurfaceRegistry
|
|
803
|
+
};
|
|
804
|
+
var randomSessionIdFactory = {
|
|
805
|
+
newSessionId: () => newSessionId()
|
|
806
|
+
};
|
|
807
|
+
var noBrowserSessionProvider = {
|
|
808
|
+
forChild: () => {
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
};
|
|
812
|
+
var noopOrchestrationHooks = {};
|
|
813
|
+
function withFindingPersistedHook(registry, onFindingPersisted) {
|
|
814
|
+
if (!onFindingPersisted)
|
|
815
|
+
return registry;
|
|
816
|
+
return {
|
|
817
|
+
get size() {
|
|
818
|
+
return registry.size;
|
|
819
|
+
},
|
|
820
|
+
getFindings: () => registry.getFindings(),
|
|
821
|
+
isDuplicate: (finding) => registry.isDuplicate(finding),
|
|
822
|
+
groupByRootCause: () => registry.groupByRootCause(),
|
|
823
|
+
unregister: (finding) => registry.unregister(finding),
|
|
824
|
+
async register(finding) {
|
|
825
|
+
const result = await registry.register(finding);
|
|
826
|
+
if (!result.duplicate)
|
|
827
|
+
await onFindingPersisted(finding);
|
|
828
|
+
return result;
|
|
829
|
+
}
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
var DEFAULT_MAX_DEPTH = 3;
|
|
833
|
+
var DEFAULT_MAX_CONCURRENT_CHILDREN = 10;
|
|
834
|
+
var defaultLimits = {
|
|
835
|
+
maxDepth: DEFAULT_MAX_DEPTH,
|
|
836
|
+
maxConcurrentChildren: DEFAULT_MAX_CONCURRENT_CHILDREN
|
|
837
|
+
};
|
|
838
|
+
|
|
839
|
+
class WorkflowLimitExceededError extends Error {
|
|
840
|
+
constructor(message) {
|
|
841
|
+
super(message);
|
|
842
|
+
this.name = "WorkflowLimitExceededError";
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
function assertDepth(depth, limits) {
|
|
846
|
+
if (depth > limits.maxDepth) {
|
|
847
|
+
throw new WorkflowLimitExceededError(`workflow depth ${depth} exceeds maxDepth ${limits.maxDepth}`);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
function withConcurrencyLimit(runner, limits) {
|
|
851
|
+
return {
|
|
852
|
+
async spawnMany(items, worker, opts) {
|
|
853
|
+
if (opts.concurrency > limits.maxConcurrentChildren) {
|
|
854
|
+
throw new WorkflowLimitExceededError(`concurrency ${opts.concurrency} exceeds maxConcurrentChildren ${limits.maxConcurrentChildren}`);
|
|
855
|
+
}
|
|
856
|
+
return runner.spawnMany(items, worker, opts);
|
|
857
|
+
}
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
function inProcessSeams(overrides) {
|
|
861
|
+
const limits = overrides?.limits ? { ...defaultLimits, ...overrides.limits } : defaultLimits;
|
|
862
|
+
return {
|
|
863
|
+
fanOut: withConcurrencyLimit(overrides?.fanOut ?? inProcessConcurrencyRunner, limits),
|
|
864
|
+
registries: overrides?.registries ?? inMemoryRegistryProvider,
|
|
865
|
+
ids: overrides?.ids ?? randomSessionIdFactory,
|
|
866
|
+
browser: overrides?.browser ?? noBrowserSessionProvider,
|
|
867
|
+
hooks: overrides?.hooks ?? noopOrchestrationHooks,
|
|
868
|
+
limits,
|
|
869
|
+
hooksForItem: overrides?.hooksForItem
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
function resolveItemHooks(hooks, seams, item, index) {
|
|
873
|
+
const itemHooks = seams.hooksForItem?.(item, index);
|
|
874
|
+
return {
|
|
875
|
+
...hooks,
|
|
876
|
+
contextRecorder: undefined,
|
|
877
|
+
inferenceRecorder: undefined,
|
|
878
|
+
toolExecutionRecorder: undefined,
|
|
879
|
+
...itemHooks,
|
|
880
|
+
...hooks?.findingPolicy === "external" ? { findingPolicy: "external" } : {}
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// src/core/agents/offSecAgent/subagentSpawner.ts
|
|
885
|
+
async function spawnBatch2(spawner, requests, opts, limiter) {
|
|
886
|
+
if (spawner.spawnBatch) {
|
|
887
|
+
if (limiter && !spawner.supportsBatchLimiter) {
|
|
888
|
+
throw new Error("Batch spawner must support the shared child limiter before scheduling limited batches");
|
|
889
|
+
}
|
|
890
|
+
return spawner.spawnBatch(requests, { ...opts, limiter });
|
|
891
|
+
}
|
|
892
|
+
const results = await spawner.spawnMany(requests, async (request) => {
|
|
893
|
+
try {
|
|
894
|
+
const run = () => spawner.spawn(request);
|
|
895
|
+
return {
|
|
896
|
+
status: "fulfilled",
|
|
897
|
+
value: await (limiter ? limiter(run) : run())
|
|
898
|
+
};
|
|
899
|
+
} catch (reason) {
|
|
900
|
+
return { status: "rejected", reason };
|
|
901
|
+
}
|
|
902
|
+
}, opts);
|
|
903
|
+
return results.map((result) => {
|
|
904
|
+
if (result === null) {
|
|
905
|
+
opts.abortSignal?.throwIfAborted();
|
|
906
|
+
throw new Error("Subagent batch lost a result");
|
|
907
|
+
}
|
|
908
|
+
return result;
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
async function runSpawnedPentestWorker(spec, input) {
|
|
912
|
+
const { TargetedPentestAgent } = await import("./agent-p8cezgbb.js");
|
|
913
|
+
const hooks = resolveItemHooks(input.hooks, input.seams, spec, 0);
|
|
914
|
+
const agent = new TargetedPentestAgent({
|
|
915
|
+
target: spec.target,
|
|
916
|
+
grpc: spec.grpc,
|
|
917
|
+
systemScope: spec.systemScope,
|
|
918
|
+
objectives: spec.objectives,
|
|
919
|
+
context: spec.context,
|
|
920
|
+
role: spec.role,
|
|
921
|
+
findingsRegistry: input.findingsRegistry,
|
|
922
|
+
browserSession: input.browserSession,
|
|
923
|
+
...getModelSettings(input),
|
|
924
|
+
session: input.session,
|
|
925
|
+
onStepFinish: input.onStepFinish,
|
|
926
|
+
onCacheMetrics: input.onCacheMetrics,
|
|
927
|
+
eventBus: input.eventBus,
|
|
928
|
+
subagentId: input.subagentId,
|
|
929
|
+
subagentName: input.subagentName,
|
|
930
|
+
environmentVariables: input.environmentVariables,
|
|
931
|
+
secretValues: input.secretValues,
|
|
932
|
+
display: input.display,
|
|
933
|
+
...hooks,
|
|
934
|
+
abortSignal: input.abortSignal && hooks.abortSignal ? AbortSignal.any([input.abortSignal, hooks.abortSignal]) : input.abortSignal ?? hooks.abortSignal
|
|
935
|
+
});
|
|
936
|
+
input.onAgentConstructed?.(agent);
|
|
937
|
+
return agent.consume();
|
|
938
|
+
}
|
|
939
|
+
var runPentestChild = async (spec, ctx) => {
|
|
940
|
+
let constructed;
|
|
941
|
+
const workerSpec = {
|
|
942
|
+
target: spec.target,
|
|
943
|
+
grpc: spec.grpc,
|
|
944
|
+
systemScope: spec.systemScope,
|
|
945
|
+
objectives: spec.objectives,
|
|
946
|
+
context: spec.context,
|
|
947
|
+
role: spec.role
|
|
948
|
+
};
|
|
949
|
+
const workerInput = {
|
|
950
|
+
...getModelSettings(ctx),
|
|
951
|
+
session: ctx.session,
|
|
952
|
+
abortSignal: ctx.abortSignal,
|
|
953
|
+
onStepFinish: ctx.onStepFinish,
|
|
954
|
+
onCacheMetrics: ctx.onCacheMetrics,
|
|
955
|
+
eventBus: ctx.eventBus,
|
|
956
|
+
subagentId: ctx.subagentId,
|
|
957
|
+
subagentName: ctx.subagentName,
|
|
958
|
+
findingsRegistry: spec.findingsRegistry,
|
|
959
|
+
browserSession: spec.browserSession,
|
|
960
|
+
environmentVariables: ctx.environmentVariables,
|
|
961
|
+
secretValues: ctx.secretValues,
|
|
962
|
+
display: ctx.display,
|
|
963
|
+
hooks: {
|
|
964
|
+
findingPolicy: ctx.findingPolicy,
|
|
965
|
+
backends: ctx.backends,
|
|
966
|
+
sandbox: ctx.sandbox,
|
|
967
|
+
languageModelMiddleware: ctx.languageModelMiddleware,
|
|
968
|
+
usageRecorder: ctx.usageRecorder,
|
|
969
|
+
toolExecutionContext: ctx.toolExecutionContext,
|
|
970
|
+
streamIdFactory: ctx.streamIdFactory
|
|
971
|
+
},
|
|
972
|
+
seams: inProcessSeams(),
|
|
973
|
+
onAgentConstructed: (agent) => {
|
|
974
|
+
constructed = agent;
|
|
975
|
+
}
|
|
976
|
+
};
|
|
977
|
+
return {
|
|
978
|
+
run: () => runSpawnedPentestWorker(workerSpec, workerInput),
|
|
979
|
+
drained: () => constructed?.drained ?? Promise.resolve()
|
|
980
|
+
};
|
|
981
|
+
};
|
|
982
|
+
var runCodeChild = async (spec, ctx) => {
|
|
983
|
+
const { CodeAgent } = await import("./agent-vvdqga09.js");
|
|
984
|
+
const agent = new CodeAgent({
|
|
985
|
+
findingPolicy: ctx.findingPolicy,
|
|
986
|
+
codebasePath: spec.codebasePath,
|
|
987
|
+
objective: spec.objective,
|
|
988
|
+
system: spec.system,
|
|
989
|
+
responseSchema: spec.responseSchema,
|
|
990
|
+
stopWhen: spec.stopWhen,
|
|
991
|
+
excludeTools: spec.excludeTools,
|
|
992
|
+
projectThreatModel: spec.projectThreatModel,
|
|
993
|
+
attackSurfaceRegistry: spec.attackSurfaceRegistry,
|
|
994
|
+
...getModelSettings(ctx),
|
|
995
|
+
session: ctx.session,
|
|
996
|
+
abortSignal: ctx.abortSignal,
|
|
997
|
+
eventBus: ctx.eventBus,
|
|
998
|
+
subagentId: ctx.subagentId,
|
|
999
|
+
subagentName: ctx.subagentName,
|
|
1000
|
+
sandbox: ctx.sandbox,
|
|
1001
|
+
backends: ctx.backends,
|
|
1002
|
+
display: ctx.display,
|
|
1003
|
+
onStepFinish: ctx.onStepFinish,
|
|
1004
|
+
onCacheMetrics: ctx.onCacheMetrics,
|
|
1005
|
+
languageModelMiddleware: ctx.languageModelMiddleware,
|
|
1006
|
+
usageRecorder: ctx.usageRecorder,
|
|
1007
|
+
toolExecutionContext: ctx.toolExecutionContext,
|
|
1008
|
+
streamIdFactory: ctx.streamIdFactory
|
|
1009
|
+
});
|
|
1010
|
+
return { run: () => agent.consume(), drained: () => agent.drained };
|
|
1011
|
+
};
|
|
1012
|
+
var runWhiteboxChild = async (spec, ctx) => {
|
|
1013
|
+
const { WhiteboxAttackSurfaceAgent } = await import("./index-3t41p258.js");
|
|
1014
|
+
const agent = new WhiteboxAttackSurfaceAgent({
|
|
1015
|
+
findingPolicy: ctx.findingPolicy,
|
|
1016
|
+
codebasePath: spec.codebasePath,
|
|
1017
|
+
attackSurfaceRegistry: spec.attackSurfaceRegistry,
|
|
1018
|
+
...getModelSettings(ctx),
|
|
1019
|
+
session: ctx.session,
|
|
1020
|
+
abortSignal: ctx.abortSignal,
|
|
1021
|
+
eventBus: ctx.eventBus,
|
|
1022
|
+
subagentId: ctx.subagentId,
|
|
1023
|
+
subagentName: ctx.subagentName,
|
|
1024
|
+
sandbox: ctx.sandbox,
|
|
1025
|
+
backends: ctx.backends,
|
|
1026
|
+
languageModelMiddleware: ctx.languageModelMiddleware,
|
|
1027
|
+
usageRecorder: ctx.usageRecorder,
|
|
1028
|
+
toolExecutionContext: ctx.toolExecutionContext,
|
|
1029
|
+
streamIdFactory: ctx.streamIdFactory
|
|
1030
|
+
});
|
|
1031
|
+
return { run: () => agent.consume(), drained: () => agent.drained };
|
|
1032
|
+
};
|
|
1033
|
+
var runBlackboxChild = async (spec, ctx) => {
|
|
1034
|
+
const { BlackboxAttackSurfaceAgent } = await import("./blackboxAgent-ct8bejef.js");
|
|
1035
|
+
const agent = new BlackboxAttackSurfaceAgent({
|
|
1036
|
+
findingPolicy: ctx.findingPolicy,
|
|
1037
|
+
target: spec.target,
|
|
1038
|
+
attackSurfaceRegistry: spec.attackSurfaceRegistry,
|
|
1039
|
+
...getModelSettings(ctx),
|
|
1040
|
+
session: ctx.session,
|
|
1041
|
+
abortSignal: ctx.abortSignal,
|
|
1042
|
+
eventBus: ctx.eventBus,
|
|
1043
|
+
subagentId: ctx.subagentId,
|
|
1044
|
+
sandbox: ctx.sandbox,
|
|
1045
|
+
backends: ctx.backends,
|
|
1046
|
+
languageModelMiddleware: ctx.languageModelMiddleware,
|
|
1047
|
+
usageRecorder: ctx.usageRecorder,
|
|
1048
|
+
toolExecutionContext: ctx.toolExecutionContext,
|
|
1049
|
+
streamIdFactory: ctx.streamIdFactory
|
|
1050
|
+
});
|
|
1051
|
+
return { run: () => agent.consume(), drained: () => agent.drained };
|
|
1052
|
+
};
|
|
1053
|
+
var runAuthChild = async (spec, ctx) => {
|
|
1054
|
+
const { runAuthenticationAgent } = await import("./agent-vaq31je6.js");
|
|
1055
|
+
return {
|
|
1056
|
+
run: () => runAuthenticationAgent({
|
|
1057
|
+
findingPolicy: ctx.findingPolicy,
|
|
1058
|
+
target: spec.target,
|
|
1059
|
+
authHints: spec.authHints,
|
|
1060
|
+
session: ctx.session,
|
|
1061
|
+
...getModelSettings(ctx),
|
|
1062
|
+
abortSignal: ctx.abortSignal,
|
|
1063
|
+
eventBus: ctx.eventBus,
|
|
1064
|
+
subagentId: ctx.subagentId,
|
|
1065
|
+
subagentName: ctx.subagentName,
|
|
1066
|
+
environmentVariables: ctx.environmentVariables,
|
|
1067
|
+
secretValues: ctx.secretValues,
|
|
1068
|
+
backends: ctx.backends,
|
|
1069
|
+
languageModelMiddleware: ctx.languageModelMiddleware,
|
|
1070
|
+
usageRecorder: ctx.usageRecorder,
|
|
1071
|
+
toolExecutionContext: ctx.toolExecutionContext,
|
|
1072
|
+
streamIdFactory: ctx.streamIdFactory
|
|
1073
|
+
})
|
|
1074
|
+
};
|
|
1075
|
+
};
|
|
1076
|
+
var runJudgeChild = async (spec, ctx) => {
|
|
1077
|
+
const { judgeFinding } = await import("./index-np405b9e.js");
|
|
1078
|
+
return {
|
|
1079
|
+
run: () => judgeFinding(spec.judgeInput, {
|
|
1080
|
+
waitForDrain: spec.waitForDrain,
|
|
1081
|
+
...getModelSettings(ctx),
|
|
1082
|
+
session: ctx.session,
|
|
1083
|
+
abortSignal: ctx.abortSignal,
|
|
1084
|
+
eventBus: ctx.eventBus,
|
|
1085
|
+
subagentId: ctx.subagentId,
|
|
1086
|
+
subagentName: ctx.subagentName,
|
|
1087
|
+
sandbox: ctx.sandbox,
|
|
1088
|
+
backends: ctx.backends,
|
|
1089
|
+
target: spec.target,
|
|
1090
|
+
languageModelMiddleware: ctx.languageModelMiddleware,
|
|
1091
|
+
usageRecorder: ctx.usageRecorder,
|
|
1092
|
+
toolExecutionContext: ctx.toolExecutionContext,
|
|
1093
|
+
streamIdFactory: ctx.streamIdFactory
|
|
1094
|
+
})
|
|
1095
|
+
};
|
|
1096
|
+
};
|
|
1097
|
+
var RUNNERS = {
|
|
1098
|
+
pentest: runPentestChild,
|
|
1099
|
+
code: runCodeChild,
|
|
1100
|
+
"whitebox-attack-surface": runWhiteboxChild,
|
|
1101
|
+
"blackbox-attack-surface": runBlackboxChild,
|
|
1102
|
+
authentication: runAuthChild,
|
|
1103
|
+
"finding-judge": runJudgeChild
|
|
1104
|
+
};
|
|
1105
|
+
function drainBounded(drained, graceMs) {
|
|
1106
|
+
return Promise.race([
|
|
1107
|
+
drained?.catch(() => {}) ?? Promise.resolve(),
|
|
1108
|
+
new Promise((resolve) => {
|
|
1109
|
+
setTimeout(resolve, graceMs).unref?.();
|
|
1110
|
+
})
|
|
1111
|
+
]);
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
class InProcessSubagentSpawner {
|
|
1115
|
+
ids;
|
|
1116
|
+
browser;
|
|
1117
|
+
ordinal = 0;
|
|
1118
|
+
constructor(ids = randomSessionIdFactory, browser) {
|
|
1119
|
+
this.ids = ids;
|
|
1120
|
+
this.browser = browser;
|
|
1121
|
+
}
|
|
1122
|
+
mintChildId(name) {
|
|
1123
|
+
return this.ids.newSessionId(name ?? "subagent", this.ordinal++);
|
|
1124
|
+
}
|
|
1125
|
+
async spawn(opts) {
|
|
1126
|
+
const childId = opts.subagentId ?? this.mintChildId(opts.subagentName);
|
|
1127
|
+
opts.onSpawned?.(childId);
|
|
1128
|
+
const parentSessionId = opts.parentSessionId ?? opts.parentSubagentId ?? opts.runtime.session.id;
|
|
1129
|
+
const lifecycleBase = {
|
|
1130
|
+
subagentId: childId,
|
|
1131
|
+
...opts.stampChildSessionId ? { sessionId: childId } : {},
|
|
1132
|
+
...opts.parentSubagentId !== undefined ? { parentSubagentId: opts.parentSubagentId } : {},
|
|
1133
|
+
...parentSessionId !== undefined ? { parentSessionId } : {}
|
|
1134
|
+
};
|
|
1135
|
+
opts.parentBus?.emit("subagent-spawn", {
|
|
1136
|
+
...lifecycleBase,
|
|
1137
|
+
name: opts.subagentName,
|
|
1138
|
+
input: opts.lifecycleInput
|
|
1139
|
+
});
|
|
1140
|
+
let handle;
|
|
1141
|
+
try {
|
|
1142
|
+
const childBus = new AgentEventBus;
|
|
1143
|
+
AgentEventBus.attachChild(childBus, opts.parentBus, childId);
|
|
1144
|
+
const spec = opts.spec.type === "pentest" && !opts.spec.browserSession && this.browser ? {
|
|
1145
|
+
...opts.spec,
|
|
1146
|
+
browserSession: this.browser.forChild({
|
|
1147
|
+
subagentId: childId,
|
|
1148
|
+
subagentName: opts.subagentName
|
|
1149
|
+
})
|
|
1150
|
+
} : opts.spec;
|
|
1151
|
+
const runner = RUNNERS[spec.type];
|
|
1152
|
+
handle = await runner(spec, {
|
|
1153
|
+
...opts.runtime,
|
|
1154
|
+
eventBus: childBus,
|
|
1155
|
+
subagentId: childId,
|
|
1156
|
+
subagentName: opts.subagentName
|
|
1157
|
+
});
|
|
1158
|
+
opts.beforeConsume?.(childBus);
|
|
1159
|
+
const result = await (opts.parentToolCallId && parentSessionId ? runWithNativeRolloutSession({
|
|
1160
|
+
sessionId: childId,
|
|
1161
|
+
parentSessionId,
|
|
1162
|
+
parentToolCallId: opts.parentToolCallId
|
|
1163
|
+
}, handle.run) : handle.run());
|
|
1164
|
+
await opts.onConsumed?.(result);
|
|
1165
|
+
if (opts.drainGraceMs != null) {
|
|
1166
|
+
await drainBounded(handle.drained?.(), opts.drainGraceMs);
|
|
1167
|
+
}
|
|
1168
|
+
opts.parentBus?.emit("subagent-complete", {
|
|
1169
|
+
...lifecycleBase,
|
|
1170
|
+
status: opts.resolveStatus?.(result) ?? "completed"
|
|
1171
|
+
});
|
|
1172
|
+
return result;
|
|
1173
|
+
} catch (error) {
|
|
1174
|
+
try {
|
|
1175
|
+
await opts.onError?.(error);
|
|
1176
|
+
if (opts.drainGraceMs != null) {
|
|
1177
|
+
await drainBounded(handle?.drained?.(), opts.drainGraceMs);
|
|
1178
|
+
}
|
|
1179
|
+
} finally {
|
|
1180
|
+
opts.parentBus?.emit("subagent-complete", {
|
|
1181
|
+
...lifecycleBase,
|
|
1182
|
+
status: "failed"
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
throw error;
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
spawnMany(items, worker, opts) {
|
|
1189
|
+
return runWithBoundedConcurrency(items, opts.concurrency, worker, opts.abortSignal);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
var inProcessSubagentSpawner2 = new InProcessSubagentSpawner;
|
|
1193
|
+
export { AgentEventBus, FindingsRegistry, inMemoryRegistryProvider, withFindingPersistedHook, assertDepth, inProcessSeams, resolveItemHooks, spawnBatch2, inProcessSubagentSpawner2 };
|