@pensar/apex 2.1.3-canary.fff17bfd → 2.2.0

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 (58) hide show
  1. package/build/agent-3cdfwxcd.js +19 -0
  2. package/build/agent-q5hwjdtb.js +27 -0
  3. package/build/{agent-c06jb5zv.js → agent-yayy5x2a.js} +10 -8
  4. package/build/{apps-mxhfapvz.js → apps-0es69e2s.js} +17 -16
  5. package/build/{auth-9ysskv2q.js → auth-q73k9bh9.js} +17 -16
  6. package/build/authentication-tqx2brcv.js +19 -0
  7. package/build/blackboxAgent-d0smgqt7.js +19 -0
  8. package/build/{blackboxPentest-pfs3d1nx.js → blackboxPentest-h28nykk5.js} +15 -14
  9. package/build/{cli-90dw3gm4.js → cli-1cf5vy5m.js} +9 -6
  10. package/build/{cli-f8tgnegf.js → cli-1f636vzd.js} +2 -2
  11. package/build/{cli-tg7mcs43.js → cli-1re2kv5v.js} +5 -3
  12. package/build/cli-2jn8104w.js +195 -0
  13. package/build/{cli-29yqhrjq.js → cli-3k1x5rn3.js} +1 -1
  14. package/build/{cli-k941a7ft.js → cli-4tc6888c.js} +1 -1
  15. package/build/{cli-qb3eddc7.js → cli-51dkmt5p.js} +1 -1
  16. package/build/{cli-v34r5t8t.js → cli-6kx46ck0.js} +6186 -513
  17. package/build/{cli-a1acedxf.js → cli-7e5ad5js.js} +15 -6
  18. package/build/{cli-8qb62k90.js → cli-7n43wz82.js} +1 -1
  19. package/build/{cli-fj04wady.js → cli-knqx15w6.js} +5 -2
  20. package/build/{cli-9wd7jy2s.js → cli-n114n8mk.js} +102 -21
  21. package/build/{cli-9h847y2w.js → cli-ptzy9ak5.js} +2 -2
  22. package/build/{cli-g6cjpbr9.js → cli-s43t5ay0.js} +1 -1
  23. package/build/{cli-nth9zebw.js → cli-tz9yg4ew.js} +1147 -336
  24. package/build/{cli-2cvp6x4p.js → cli-vgg6h2ax.js} +1 -1
  25. package/build/{cli-akhax3yn.js → cli-xbdn034b.js} +671 -105
  26. package/build/{cli-wkpsqyjc.js → cli-xpn9tsw0.js} +27 -5
  27. package/build/cli.js +48 -39
  28. package/build/{config-ky5za80x.js → config-xcf7rc6x.js} +3 -3
  29. package/build/{doctor-t94dqmcs.js → doctor-98zperf9.js} +7 -7
  30. package/build/fastStrike-9tm6bedh.js +156 -0
  31. package/build/{fixes-fv2zmk0m.js → fixes-1dry8j61.js} +17 -16
  32. package/build/{index-s2jr2ve2.js → index-0j880wqc.js} +70 -44
  33. package/build/{index-8mgv8hke.js → index-31rwrakh.js} +2 -2
  34. package/build/{index-b7zrdy9b.js → index-34h2nb6x.js} +3 -3
  35. package/build/{index-pmsx73c3.js → index-4hqbyzf5.js} +11 -9
  36. package/build/{index-qshyncep.js → index-6dbym5jy.js} +8 -7
  37. package/build/{index-pycryjc3.js → index-f6ttv7np.js} +4 -4
  38. package/build/{index-n1yh9e86.js → index-gq9h8j5j.js} +1 -1
  39. package/build/{index-rxskwf3k.js → index-pxwwcefn.js} +10 -10
  40. package/build/{issues-bwdnb233.js → issues-wjakj8yy.js} +28 -16
  41. package/build/{logs-2g3rsh9f.js → logs-ewyzcspy.js} +17 -16
  42. package/build/{offesecAgent-4ykghkvs.js → offesecAgent-7m9smy0t.js} +9 -9
  43. package/build/pentest-1cb5t0dt.js +29 -0
  44. package/build/{pentests-d9dt495f.js → pentests-1hcqjyk2.js} +17 -16
  45. package/build/targetedPentest-3x13kqvc.js +56 -0
  46. package/build/{targets-3n7wsm3g.js → targets-nqz8w37e.js} +17 -16
  47. package/build/threatModel-7f02av7y.js +27 -0
  48. package/build/{uninstall-jvjv6d2n.js → uninstall-p1q5pkx2.js} +1 -1
  49. package/build/{upload-x2b8s1xb.js → upload-x7etpfp4.js} +7 -7
  50. package/build/{utils-kx8jqmv0.js → utils-kw6yh5yn.js} +7 -7
  51. package/package.json +2 -2
  52. package/build/agent-7ab546zf.js +0 -19
  53. package/build/agent-dsqssedb.js +0 -25
  54. package/build/authentication-h1zxd74t.js +0 -19
  55. package/build/blackboxAgent-32zwxt6w.js +0 -19
  56. package/build/pentest-91bygv3h.js +0 -28
  57. package/build/targetedPentest-eavq5h7v.js +0 -43
  58. package/build/threatModel-2re9tykx.js +0 -26
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  detectOSAndEnhancePrompt
3
- } from "./cli-g6cjpbr9.js";
3
+ } from "./cli-s43t5ay0.js";
4
4
  import {
5
5
  parseTargetUrl
6
6
  } from "./cli-c8131c4q.js";
@@ -30,15 +30,16 @@ import {
30
30
  scopedLogger,
31
31
  stepCountIs,
32
32
  streamResponse,
33
+ withSubagentSessionBaggage,
33
34
  write
34
- } from "./cli-v34r5t8t.js";
35
+ } from "./cli-6kx46ck0.js";
35
36
  import {
36
37
  ensureValidToken,
37
38
  getPensarApiUrl,
38
39
  init_auth,
39
40
  init_constants,
40
41
  signGatewayRequest
41
- } from "./cli-k941a7ft.js";
42
+ } from "./cli-4tc6888c.js";
42
43
  import {
43
44
  _enum,
44
45
  _null,
@@ -65,11 +66,11 @@ import {
65
66
  tool,
66
67
  union,
67
68
  unknown
68
- } from "./cli-29yqhrjq.js";
69
+ } from "./cli-3k1x5rn3.js";
69
70
  import {
70
71
  config,
71
72
  init_config
72
- } from "./cli-qb3eddc7.js";
73
+ } from "./cli-51dkmt5p.js";
73
74
  import {
74
75
  __commonJS,
75
76
  __require,
@@ -99665,7 +99666,7 @@ class AgentEventBus {
99665
99666
  }
99666
99667
  emitStreamPart(chunk, ids) {
99667
99668
  const ctx = typeof ids === "string" ? { subagentId: ids } : ids ?? {};
99668
- const subagentId = ctx.subagentId ?? ctx.sessionId;
99669
+ const subagentId = ctx.subagentId;
99669
99670
  const sessionId = ctx.sessionId;
99670
99671
  const messageId = ctx.messageId;
99671
99672
  switch (chunk.type) {
@@ -99909,11 +99910,11 @@ You can perform the full lifecycle of a penetration test and support a wide rang
99909
99910
  You run inside Pensar Apex, and the \`pensar\` CLI is available on this machine. Invoke it through \`execute_command\` to perform Console/workspace operations your other tools don't cover — most importantly managing the **attack surface** and triaging results. These commands act on the workspace the user connected with \`pensar login\` and print JSON to stdout, so you can parse and chain them.
99910
99911
 
99911
99912
  - **Attack surface (apps & endpoints)** — \`pensar apps\` (list), \`apps get <appId>\`, \`apps create\`, \`apps update <appId>\`, \`apps delete <appId>\`; endpoints via \`apps endpoints <appId>\`, \`apps endpoint <endpointId>\`, \`apps endpoint-create <appId>\`, \`apps endpoint-update <endpointId>\`, \`apps endpoint-delete <endpointId>\`; and \`apps search <query>\` / \`apps search-endpoints <query>\`.
99912
- - **Issues & fixes** — \`pensar issues [--status --severity --scan --branch]\`, \`issues get <id>\`, \`issues update <id>\`; \`pensar fixes <issueId>\`, \`fixes get <fixId>\`.
99913
+ - **Issues & fixes** — \`pensar issues [--status --severity --scan --branch]\`, \`issues get <id>\`, \`issues update <id>\`, \`issues retest <id>\`; \`pensar fixes <issueId>\`, \`fixes get <fixId>\`.
99913
99914
  - **Scans** — \`pensar pentests\`, \`pentests get <id>\`, \`pentests dispatch [--branch --level]\`.
99914
99915
  - **Agent logs** — \`pensar logs <issueId>\`, \`logs search <issueId> <query>\`.
99915
99916
 
99916
- Run \`pensar --help\` or \`pensar <command> --help\` for exact flags — the CLI is the source of truth. These commands require the user to be logged in; if one reports it is not authenticated, tell the user to run \`pensar login\` rather than attempting the device flow yourself. Confirm with the user before any mutating command (\`create\`/\`update\`/\`delete\`, \`endpoint-*\`, \`pentests dispatch\`, \`issues update\`). Do NOT launch nested engagements from here (\`pensar pentest\`, \`pensar targeted-pentest\`, \`pensar operator\`, or \`pensar -p\`) — use your own tools for testing.
99917
+ Run \`pensar --help\` or \`pensar <command> --help\` for exact flags — the CLI is the source of truth. These commands require the user to be logged in; if one reports it is not authenticated, tell the user to run \`pensar login\` rather than attempting the device flow yourself. Confirm with the user before any mutating command (\`create\`/\`update\`/\`delete\`, \`endpoint-*\`, \`pentests dispatch\`, \`issues update\`, \`issues retest\`). Do NOT launch nested engagements from here (\`pensar pentest\`, \`pensar targeted-pentest\`, \`pensar operator\`, or \`pensar -p\`) — use your own tools for testing.
99917
99918
 
99918
99919
  # How to Work
99919
99920
 
@@ -102688,12 +102689,25 @@ var CAMOUFOX_OPTIONS = {
102688
102689
  humanize: true,
102689
102690
  block_webrtc: true
102690
102691
  };
102692
+ var MEMORY_FIREFOX_PREFS = {
102693
+ "fission.autostart": false,
102694
+ "dom.ipc.processCount": 1,
102695
+ "dom.ipc.processPrelaunch.enabled": false,
102696
+ "browser.cache.disk.enable": false,
102697
+ "browser.cache.memory.enable": false,
102698
+ "browser.sessionhistory.max_total_viewers": 0
102699
+ };
102691
102700
  async function resolveCamoufoxLaunchOptions(headless) {
102692
102701
  const { launchOptions: camoufoxLaunchOptions } = await import("camoufox-js");
102693
- return await camoufoxLaunchOptions({
102702
+ const opts = await camoufoxLaunchOptions({
102694
102703
  ...CAMOUFOX_OPTIONS,
102695
102704
  headless
102696
102705
  });
102706
+ opts.firefoxUserPrefs = {
102707
+ ...opts.firefoxUserPrefs,
102708
+ ...MEMORY_FIREFOX_PREFS
102709
+ };
102710
+ return opts;
102697
102711
  }
102698
102712
  var ensurePromise = null;
102699
102713
  function ensureCamoufox(log) {
@@ -102805,6 +102819,16 @@ function withTimeout(promise2, ms, message) {
102805
102819
  });
102806
102820
  return Promise.race([promise2, timeout]).finally(() => clearTimeout(timer));
102807
102821
  }
102822
+ function readProcStartTime(pid) {
102823
+ try {
102824
+ const stat = readFileSync(`/proc/${pid}/stat`, "utf8");
102825
+ const afterComm = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
102826
+ const starttime = Number(afterComm[19]);
102827
+ return Number.isFinite(starttime) ? starttime : null;
102828
+ } catch {
102829
+ return null;
102830
+ }
102831
+ }
102808
102832
  function collectDescendantPids(rootPid) {
102809
102833
  let entries;
102810
102834
  try {
@@ -102907,11 +102931,16 @@ class PlaywrightMcpSession {
102907
102931
  forceKillProcess(proc = this.mcpProcess) {
102908
102932
  if (!proc || proc.exitCode !== null)
102909
102933
  return;
102910
- const descendants = proc.pid ? collectDescendantPids(proc.pid) : [];
102934
+ const rootPid = proc.pid ?? null;
102935
+ const descendants = rootPid != null ? collectDescendantPids(rootPid) : [];
102936
+ const descendantStarts = new Map;
102937
+ for (const pid of descendants) {
102938
+ descendantStarts.set(pid, readProcStartTime(pid));
102939
+ }
102911
102940
  try {
102912
- if (proc.pid) {
102941
+ if (rootPid != null) {
102913
102942
  try {
102914
- process.kill(-proc.pid, "SIGKILL");
102943
+ process.kill(-rootPid, "SIGKILL");
102915
102944
  } catch {
102916
102945
  proc.kill("SIGKILL");
102917
102946
  }
@@ -102920,6 +102949,12 @@ class PlaywrightMcpSession {
102920
102949
  }
102921
102950
  } catch {}
102922
102951
  for (const pid of descendants) {
102952
+ const expected = descendantStarts.get(pid);
102953
+ if (expected != null) {
102954
+ const now = readProcStartTime(pid);
102955
+ if (now == null || now !== expected)
102956
+ continue;
102957
+ }
102923
102958
  try {
102924
102959
  process.kill(pid, "SIGKILL");
102925
102960
  } catch {}
@@ -104892,6 +104927,10 @@ const fs = require('fs');
104892
104927
  try {
104893
104928
  context = await firefox.launchPersistentContext('/tmp/pw-user-data', {
104894
104929
  ...__camou,
104930
+ // Layer memory prefs over Camoufox's fingerprint prefs (ours win); see
104931
+ // MEMORY_FIREFOX_PREFS in ./camoufox — collapses Fission/content-process
104932
+ // fan-out that otherwise costs ~3 GB across the run.
104933
+ firefoxUserPrefs: { ...__camou.firefoxUserPrefs, ...${JSON.stringify(MEMORY_FIREFOX_PREFS)} },
104895
104934
  ...(__extraHeaders ? { extraHTTPHeaders: __extraHeaders } : {}),
104896
104935
  });
104897
104936
  const pages = context.pages();
@@ -105094,6 +105133,7 @@ Use this to document:
105094
105133
  mkdirSync2(dir, { recursive: true });
105095
105134
  }
105096
105135
  writeFileSync2(localPath, Buffer.from(result.data, "base64"));
105136
+ sandbox.execute(`rm -f ${sandboxPath}`, { timeout: 10 }).catch(() => {});
105097
105137
  return {
105098
105138
  success: true,
105099
105139
  path: localPath,
@@ -106412,6 +106452,7 @@ init_zod();
106412
106452
  init_credentials();
106413
106453
  import { writeFileSync as writeFileSync6 } from "node:fs";
106414
106454
  import { join as join10 } from "node:path";
106455
+ init_id();
106415
106456
  init_structured();
106416
106457
  init_lazyLogger();
106417
106458
  var log4 = scopedLogger(() => createLogger("delegate_auth"));
@@ -106498,6 +106539,8 @@ IMPORTANT: Pass protectedEndpoints in authHints when you've discovered 401/403 e
106498
106539
  authHints,
106499
106540
  reason
106500
106541
  }) => {
106542
+ const subagentId = newSessionId();
106543
+ const subagentName = "Authentication Agent";
106501
106544
  try {
106502
106545
  if (!ctx.model) {
106503
106546
  return {
@@ -106523,9 +106566,9 @@ IMPORTANT: Pass protectedEndpoints in authHints when you've discovered 401/403 e
106523
106566
  tokens = { ...stored.tokens };
106524
106567
  }
106525
106568
  }
106526
- const subagentId = "auth-agent";
106527
106569
  ctx.eventBus?.emit("subagent-spawn", {
106528
106570
  subagentId,
106571
+ name: subagentName,
106529
106572
  input: { target, reason },
106530
106573
  parentSubagentId: ctx.subagentId
106531
106574
  });
@@ -106570,7 +106613,7 @@ IMPORTANT: Pass protectedEndpoints in authHints when you've discovered 401/403 e
106570
106613
  if (credentials) {
106571
106614
  ctx.session.credentialManager.addFromAuthCredentials(credentials);
106572
106615
  }
106573
- const { runAuthenticationAgent } = await import("./authentication-h1zxd74t.js");
106616
+ const { runAuthenticationAgent } = await import("./authentication-tqx2brcv.js");
106574
106617
  const localBus = new AgentEventBus;
106575
106618
  AgentEventBus.attachChild(localBus, ctx.eventBus, subagentId);
106576
106619
  const result = await runAuthenticationAgent({
@@ -106581,7 +106624,9 @@ IMPORTANT: Pass protectedEndpoints in authHints when you've discovered 401/403 e
106581
106624
  authConfig: ctx.authConfig,
106582
106625
  abortSignal: ctx.abortSignal,
106583
106626
  eventBus: localBus,
106584
- subagentId
106627
+ subagentId,
106628
+ subagentName,
106629
+ secretValues: ctx.secretValues
106585
106630
  });
106586
106631
  ctx.eventBus?.emit("subagent-complete", {
106587
106632
  subagentId,
@@ -106635,7 +106680,7 @@ Auth data saved to: ${result.authDataPath}` : ""}` : `Authentication subagent fa
106635
106680
  };
106636
106681
  } catch (error) {
106637
106682
  ctx.eventBus?.emit("subagent-complete", {
106638
- subagentId: "auth-agent",
106683
+ subagentId,
106639
106684
  status: "failed",
106640
106685
  parentSubagentId: ctx.subagentId
106641
106686
  });
@@ -106896,6 +106941,87 @@ Each application creates a JSON file in the apps directory for tracking and anal
106896
106941
  });
106897
106942
  }
106898
106943
 
106944
+ // src/core/agents/specialized/attackSurface/grpcSchema.ts
106945
+ init_zod();
106946
+ var EndpointTransportEnum = exports_external.enum([
106947
+ "http",
106948
+ "grpc",
106949
+ "grpc_web",
106950
+ "connect"
106951
+ ]);
106952
+ var GrpcStreamingTypeEnum = exports_external.enum([
106953
+ "unary",
106954
+ "server_stream",
106955
+ "client_stream",
106956
+ "bidi"
106957
+ ]);
106958
+ var GrpcSchemaSourceEnum = exports_external.enum([
106959
+ "proto",
106960
+ "reflection",
106961
+ "protoset",
106962
+ "js_bundle",
106963
+ "gateway_derived",
106964
+ "unknown"
106965
+ ]);
106966
+ var GrpcGatewayTypeEnum = exports_external.enum(["rest", "graphql", "connect"]);
106967
+ var GrpcEndpointMetadataSchema = exports_external.object({
106968
+ serviceFqn: exports_external.string().describe("Fully-qualified service name, e.g. 'account.v1.AccountService'"),
106969
+ method: exports_external.string().describe("RPC method name, e.g. 'GetAccount'"),
106970
+ streamingType: GrpcStreamingTypeEnum.default("unary"),
106971
+ reflectionAvailable: exports_external.boolean().optional(),
106972
+ schemaSource: GrpcSchemaSourceEnum.optional(),
106973
+ host: exports_external.string().nullish().describe("Authority override; null falls back to the app domain"),
106974
+ frontingGatewayOperation: exports_external.object({
106975
+ gatewayType: GrpcGatewayTypeEnum,
106976
+ operation: exports_external.string()
106977
+ }).nullish()
106978
+ });
106979
+ var GrpcPentestContextSchema = exports_external.object({
106980
+ transport: exports_external.enum(["grpc", "grpc_web", "connect"]),
106981
+ serviceFqn: exports_external.string(),
106982
+ method: exports_external.string(),
106983
+ streamingType: GrpcStreamingTypeEnum.optional(),
106984
+ reflectionAvailable: exports_external.boolean().optional(),
106985
+ schemaSource: GrpcSchemaSourceEnum.optional(),
106986
+ host: exports_external.string().nullish().describe("Authority override; null falls back to the app domain"),
106987
+ frontingGatewayOperation: exports_external.object({
106988
+ gatewayType: GrpcGatewayTypeEnum,
106989
+ operation: exports_external.string()
106990
+ }).nullish()
106991
+ });
106992
+ function deriveGrpcMetadataFromPath(path) {
106993
+ if (!path)
106994
+ return;
106995
+ const wire = path.replace(/^\//, "").split("?")[0];
106996
+ const slash = wire.lastIndexOf("/");
106997
+ if (slash <= 0 || slash === wire.length - 1)
106998
+ return;
106999
+ return { serviceFqn: wire.slice(0, slash), method: wire.slice(slash + 1) };
107000
+ }
107001
+ function toGrpcPentestContext(transport, grpc, path) {
107002
+ if (!transport || transport === "http")
107003
+ return;
107004
+ const derived = grpc ?? deriveGrpcMetadataFromPath(path);
107005
+ if (!derived)
107006
+ return;
107007
+ return {
107008
+ transport,
107009
+ serviceFqn: derived.serviceFqn,
107010
+ method: derived.method,
107011
+ streamingType: derived.streamingType,
107012
+ reflectionAvailable: derived.reflectionAvailable,
107013
+ schemaSource: derived.schemaSource,
107014
+ host: derived.host,
107015
+ frontingGatewayOperation: derived.frontingGatewayOperation
107016
+ };
107017
+ }
107018
+ function grpcAuthorityTarget(grpc, path) {
107019
+ if (!grpc?.host)
107020
+ return;
107021
+ const wirePath = path.startsWith("/") ? path : `/${path}`;
107022
+ return `grpc://${grpc.host}${wirePath}`;
107023
+ }
107024
+
106899
107025
  // src/core/agents/offSecAgent/tools/documentEndpoint.ts
106900
107026
  init_dist();
106901
107027
  init_zod();
@@ -107218,6 +107344,7 @@ function validateConcurrency(concurrency) {
107218
107344
 
107219
107345
  // src/core/agents/offSecAgent/tools/threatModelGenerator.ts
107220
107346
  init_zod();
107347
+ init_id();
107221
107348
  init_structured();
107222
107349
  init_lazyLogger();
107223
107350
  var log5 = scopedLogger(() => createLogger("threat-model-generator"));
@@ -107242,8 +107369,8 @@ var PentestObjectiveSchema = exports_external.object({
107242
107369
  });
107243
107370
  var SCHEMA_SOURCE_UNAVAILABLE_NOTE = "If the handler source is unreachable in your environment (source-unavailable mode — see prompt body), cite the grounding source you actually read (endpoint description, route table, OpenAPI spec, README) instead of `file:line` and tag the claim `[unverified: source unavailable]`. Never fabricate `file:line` references or invent code paths.";
107244
107371
  var ThreatModelResultSchema = exports_external.object({
107245
- businessLogic: exports_external.string().describe("Comprehensive narrative (aim for 800-2000 words) of what this endpoint does and the " + "rules it must uphold. Use markdown headings. Cover, in order: " + "(1) Purpose — what the endpoint accomplishes in user terms. " + "(2) Actors — who legitimately interacts with it, what they can do, how they authenticate. " + "(3) Data flow — inputs (with channel and trust level), transformations, side effects, outputs. " + "(4) Invariants — falsifiable business rules the endpoint must uphold, with enforcement " + "location (file:line) and confidence level. " + "(5) Trust boundaries — what actually guards each crossing (not what should). " + "(6) Analysis gaps — what you did NOT trace into and why. " + "Cite file:line for every concrete claim. Prefer completeness over brevity. " + SCHEMA_SOURCE_UNAVAILABLE_NOTE),
107246
- threatModel: exports_external.string().describe("Comprehensive threat model (aim for 800-2000 words) grounded in the business logic. " + "Use markdown headings. Cover: " + "(1) Attacker profiles — 2-4 realistic attackers with motivation, skill, position, and goal. " + "(2) Attack vectors — enumerate thoroughly (typically 8-15 for a non-trivial endpoint). " + "Each vector must reference a concrete input from the business logic, the invariant it " + "would break, the mechanism, the observable signal of success, an attacker profile, " + "likelihood, and impact. Cite file:line. " + "(3) Risk assessment — worst-case impact and vector prioritization. " + "Do not include generic OWASP filler ungrounded in evidence you actually read. " + SCHEMA_SOURCE_UNAVAILABLE_NOTE),
107372
+ businessLogic: exports_external.string().describe("Comprehensive narrative (aim for 800-2000 words) of what this endpoint does and the " + "rules it must uphold. Use markdown headings. Cover, in order: " + "(1) Purpose — what the endpoint accomplishes in user terms. " + "(2) Actors — who legitimately interacts with it, what they can do, how they authenticate. " + "(3) Data flow — inputs including implicit framework-provided entry points reachable through the endpoint (with channel and trust level), transformations, side effects, outputs. " + "(4) Invariants — falsifiable business rules the endpoint must uphold, with enforcement " + "location (file:line) and confidence level. " + "(5) Trust boundaries — what actually guards each crossing (not what should). " + "(6) Analysis gaps — what you did NOT trace into and why. " + "Cite file:line for every concrete claim. Prefer completeness over brevity. " + SCHEMA_SOURCE_UNAVAILABLE_NOTE),
107373
+ threatModel: exports_external.string().describe("Comprehensive threat model (aim for 800-2000 words) grounded in the business logic. " + "Use markdown headings. Cover: " + "(1) Attacker profiles — 2-4 realistic attackers with motivation, skill, position, and goal. " + "(2) Attack vectors — enumerate thoroughly (typically 8-15 for a non-trivial endpoint). " + "Each vector must reference a concrete entry point from the business logic (including implicit framework-provided entry points), the invariant it " + "would break, the mechanism, the observable signal of success, an attacker profile, " + "likelihood, and impact. Cite file:line. " + "(3) Risk assessment — worst-case impact and vector prioritization. " + "Do not include generic OWASP filler ungrounded in evidence you actually read. " + SCHEMA_SOURCE_UNAVAILABLE_NOTE),
107247
107374
  exposure: exports_external.number().min(0).max(3).describe("Exposure Level (0-3): how reachable is this endpoint by an attacker? " + "3 = Public, no authentication required. " + "2 = Standard authenticated user can reach it. " + "1 = Requires privileged/admin role. " + "0 = Private, internal-only, IP-restricted, or behind a service mesh."),
107248
107375
  exposureReasoning: exports_external.string().describe("Cite specific code/config signals (file:line) that determined the exposure score: " + "route registration, auth middleware, role guards, IP allowlists, network placement. " + "E.g. 'Route registered on public router with no auth middleware (app.ts:18). " + "Handler does not check req.user.' " + SCHEMA_SOURCE_UNAVAILABLE_NOTE),
107249
107376
  dataSensitivity: exports_external.number().min(0).max(3).describe("Data Sensitivity (0-3): how sensitive is the data this endpoint reads, writes, or returns? " + "3 = PII, PHI, financial records, passwords, tokens, secrets, session material. " + "2 = Business-operations data, tenant configs, internal settings, non-public metadata. " + "1 = Low-value user data (public profile, preferences). " + "0 = No meaningful data (static content, health checks, public catalog)."),
@@ -107277,11 +107404,12 @@ async function generateThreatModelForEndpoint(ctx, input) {
107277
107404
  return threatModelLimiter(async () => {
107278
107405
  if (ctx.abortSignal?.aborted)
107279
107406
  return null;
107280
- const { CodeAgent } = await import("./agent-7ab546zf.js");
107281
- const subagentId = `threat-model-${sanitize(input.appName)}-${sanitize(input.routePath)}`;
107407
+ const { CodeAgent } = await import("./agent-3cdfwxcd.js");
107408
+ const subagentId = newSessionId();
107409
+ const subagentName = `Threat Model: ${input.routePath}`;
107282
107410
  ctx.eventBus?.emit("subagent-spawn", {
107283
107411
  subagentId,
107284
- name: `Threat Model: ${input.routePath}`,
107412
+ name: subagentName,
107285
107413
  input: { app: input.appName, endpoint: input.routePath },
107286
107414
  parentSubagentId: ctx.subagentId
107287
107415
  });
@@ -107307,6 +107435,7 @@ async function generateThreatModelForEndpoint(ctx, input) {
107307
107435
  abortSignal: childAbort.signal,
107308
107436
  eventBus: localBus,
107309
107437
  subagentId,
107438
+ subagentName,
107310
107439
  responseSchema: ThreatModelResultSchema,
107311
107440
  stopWhen: stepCountIs(THREAT_MODEL_MAX_STEPS),
107312
107441
  excludeTools: ["document_endpoint", "document_app"]
@@ -107356,6 +107485,13 @@ function buildThreatModelPrompt(input, projectThreatModel) {
107356
107485
  const lineRange = input.line ? `around line ${input.line}` : "";
107357
107486
  const authInfo = input.authRequired ? "Authentication required" : "No authentication required";
107358
107487
  const methodStr = Array.isArray(input.method) ? input.method.join(", ") : input.method ?? "unknown";
107488
+ const isGrpc = !!input.transport && input.transport !== "http";
107489
+ const grpcSection = isGrpc ? `- **Transport**: ${input.transport} — this is a **gRPC method**, not an HTTP route. \`Path\` is the wire path \`/package.Service/Method\`; do NOT model it as a REST URL.
107490
+ - **gRPC service**: ${input.grpc?.serviceFqn ?? "(derive from Path)"}
107491
+ - **gRPC method**: ${input.grpc?.method ?? "(derive from Path)"}
107492
+ - **Streaming**: ${input.grpc?.streamingType ?? "unary"}
107493
+ The threat model and pentest objectives must be gRPC-specific (reflection/schema recovery, per-method missing-auth, BOLA on request-message IDs, metadata trust, \`:path\` authz bypass, protobuf fuzzing, streaming abuse) rather than HTTP-centric.
107494
+ ` : "";
107359
107495
  let prompt = `# Endpoint Analysis
107360
107496
 
107361
107497
  ## Target Endpoint
@@ -107366,7 +107502,7 @@ function buildThreatModelPrompt(input, projectThreatModel) {
107366
107502
  - **Handler**: ${input.handler ?? "unknown"}
107367
107503
  - **Auth**: ${authInfo}
107368
107504
  - **Description**: ${input.description}
107369
-
107505
+ ${grpcSection}
107370
107506
  ## Reading the code
107371
107507
 
107372
107508
  1. Read the source file at \`${input.file ?? "(unknown)"}\` ${lineRange ? `(${lineRange})` : ""} to understand the implementation.
@@ -107389,7 +107525,7 @@ Produce a comprehensive narrative of what this endpoint does and what must be tr
107389
107525
  Who legitimately interacts with this endpoint. For each actor list their role, what they can legitimately do via this endpoint, and how they authenticate (session cookie, JWT, signed webhook, mTLS, API key, IP allowlist, etc.).
107390
107526
 
107391
107527
  ### Data Flow
107392
- - **Inputs**: each input the handler accepts — name, channel (path / query / body / header / cookie / file / env / upstream-service / queue / webhook), trust level (untrusted / semi-trusted / trusted), and any observed validation. Cite file:line.
107528
+ - **Inputs**: each input reachable through this endpoint — name, channel (path / query / body / header / cookie / file / env / upstream-service / queue / webhook / server-action / rsc), trust level (untrusted / semi-trusted / trusted), and any observed validation. Include not just what the handler's signature accepts but implicit, framework-provided entry points a client can reach through this endpoint (e.g. server actions invoked from a page, RSC data fetches, framework-generated routes). Cite file:line.
107393
107529
  - **Transformations**: the ordered steps the handler takes — fetch, validate, authorize, compute, call external service. Cite file:line for each.
107394
107530
  - **Side effects**: every write the handler causes — DB writes, external API calls, email/queue/file writes, auth-state changes, payments. Name the target (table, service, queue).
107395
107531
  - **Outputs**: what the response body, headers, and status codes contain.
@@ -107444,10 +107580,29 @@ Before enumerating, name the 1–3 **functional roles** this endpoint plays (fro
107444
107580
 
107445
107581
  This is a **recall checklist, not a license to invent.** Skip role classes that have no anchor in this endpoint's code. Endpoint-specific vectors that don't fall under a listed role are welcome — mark them "endpoint-specific". Endpoint descriptions that name a role (e.g. "AI assistant", "payment intent", "webhook receiver", "password reset") are a strong signal to classify the corresponding role; if the handler source is unreachable, classify the role anyway and proceed under **source-unavailable mode** (defined in the reading-the-code section) — vectors still emit, but they're tagged \`[unverified: source unavailable]\` and capped at \`p1\` in Part 4.
107446
107582
 
107583
+ #### Step 1b — recall framework-derived surface
107584
+
107585
+ Identify the framework and stack this endpoint runs on — from the code where available (package.json, config, imports, file-system conventions), or from the endpoint description in source-unavailable mode. Frameworks expose implicit, convention-based surface that is reachable *through* this endpoint but never appears as an explicit route in the endpoint list. Enumerate it and fold it into this endpoint's threat model and test plan.
107586
+
107587
+ The classes below are high-signal framework sharp edges, with a few frameworks named under each. **They are examples to jog recall — not an exhaustive catalog, and not a fixed list of frameworks.** The named frameworks are illustrations; every stack has its own version of each class, and classes not listed here exist too. Skip any class with no anchor in this endpoint's code, and **generalize the same reasoning to whatever framework you actually detect**, including ones not named here — the point is the *class of sharp edge*, not the specific example.
107588
+
107589
+ | Framework sharp-edge class (examples) | Where it shows up (example frameworks) | Probe / confirming signal |
107590
+ |---|---|---|
107591
+ | **Hidden data/RPC endpoint reachable by suffix or header** — auth enforced in the UI component, not the loader/action/endpoint | e.g. Next.js server actions (POST the page URL + \`Next-Action: <hash>\`), RSC stream (\`RSC: 1\`); Remix / React Router \`<route>.data\` or \`?_data=<id>\`; SvelteKit \`<route>/__data.json\`, \`+server.ts\`; Nuxt \`<route>/_payload.json\`, \`/__nuxt_island/<Name>_<hash>?props=\`, \`server/api/**\`; FastAPI \`/openapi.json\` | Hit the data URL directly with no / other-tenant session; a 200 with data instead of 302/401 means access control lives only in the UI |
107592
+ | **Debug/error mode shipped to prod → secret leak, often RCE** — force a 500 and fingerprint the framework error UI | e.g. Django technical-500 (dumps \`SECRET_KEY\`), Flask/Werkzeug \`/console\`, Rails dev pages + \`/rails/info\`, Laravel Ignition (\`POST /_ignition/execute-solution\` = unauth RCE), ASP.NET dev-exception page / \`elmah.axd\` / \`Trace.axd\`, Nuxt devtools + \`/_nuxt/*.map\` | Trigger an unhandled error; a framework-styled traceback means debug is on — chase the signing secret or the console/Ignition RCE from there |
107593
+ | **Conventional admin / introspection / actuator paths** | e.g. Spring \`/actuator/{env,heapdump,mappings,gateway/routes}\` (heapdump → creds, env → RCE), Django \`/admin\`, Laravel \`/telescope\` \`/horizon\`, Rails \`/rails/info/routes\` | GET the known paths directly; 200 = exposed (\`/actuator/heapdump\` returning an \`octet-stream\` hprof is the tell) |
107594
+ | **Signing key / app secret → cookie & session forgery (sometimes RCE)** | e.g. Django \`SECRET_KEY\` (+ pickle sessions → RCE), Flask \`SECRET_KEY\`, Rails \`secret_key_base\` (Marshal cookie → RCE), Laravel \`APP_KEY\`, ASP.NET \`machineKey\` / ViewState | Recover the key via a debug/config leak, forge an elevated session cookie, replay it |
107595
+ | **"Public" env by naming prefix → secret inlined into the client bundle** | e.g. Next \`NEXT_PUBLIC_*\`, SvelteKit \`PUBLIC_*\` (\`/_app/env.js\`), Nuxt \`runtimeConfig.public\` (\`__NUXT_DATA__\`) | Grep \`/_next/static\`, \`/_app/immutable\`, and hydration blobs for secret-shaped values and \`.map\` files |
107596
+ | **ORM mass-assignment via binding defaults** | e.g. Django \`ModelForm fields='__all__'\` / DRF, Rails \`permit!\`, Laravel \`$guarded = []\`, NestJS missing \`whitelist: true\`, FastAPI missing \`response_model\` (over-serializes \`hashed_password\`) | Over-post \`is_admin\` / \`role\` / \`owner_id\`; check whether it persists or leaks |
107597
+ | **Built-in fetch or image proxy → SSRF / traversal** | e.g. Next \`/_next/image?url=\`, Nuxt \`/_ipx/\`, Rails \`open()\` / open-uri | Point it at \`http://169.254.169.254/…\` or an internal host; a proxied or timing-differentiated response confirms |
107598
+ | **Framework expression / deserialization RCE** | e.g. Spring SpEL (\`#{T(java.lang.Runtime)…}\`) and data-binding classloader (\`class.module.classLoader.*\`), ASP.NET ViewState / Json.NET \`$type\`, Rails YAML/Marshal, template injection in Jinja / Blade / ERB, Node prototype pollution (\`__proto__[x]\`) | A framework-specific canary (\`#{7*7}\`, \`{{7*7}}\`, a bad-\`$type\` error, \`__proto__\` reflection) that proves evaluation |
107599
+
107600
+ Treat any framework-derived surface you find this way as in-scope for this endpoint whenever this endpoint can reach it, and carry it into Part 4's test plan.
107601
+
107447
107602
  #### Step 2 — enumerate
107448
107603
 
107449
107604
  Each vector must:
107450
- - Reference a concrete **input** from Part 1's data flow (the entry point).
107605
+ - Reference a concrete **entry point** — an input from Part 1's data flow, including the framework-derived entry points captured there (Step 1b).
107451
107606
  - Reference the **invariant(s)** from Part 1 that it would break, when applicable. If a vector is a config/infrastructure concern with no business invariant, say so.
107452
107607
  - Describe the **mechanism** concretely — reference actual parameters, data flows, and code patterns you observed. Cite file:line (or, in source-unavailable mode, the grounding source you read — see the reading-the-code section).
107453
107608
  - State the **observable signal** of a successful exploit (what you would see externally to confirm it worked).
@@ -107563,9 +107718,6 @@ function flattenPentestObjective(o) {
107563
107718
  ].join(`
107564
107719
  `);
107565
107720
  }
107566
- function sanitize(s) {
107567
- return s.toLowerCase().replace(/[^a-z0-9-_.]/g, "_");
107568
- }
107569
107721
 
107570
107722
  // src/core/agents/offSecAgent/tools/documentEndpoint.ts
107571
107723
  function sanitizeName2(name) {
@@ -107574,7 +107726,9 @@ function sanitizeName2(name) {
107574
107726
  var documentEndpointInputSchema = exports_external.object({
107575
107727
  appName: exports_external.string().describe("Name of the parent application this endpoint belongs to. " + "Must match the appName used in a prior document_app call."),
107576
107728
  routePath: exports_external.string().describe("The endpoint's unique identity — its HTTP route or access pattern. " + "This is the URL path a client requests, NOT a source-file path. " + "Examples: '/api/users', '/dashboard', '/auth/login', " + "'/{objectKey}?X-Amz-Signature=...', 'arn:aws:s3:::bucket-name'. " + "For cloud-resource endpoints (endpointType 'asset') use the specific " + "access pattern, not the base domain URL (which is stored on the parent " + "application). The route path is the endpoint's display identity — there " + "is no separate name field."),
107577
- endpointType: exports_external.enum(["api-endpoint", "web-endpoint", "asset"]).describe("Type of endpoint: 'api-endpoint' for REST/GraphQL APIs, " + "'web-endpoint' for pages/views, 'asset' for other resources"),
107729
+ endpointType: exports_external.enum(["api-endpoint", "web-endpoint", "asset"]).describe("Type of endpoint: 'api-endpoint' for REST/GraphQL APIs, " + "'web-endpoint' for pages/views, 'asset' for other resources. " + "A gRPC method is still 'api-endpoint' — set `transport` to mark it gRPC."),
107730
+ transport: EndpointTransportEnum.optional().describe("Wire transport. Omit or 'http' for normal endpoints. Set 'grpc' " + "(or 'grpc_web' / 'connect') for a gRPC method and populate `grpc`."),
107731
+ grpc: GrpcEndpointMetadataSchema.optional().describe("gRPC attributes — required when transport is a gRPC variant. Put the " + "wire path '/package.Service/Method' in `routePath`; do not glue on a host."),
107578
107732
  description: exports_external.string().describe("Detailed description of the endpoint including what it does"),
107579
107733
  method: exports_external.union([exports_external.string(), exports_external.array(exports_external.string())]).optional().describe("HTTP method(s) supported (e.g., 'GET', 'POST', or ['GET', 'POST', 'DELETE']). " + "Use 'PAGE' for web pages/views."),
107580
107734
  handler: exports_external.string().optional().describe("Handler function or component name (whitebox analysis)"),
@@ -107671,7 +107825,9 @@ Each endpoint creates a JSON file in the assets directory for tracking and analy
107671
107825
  line: input.line,
107672
107826
  handler: input.handler,
107673
107827
  authRequired: input.authRequired,
107674
- description: input.description
107828
+ description: input.description,
107829
+ transport: input.transport,
107830
+ grpc: input.grpc
107675
107831
  };
107676
107832
  const threatModelOutput = await generateThreatModelForEndpoint(ctx, subagentInput);
107677
107833
  const riskScore = threatModelOutput?.riskScore ?? heuristicRiskScore;
@@ -107707,6 +107863,8 @@ Each endpoint creates a JSON file in the assets directory for tracking and analy
107707
107863
  appName: input.appName,
107708
107864
  routePath: input.routePath,
107709
107865
  endpointType: input.endpointType,
107866
+ transport: input.transport,
107867
+ grpc: input.grpc,
107710
107868
  riskLevel: input.riskLevel,
107711
107869
  filepath,
107712
107870
  businessLogic: businessLogic ?? undefined,
@@ -107747,6 +107905,7 @@ function hasCanonicalName(cwe) {
107747
107905
  }
107748
107906
 
107749
107907
  // src/core/agents/offSecAgent/tools/documentFinding.ts
107908
+ init_id();
107750
107909
  init_structured();
107751
107910
  init_lazyLogger();
107752
107911
 
@@ -109168,7 +109327,7 @@ init_lazyLogger();
109168
109327
  var log6 = scopedLogger(() => createLogger("FindingJudge"));
109169
109328
  async function judgeFinding(input, ctx) {
109170
109329
  try {
109171
- const { FindingJudgeAgent } = await import("./agent-c06jb5zv.js");
109330
+ const { FindingJudgeAgent } = await import("./agent-yayy5x2a.js");
109172
109331
  const agent = new FindingJudgeAgent({
109173
109332
  finding: input,
109174
109333
  model: ctx.model,
@@ -109177,9 +109336,11 @@ async function judgeFinding(input, ctx) {
109177
109336
  abortSignal: ctx.abortSignal,
109178
109337
  eventBus: ctx.eventBus,
109179
109338
  subagentId: ctx.subagentId,
109339
+ subagentName: ctx.subagentName,
109180
109340
  sandbox: ctx.sandbox,
109181
109341
  target: input.target ?? ctx.target ?? ctx.session.targets[0],
109182
109342
  enableThinking: ctx.enableThinking,
109343
+ thinkingEffort: ctx.thinkingEffort,
109183
109344
  openAIReasoningEffort: ctx.openAIReasoningEffort
109184
109345
  });
109185
109346
  const result = await agent.consume();
@@ -109402,10 +109563,11 @@ CRITICAL RULES — READ BEFORE CALLING:
109402
109563
  vulnerabilityClass: input.vulnerabilityClass
109403
109564
  }
109404
109565
  };
109405
- const judgeSubagentId = nextJudgeSubagentId(ctx.subagentId);
109566
+ const judgeSubagentId = newSessionId();
109567
+ const judgeSubagentName = "Finding Judge";
109406
109568
  ctx.eventBus?.emit("subagent-spawn", {
109407
109569
  subagentId: judgeSubagentId,
109408
- name: "Finding Judge",
109570
+ name: judgeSubagentName,
109409
109571
  input: { title: input.title, endpoint: input.endpoint },
109410
109572
  parentSubagentId: ctx.subagentId
109411
109573
  });
@@ -109420,9 +109582,11 @@ CRITICAL RULES — READ BEFORE CALLING:
109420
109582
  abortSignal: ctx.abortSignal,
109421
109583
  eventBus: judgeBus,
109422
109584
  subagentId: judgeSubagentId,
109585
+ subagentName: judgeSubagentName,
109423
109586
  sandbox: ctx.sandbox,
109424
109587
  target: ctx.target,
109425
109588
  enableThinking: ctx.enableThinking,
109589
+ thinkingEffort: ctx.thinkingEffort,
109426
109590
  openAIReasoningEffort: ctx.openAIReasoningEffort
109427
109591
  });
109428
109592
  } catch (error) {
@@ -109735,14 +109899,6 @@ ${finding.references}` : ""}
109735
109899
  }
109736
109900
  });
109737
109901
  }
109738
- var judgeCounters = new Map;
109739
- function nextJudgeSubagentId(parentSubagentId) {
109740
- const key = parentSubagentId ?? "";
109741
- const next = (judgeCounters.get(key) ?? 0) + 1;
109742
- judgeCounters.set(key, next);
109743
- const prefix = parentSubagentId ? `${parentSubagentId}-` : "";
109744
- return `${prefix}finding-judge-${next}`;
109745
- }
109746
109902
  async function executeLocalPoc(ctx, input) {
109747
109903
  const pocsPath = ctx.session.pocsPath;
109748
109904
  if (!existsSync8(pocsPath)) {
@@ -113266,7 +113422,306 @@ var SEND_EMAIL_TOOL_NAME = "send_email";
113266
113422
  init_dist();
113267
113423
  init_zod();
113268
113424
  import { existsSync as existsSync9, mkdirSync as mkdirSync8, writeFileSync as writeFileSync10 } from "node:fs";
113425
+ import { join as join15 } from "node:path";
113426
+
113427
+ // src/core/agents/offSecAgent/tools/agentScratch.ts
113269
113428
  import { join as join14 } from "node:path";
113429
+ function agentLogsDir(ctx) {
113430
+ return ctx.subagentId ? join14(ctx.session.rootPath, "subagents", ctx.subagentId, "logs") : ctx.session.logsPath;
113431
+ }
113432
+
113433
+ // src/core/agents/offSecAgent/tools/destructiveGuard.ts
113434
+ var NOT_DESTRUCTIVE = { destructive: false };
113435
+ function safeDecode2(input) {
113436
+ let out = input ?? "";
113437
+ for (let pass = 0;pass < 2; pass++) {
113438
+ const decoded = out.replace(/\+/g, " ").replace(/%[0-9a-fA-F]{2}/g, (m) => {
113439
+ try {
113440
+ return decodeURIComponent(m);
113441
+ } catch {
113442
+ return m;
113443
+ }
113444
+ });
113445
+ if (decoded === out)
113446
+ break;
113447
+ out = decoded;
113448
+ }
113449
+ return out;
113450
+ }
113451
+ var SQL_SEP = String.raw`(?:\s|/\*[\s\S]*?\*/)+`;
113452
+ var SQL_DESTRUCTIVE_STATEMENT = new RegExp(`(?:drop${SQL_SEP}(?:table|database|schema|index|view|column|role|user|sequence|trigger|function|procedure)` + `|truncate${SQL_SEP}(?:table${SQL_SEP})?[\`"\\w]` + `|delete${SQL_SEP}from${SQL_SEP}[\`"\\w]` + `|alter${SQL_SEP}table${SQL_SEP}[^;]*\\bdrop\\b)`, "i");
113453
+ var SQL_STATEMENT_START = new RegExp(`^\\s*['"\`;)\\s]*(?:${SQL_DESTRUCTIVE_STATEMENT.source})`, "i");
113454
+ var SQL_INJECTION_CONTEXT = new RegExp(`['"\`]\\s*;?\\s*(?:${SQL_DESTRUCTIVE_STATEMENT.source})|;\\s*(?:${SQL_DESTRUCTIVE_STATEMENT.source})`, "i");
113455
+ function httpCarriesDestructiveSql(decoded) {
113456
+ if (!SQL_DESTRUCTIVE_STATEMENT.test(decoded))
113457
+ return false;
113458
+ return SQL_STATEMENT_START.test(decoded) || SQL_INJECTION_CONTEXT.test(decoded);
113459
+ }
113460
+ var NOSQL_HTTP_DESTRUCTIVE = /\bdb(?:\.\w+)+\.(?:drop(?:Database)?|deleteMany|remove)\s*\(|\.dropDatabase\s*\(|\.drop\s*\(\s*\)|\bdeleteMany\s*\(\s*\{|(?:^|[\n;])\s*(?:flushall|flushdb)\b/i;
113461
+ var NOSQL_CMD_DESTRUCTIVE = /\bdb(?:\.\w+)*\.(?:drop(?:Database)?|deleteMany|remove)\s*\(|\.dropDatabase\s*\(|\.drop\s*\(\s*\)|\bflushall\b|\bflushdb\b/i;
113462
+ var WRITE_METHODS = new Set(["POST", "PUT", "PATCH"]);
113463
+ var DESTRUCTIVE_PATH_PATTERN = /(?:^|[/_?&=.-])(?:delete|destroy|drop|purge|truncate|wipe|obliterate|nuke|remove|hard[-_]?delete|force[-_]?delete)(?:[/_?&=.-]|$)/i;
113464
+ var METHOD_OVERRIDE_DELETE_PATTERN = /(?:_method|x-http-method-override|x-http-method|x-method-override|x-method|httpmethod)\s*[=:]\s*['"]?delete\b/i;
113465
+ var CMD_BOUNDARY = String.raw`(?:^|[\n;&|(]|&&|\|\||-[a-z]*c\s+["']?)\s*(?:sudo\s+)?`;
113466
+ var HOME_REF = /^(?:~[^/]*|\$\{?HOME\}?)(?:\/|$)/;
113467
+ function normalizeRmTarget(raw) {
113468
+ const unquoted = raw.replace(/^['"]|['"]$/g, "");
113469
+ if (HOME_REF.test(unquoted))
113470
+ return "~";
113471
+ if (!unquoted.startsWith("/"))
113472
+ return unquoted;
113473
+ const stack = [];
113474
+ for (const seg of unquoted.split("/")) {
113475
+ if (seg === "" || seg === ".")
113476
+ continue;
113477
+ if (seg === "..") {
113478
+ stack.pop();
113479
+ continue;
113480
+ }
113481
+ stack.push(seg);
113482
+ }
113483
+ return `/${stack.join("/")}`;
113484
+ }
113485
+ function isDangerousRm(command) {
113486
+ const rmRe = /\brm\b([^\n;|&]*)/gi;
113487
+ for (const match of command.matchAll(rmRe)) {
113488
+ const args = match[1] ?? "";
113489
+ const hasRecursive = /(?:^|\s)(?:-[a-z]*r[a-z]*|--recursive)\b/i.test(args);
113490
+ const hasForce = /(?:^|\s)(?:-[a-z]*f[a-z]*|--force)\b/i.test(args);
113491
+ if (!hasRecursive || !hasForce)
113492
+ continue;
113493
+ if (/--no-preserve-root/i.test(args))
113494
+ return true;
113495
+ for (const rawToken of args.split(/\s+/)) {
113496
+ if (!rawToken || rawToken.startsWith("-"))
113497
+ continue;
113498
+ if (/^['"]?\/\*/.test(rawToken))
113499
+ return true;
113500
+ const norm = normalizeRmTarget(rawToken);
113501
+ if (norm === "~")
113502
+ return true;
113503
+ if (norm === "/")
113504
+ return true;
113505
+ if (/^\/(?:etc|var|usr|bin|sbin|boot|lib|lib64|root|home|opt|srv|sys|proc|dev)(?:\/|$)/i.test(norm)) {
113506
+ return true;
113507
+ }
113508
+ }
113509
+ }
113510
+ return false;
113511
+ }
113512
+ var HOST_DESTRUCTIVE_RULES = [
113513
+ { id: "rm-rf-root", test: isDangerousRm },
113514
+ {
113515
+ id: "dd-to-device",
113516
+ test: (c) => {
113517
+ const m = new RegExp(`${CMD_BOUNDARY}dd\\b[^\\n;|&]*\\bof=\\s*['"]?(/dev/\\S+)`, "i").exec(c);
113518
+ if (!m)
113519
+ return false;
113520
+ const dev = m[1].replace(/['"].*$/, "");
113521
+ return !/^\/dev\/(?:null|zero|full|random|urandom|stdout|stderr|tty|fd\/)/i.test(dev);
113522
+ }
113523
+ },
113524
+ {
113525
+ id: "mkfs",
113526
+ test: (c) => new RegExp(`${CMD_BOUNDARY}(?:/s?bin/)?mkfs(?:\\.\\w+)?\\b`, "i").test(c)
113527
+ },
113528
+ {
113529
+ id: "clobber-device",
113530
+ test: (c) => />\s*['"]?\/dev\/(?:sd|nvme|hd|vd|xvd|mapper|disk|loop|dm-|md)/i.test(c)
113531
+ },
113532
+ {
113533
+ id: "fork-bomb",
113534
+ test: (c) => /:\s*\(\s*\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;\s*:/.test(c)
113535
+ },
113536
+ {
113537
+ id: "power-state",
113538
+ test: (c) => new RegExp(`${CMD_BOUNDARY}(?:/(?:usr/)?s?bin/)?(?:shutdown|reboot|halt|poweroff)\\b`, "i").test(c) || new RegExp(`${CMD_BOUNDARY}init\\s+0\\b`, "i").test(c)
113539
+ }
113540
+ ];
113541
+ var HTTP_CLIENT_PATTERN = /\b(?:curl|wget|xh|httpie)\b/i;
113542
+ var URL_IN_COMMAND = /https?:\/\/[^\s"'`;|&)<>]+/gi;
113543
+ var CURL_DELETE = /(?:-X|--request|--method)[=\s]*['"]?delete\b/i;
113544
+ var HTTPIE_METHOD = new RegExp(`${CMD_BOUNDARY}(?:https?|xh|httpie)\\s+(?:-{1,2}\\S+\\s+)*(delete|post|put|patch)\\b`, "i");
113545
+ var CURL_WRITE_METHOD = /(?:-X|--request|--method)[=\s]*['"]?(?:post|put|patch)\b/i;
113546
+ var CURL_DATA_FLAG = /(?:^|\s)(?:-d|--data(?:-raw|-binary|-urlencode|-ascii)?|-F|--form|--json|--post-data|--post-file|--body-data|--body-file)\b/i;
113547
+ var JS_METHOD_DELETE = /\bmethod\s*:\s*['"`]?\s*delete\b/i;
113548
+ var JS_HTTP_CLIENT_HINT = /\b(?:fetch|axios|got|ky|superagent|needle|node-fetch|undici|phin|XMLHttpRequest|xhr)\b|\.\s*(?:request|open)\s*\(/i;
113549
+ var JS_CLIENT_DELETE_CALL = /\b(?:axios|got|ky|superagent|needle|supertest|phin|page|apiContext|apiRequestContext|request)\s*(?:\.\s*\w+)*\.\s*delete\s*\(/i;
113550
+ function classifyCommandHttp(command) {
113551
+ const decoded = safeDecode2(command);
113552
+ const httpieMethod = HTTPIE_METHOD.exec(command)?.[1]?.toLowerCase();
113553
+ const hasShellHttpClient = HTTP_CLIENT_PATTERN.test(command) || httpieMethod !== undefined;
113554
+ const hasJsHttpClient = JS_HTTP_CLIENT_HINT.test(command);
113555
+ if (hasShellHttpClient && (CURL_DELETE.test(command) || httpieMethod === "delete")) {
113556
+ return {
113557
+ destructive: true,
113558
+ category: "http-delete-method",
113559
+ ruleId: "cmd-http-delete",
113560
+ reason: "command issues an HTTP DELETE against the target"
113561
+ };
113562
+ }
113563
+ if (JS_METHOD_DELETE.test(command) && hasJsHttpClient || JS_CLIENT_DELETE_CALL.test(command)) {
113564
+ return {
113565
+ destructive: true,
113566
+ category: "http-delete-method",
113567
+ ruleId: "cmd-script-http-delete",
113568
+ reason: "command runs an in-script HTTP DELETE (fetch/axios/Playwright) against the target"
113569
+ };
113570
+ }
113571
+ if ((hasShellHttpClient || hasJsHttpClient) && METHOD_OVERRIDE_DELETE_PATTERN.test(decoded)) {
113572
+ return {
113573
+ destructive: true,
113574
+ category: "http-delete-method",
113575
+ ruleId: "cmd-http-override-delete",
113576
+ reason: "command overrides the HTTP method to DELETE"
113577
+ };
113578
+ }
113579
+ if (hasShellHttpClient && httpCarriesDestructiveSql(decoded)) {
113580
+ return {
113581
+ destructive: true,
113582
+ category: "sql-destructive",
113583
+ ruleId: "cmd-http-sql",
113584
+ reason: "command sends a destructive SQL statement in an HTTP request (DROP/TRUNCATE/DELETE FROM/ALTER…DROP)"
113585
+ };
113586
+ }
113587
+ if (hasShellHttpClient && NOSQL_HTTP_DESTRUCTIVE.test(decoded)) {
113588
+ return {
113589
+ destructive: true,
113590
+ category: "nosql-destructive",
113591
+ ruleId: "cmd-http-nosql",
113592
+ reason: "command sends a destructive NoSQL/cache operation in an HTTP request"
113593
+ };
113594
+ }
113595
+ const writeIndicated = CURL_WRITE_METHOD.test(command) || CURL_DATA_FLAG.test(command) || httpieMethod === "post" || httpieMethod === "put" || httpieMethod === "patch";
113596
+ if (hasShellHttpClient && writeIndicated) {
113597
+ for (const rawUrl of command.match(URL_IN_COMMAND) ?? []) {
113598
+ if (DESTRUCTIVE_PATH_PATTERN.test(safeDecode2(rawUrl))) {
113599
+ return {
113600
+ destructive: true,
113601
+ category: "http-destructive-path",
113602
+ ruleId: "cmd-http-destructive-path",
113603
+ reason: "command sends a write request to a delete/destroy/purge route"
113604
+ };
113605
+ }
113606
+ }
113607
+ }
113608
+ return NOT_DESTRUCTIVE;
113609
+ }
113610
+ var DB_CLIENT_PATTERN = /\b(?:psql|mysql|mariadb|mongosh|mongo|sqlite3|sqlcmd|sqlplus|cqlsh|clickhouse-client|redis-cli|sqlmap)\b|\bcockroach\s+sql\b/i;
113611
+ function classifyHttpAction(input) {
113612
+ const method = input.method.toUpperCase();
113613
+ const decodedUrl = safeDecode2(input.url);
113614
+ const decodedBody = safeDecode2(typeof input.body === "string" ? input.body : "");
113615
+ const headerText = input.headers ? Object.entries(input.headers).map(([name, value]) => `${name}: ${String(value)}`).join(`
113616
+ `) : "";
113617
+ const overrideHaystack = `${decodedUrl}
113618
+ ${decodedBody}
113619
+ ${headerText}`;
113620
+ if (method === "DELETE") {
113621
+ return {
113622
+ destructive: true,
113623
+ category: "http-delete-method",
113624
+ ruleId: "http-method-delete",
113625
+ reason: "HTTP DELETE removes a resource on the target"
113626
+ };
113627
+ }
113628
+ if (METHOD_OVERRIDE_DELETE_PATTERN.test(overrideHaystack)) {
113629
+ return {
113630
+ destructive: true,
113631
+ category: "http-delete-method",
113632
+ ruleId: "http-method-override-delete",
113633
+ reason: "request overrides the HTTP method to DELETE"
113634
+ };
113635
+ }
113636
+ if (httpCarriesDestructiveSql(`${decodedUrl}
113637
+ ${decodedBody}`)) {
113638
+ return {
113639
+ destructive: true,
113640
+ category: "sql-destructive",
113641
+ ruleId: "http-body-sql",
113642
+ reason: "request carries a destructive SQL statement (DROP/TRUNCATE/DELETE FROM/ALTER…DROP)"
113643
+ };
113644
+ }
113645
+ if (NOSQL_HTTP_DESTRUCTIVE.test(decodedBody)) {
113646
+ return {
113647
+ destructive: true,
113648
+ category: "nosql-destructive",
113649
+ ruleId: "http-body-nosql",
113650
+ reason: "request carries a destructive NoSQL/cache operation (drop/deleteMany/flush)"
113651
+ };
113652
+ }
113653
+ if (WRITE_METHODS.has(method) && DESTRUCTIVE_PATH_PATTERN.test(decodedUrl)) {
113654
+ return {
113655
+ destructive: true,
113656
+ category: "http-destructive-path",
113657
+ ruleId: "http-destructive-path",
113658
+ reason: "write request targets a delete/destroy/purge route"
113659
+ };
113660
+ }
113661
+ return NOT_DESTRUCTIVE;
113662
+ }
113663
+ function classifyCommandAction(command) {
113664
+ for (const { id, test } of HOST_DESTRUCTIVE_RULES) {
113665
+ if (test(command)) {
113666
+ return {
113667
+ destructive: true,
113668
+ category: "host-destructive",
113669
+ ruleId: id,
113670
+ reason: `command performs a catastrophic host operation (${id})`
113671
+ };
113672
+ }
113673
+ }
113674
+ const decodedCommand = safeDecode2(command);
113675
+ if (DB_CLIENT_PATTERN.test(command)) {
113676
+ if (SQL_DESTRUCTIVE_STATEMENT.test(decodedCommand)) {
113677
+ return {
113678
+ destructive: true,
113679
+ category: "sql-destructive",
113680
+ ruleId: "cmd-sql",
113681
+ reason: "command runs a destructive SQL statement via a DB client (DROP/TRUNCATE/DELETE FROM/ALTER…DROP)"
113682
+ };
113683
+ }
113684
+ if (NOSQL_CMD_DESTRUCTIVE.test(decodedCommand)) {
113685
+ return {
113686
+ destructive: true,
113687
+ category: "nosql-destructive",
113688
+ ruleId: "cmd-nosql",
113689
+ reason: "command runs a destructive NoSQL/cache operation via a DB client (drop/deleteMany/flush)"
113690
+ };
113691
+ }
113692
+ }
113693
+ return classifyCommandHttp(command);
113694
+ }
113695
+
113696
+ class DestructiveActionError extends Error {
113697
+ classification;
113698
+ constructor(classification, subject) {
113699
+ super(`Destructive action blocked: ${subject} — ${classification.reason ?? "destructive operation"}. ` + `Destructive testing (DB deletes/drops, API write-deletes, catastrophic host operations) is NOT authorized for this engagement. ` + `Prove the flaw without completing the destructive step (e.g. demonstrate the authorization boundary is crossed with a read or a reversible action), or ask the operator to enable destructive testing.`);
113700
+ this.classification = classification;
113701
+ this.name = "DestructiveActionError";
113702
+ }
113703
+ }
113704
+ function isDestructiveTestingAllowed(ctx) {
113705
+ return ctx.session?.config?.allowDestructiveActions === true;
113706
+ }
113707
+ function assertHttpActionAllowed(input, ctx) {
113708
+ if (isDestructiveTestingAllowed(ctx))
113709
+ return;
113710
+ const classification = classifyHttpAction(input);
113711
+ if (classification.destructive) {
113712
+ throw new DestructiveActionError(classification, `${input.method.toUpperCase()} ${input.url}`);
113713
+ }
113714
+ }
113715
+ function assertCommandActionAllowed(command, ctx) {
113716
+ if (isDestructiveTestingAllowed(ctx))
113717
+ return;
113718
+ const classification = classifyCommandAction(command);
113719
+ if (classification.destructive) {
113720
+ throw new DestructiveActionError(classification, command);
113721
+ }
113722
+ }
113723
+
113724
+ // src/core/agents/offSecAgent/tools/executeCommand.ts
113270
113725
  var MAX_INLINE = 50000;
113271
113726
  var MS_TIMEOUT_THRESHOLD = 1e4;
113272
113727
  var DEFAULT_PROMPT_INJECTION_FILE_ENV = "APEX_PROMPT_INJECTION_FILE";
@@ -113309,13 +113764,13 @@ function maybeSaveFullOutput(raw, ctx) {
113309
113764
  if (raw.length <= MAX_INLINE) {
113310
113765
  return { text: raw || "(no output)" };
113311
113766
  }
113312
- const outputDir = join14(ctx.session.logsPath, "cmd-output");
113767
+ const outputDir = join15(agentLogsDir(ctx), "cmd-output");
113313
113768
  if (!existsSync9(outputDir)) {
113314
113769
  mkdirSync8(outputDir, { recursive: true });
113315
113770
  }
113316
113771
  const ts = new Date().toISOString().replace(/[:.]/g, "-");
113317
113772
  const filename = `output-${ts}.txt`;
113318
- const filePath = join14(outputDir, filename);
113773
+ const filePath = join15(outputDir, filename);
113319
113774
  try {
113320
113775
  writeFileSync10(filePath, raw);
113321
113776
  } catch {
@@ -113336,6 +113791,15 @@ function maybeSaveFullOutput(raw, ctx) {
113336
113791
  function shellQuote2(value) {
113337
113792
  return `'${value.replace(/'/g, `'\\''`)}'`;
113338
113793
  }
113794
+ function redactSecretValues(text, secrets) {
113795
+ if (!secrets?.length)
113796
+ return text;
113797
+ let out = text;
113798
+ for (const s of [...secrets].filter((v) => v && v.length >= 6).sort((a, b) => b.length - a.length)) {
113799
+ out = out.split(s).join("[REDACTED]");
113800
+ }
113801
+ return out;
113802
+ }
113339
113803
  function wrapCommandWithEnv(command, envVars) {
113340
113804
  if (!envVars || Object.keys(envVars).length === 0)
113341
113805
  return command;
@@ -113488,6 +113952,20 @@ IMPORTANT: Always analyze results and adjust your approach based on findings.`,
113488
113952
  };
113489
113953
  }
113490
113954
  const commandWithHeaders = inject.status === "injected" ? inject.command : command;
113955
+ try {
113956
+ assertCommandActionAllowed(commandWithHeaders, ctx);
113957
+ } catch (e) {
113958
+ if (e instanceof DestructiveActionError) {
113959
+ return {
113960
+ success: false,
113961
+ error: e.message,
113962
+ stdout: "",
113963
+ stderr: e.message,
113964
+ command
113965
+ };
113966
+ }
113967
+ throw e;
113968
+ }
113491
113969
  let promptInjectionLibrary;
113492
113970
  let promptInjectionEnvVars;
113493
113971
  let promptInjectionPayloadContent;
@@ -113515,7 +113993,10 @@ IMPORTANT: Always analyze results and adjust your approach based on findings.`,
113515
113993
  command
113516
113994
  };
113517
113995
  }
113518
- const redact = (value) => promptInjectionLibrary ? redactPromptInjectionPayloads(value, promptInjectionLibrary) : value;
113996
+ const redact = (value) => {
113997
+ const stripped = promptInjectionLibrary ? redactPromptInjectionPayloads(value, promptInjectionLibrary) : value;
113998
+ return redactSecretValues(stripped, ctx.secretValues);
113999
+ };
113519
114000
  if (ctx.sandbox) {
113520
114001
  try {
113521
114002
  const ssmOpts = {};
@@ -113908,7 +114389,7 @@ search with flags or a more specific directory if results are truncated.`,
113908
114389
  init_dist();
113909
114390
  init_zod();
113910
114391
  import { existsSync as existsSync10, mkdirSync as mkdirSync9, writeFileSync as writeFileSync11 } from "node:fs";
113911
- import { join as join15 } from "node:path";
114392
+ import { join as join16 } from "node:path";
113912
114393
  var MAX_INLINE_BODY = 5000;
113913
114394
  var promptInjectionRefSchema = exports_external.object({
113914
114395
  kind: exports_external.literal("prompt_injection_ref"),
@@ -113938,13 +114419,13 @@ function containsPromptInjectionRef(value) {
113938
114419
  function maybeSaveBody(body, ctx) {
113939
114420
  if (body.length <= MAX_INLINE_BODY)
113940
114421
  return { text: body };
113941
- const outputDir = join15(ctx.session.logsPath, "http-responses");
114422
+ const outputDir = join16(agentLogsDir(ctx), "http-responses");
113942
114423
  if (!existsSync10(outputDir)) {
113943
114424
  mkdirSync9(outputDir, { recursive: true });
113944
114425
  }
113945
114426
  const ts = new Date().toISOString().replace(/[:.]/g, "-");
113946
114427
  const filename = `response-${ts}.txt`;
113947
- const filePath = join15(outputDir, filename);
114428
+ const filePath = join16(outputDir, filename);
113948
114429
  try {
113949
114430
  writeFileSync11(filePath, body);
113950
114431
  } catch {
@@ -114009,6 +114490,7 @@ COMMON TESTING PATTERNS:
114009
114490
  followRedirects,
114010
114491
  timeout
114011
114492
  }) => {
114493
+ let headers = parseHeaders(rawHeaders);
114012
114494
  try {
114013
114495
  assertUrlInScope(url, ctx);
114014
114496
  } catch (e) {
@@ -114027,7 +114509,6 @@ COMMON TESTING PATTERNS:
114027
114509
  }
114028
114510
  throw e;
114029
114511
  }
114030
- let headers = parseHeaders(rawHeaders);
114031
114512
  let resolvedBody;
114032
114513
  let library = EMPTY_PROMPT_INJECTION_LIBRARY;
114033
114514
  try {
@@ -114038,6 +114519,8 @@ COMMON TESTING PATTERNS:
114038
114519
  }) : EMPTY_PROMPT_INJECTION_LIBRARY;
114039
114520
  headers = resolvePromptInjectionRefs(headers, library);
114040
114521
  resolvedBody = body === undefined ? undefined : String(resolvePromptInjectionRefs(body, library));
114522
+ const effectiveHeaders = resolveEffectiveHeaders(resolverSessionFromCtx(ctx), url, headers);
114523
+ assertHttpActionAllowed({ method, url, body: resolvedBody, headers: effectiveHeaders }, ctx);
114041
114524
  } catch (e) {
114042
114525
  return {
114043
114526
  success: false,
@@ -114137,17 +114620,17 @@ COMMON TESTING PATTERNS:
114137
114620
  }
114138
114621
  async function executeSandboxHttpRequest(ctx, opts, library) {
114139
114622
  const { url, method, headers, body, followRedirects, timeout } = opts;
114623
+ const { sandbox } = ctx;
114624
+ if (!sandbox) {
114625
+ throw new Error("executeSandboxHttpRequest requires a sandbox");
114626
+ }
114627
+ let bodyTempFile = null;
114140
114628
  try {
114141
114629
  let curlCommand = `curl -i -X ${method}`;
114142
114630
  const mergedHeaders = resolveEffectiveHeaders(resolverSessionFromCtx(ctx), url, headers);
114143
114631
  for (const [key, value] of Object.entries(mergedHeaders)) {
114144
114632
  curlCommand += ` -H "${shellQuote(`${key}: ${value}`)}"`;
114145
114633
  }
114146
- const { sandbox } = ctx;
114147
- if (!sandbox) {
114148
- throw new Error("executeSandboxHttpRequest requires a sandbox");
114149
- }
114150
- let bodyTempFile = null;
114151
114634
  if (body && ["POST", "PUT", "PATCH"].includes(method)) {
114152
114635
  bodyTempFile = `/tmp/apex_http_body_${Date.now()}_${Math.random().toString(36).slice(2, 11)}.txt`;
114153
114636
  const escapedForPrintf = body.replace(/\\/g, "\\\\").replace(/%/g, "%%");
@@ -114233,6 +114716,10 @@ async function executeSandboxHttpRequest(ctx, opts, library) {
114233
114716
  url,
114234
114717
  redirected: false
114235
114718
  };
114719
+ } finally {
114720
+ if (bodyTempFile) {
114721
+ await sandbox.execute(`rm -f ${bodyTempFile}`, { timeout: 10 }).catch(() => {});
114722
+ }
114236
114723
  }
114237
114724
  }
114238
114725
 
@@ -114240,7 +114727,7 @@ async function executeSandboxHttpRequest(ctx, opts, library) {
114240
114727
  init_dist();
114241
114728
  init_zod();
114242
114729
  import { readdir, stat } from "node:fs/promises";
114243
- import { isAbsolute as isAbsolute3, join as join16, relative as relative2, resolve as resolve3 } from "node:path";
114730
+ import { isAbsolute as isAbsolute3, join as join17, relative as relative2, resolve as resolve3 } from "node:path";
114244
114731
  var listFilesInputSchema = exports_external.object({
114245
114732
  directory: exports_external.string().optional().describe("Absolute or relative path to the directory to list (defaults to current working directory)"),
114246
114733
  recursive: exports_external.boolean().optional().describe("If true, list files recursively (default: false)"),
@@ -114260,7 +114747,7 @@ async function listRecursive(dir, maxEntries) {
114260
114747
  }
114261
114748
  for (const entry of entries) {
114262
114749
  total++;
114263
- const fullPath = join16(current, entry.name);
114750
+ const fullPath = join17(current, entry.name);
114264
114751
  if (entry.isDirectory()) {
114265
114752
  if (results.length < maxEntries)
114266
114753
  results.push(`${fullPath}/`);
@@ -114323,7 +114810,7 @@ Each directory entry is suffixed with "/" for easy identification.`,
114323
114810
  }
114324
114811
  const entries = await readdir(dir, { withFileTypes: true });
114325
114812
  const fullPaths = entries.map((e) => {
114326
- const name = join16(dir, e.name);
114813
+ const name = join17(dir, e.name);
114327
114814
  return e.isDirectory() ? `${name}/` : name;
114328
114815
  });
114329
114816
  const capped = fullPaths.slice(0, MAX_NON_RECURSIVE);
@@ -114465,9 +114952,9 @@ import {
114465
114952
  unlinkSync as unlinkSync2
114466
114953
  } from "node:fs";
114467
114954
  import { tmpdir } from "node:os";
114468
- import { join as join17 } from "node:path";
114955
+ import { join as join18 } from "node:path";
114469
114956
  var MAX_BUFFER = 5000000;
114470
- var APEX_TMP_ROOT = join17(tmpdir(), `apex-shell-${process.pid}`);
114957
+ var APEX_TMP_ROOT = join18(tmpdir(), `apex-shell-${process.pid}`);
114471
114958
  try {
114472
114959
  mkdirSync10(APEX_TMP_ROOT, { recursive: true });
114473
114960
  } catch {}
@@ -114739,8 +115226,8 @@ ${commandOutput}`;
114739
115226
  const marker = `__APEX_${nonceHex}__`;
114740
115227
  const exitMarkerPrefix = `${marker}_EXIT_`;
114741
115228
  const cutoverMarker = `${marker}_CUTOVER`;
114742
- const outPath = join17(APEX_TMP_ROOT, `${nonceHex}.out`);
114743
- const errPath = join17(APEX_TMP_ROOT, `${nonceHex}.err`);
115229
+ const outPath = join18(APEX_TMP_ROOT, `${nonceHex}.out`);
115230
+ const errPath = join18(APEX_TMP_ROOT, `${nonceHex}.err`);
114744
115231
  const pending = {
114745
115232
  streamedStdout: "",
114746
115233
  authoritativeStdout: "",
@@ -115159,7 +115646,7 @@ Returns discovered endpoints and recommended login approach.`,
115159
115646
  init_dist();
115160
115647
  init_zod();
115161
115648
  import { writeFileSync as writeFileSync12 } from "node:fs";
115162
- import { join as join18 } from "node:path";
115649
+ import { join as join19 } from "node:path";
115163
115650
  function provideComparisonResults(ctx) {
115164
115651
  return tool({
115165
115652
  description: `Provide the final comparison results with matched, missed, and extra findings.
@@ -115205,7 +115692,7 @@ Results will be saved to: comparison-results.json in the session directory.`,
115205
115692
  recall,
115206
115693
  precision
115207
115694
  };
115208
- const resultsPath = join18(ctx.session.rootPath, "comparison-results.json");
115695
+ const resultsPath = join19(ctx.session.rootPath, "comparison-results.json");
115209
115696
  writeFileSync12(resultsPath, JSON.stringify(result, null, 2));
115210
115697
  return {
115211
115698
  success: true,
@@ -115363,6 +115850,7 @@ function createResponseTool(responseSchema, onResult) {
115363
115850
  // src/core/agents/offSecAgent/tools/runAttackSurface.ts
115364
115851
  init_dist();
115365
115852
  init_zod();
115853
+ init_id();
115366
115854
  init_structured();
115367
115855
  init_lazyLogger();
115368
115856
  var log8 = scopedLogger(() => createLogger("run_attack_surface"));
@@ -115394,15 +115882,17 @@ should be passed directly to spawn_pentest_swarm for deep testing.`,
115394
115882
  message: "run_attack_surface requires a model in the tool context."
115395
115883
  };
115396
115884
  }
115397
- const subagentId = "attack-surface-agent";
115885
+ const subagentName = "Attack Surface";
115886
+ const subagentId = cwd ? newSessionId() : "attack-surface-agent";
115398
115887
  ctx.eventBus?.emit("subagent-spawn", {
115399
115888
  subagentId,
115889
+ name: subagentName,
115400
115890
  input: { target, cwd },
115401
115891
  parentSubagentId: ctx.subagentId
115402
115892
  });
115403
115893
  if (cwd) {
115404
115894
  try {
115405
- const { WhiteboxAttackSurfaceAgent } = await import("./index-rxskwf3k.js");
115895
+ const { WhiteboxAttackSurfaceAgent } = await import("./index-pxwwcefn.js");
115406
115896
  const localBus = new AgentEventBus;
115407
115897
  AgentEventBus.attachChild(localBus, ctx.eventBus, subagentId);
115408
115898
  const agent = new WhiteboxAttackSurfaceAgent({
@@ -115413,14 +115903,19 @@ should be passed directly to spawn_pentest_swarm for deep testing.`,
115413
115903
  abortSignal: ctx.abortSignal,
115414
115904
  attackSurfaceRegistry: ctx.attackSurfaceRegistry,
115415
115905
  eventBus: localBus,
115416
- subagentId
115906
+ subagentId,
115907
+ subagentName
115417
115908
  });
115418
115909
  const result = await agent.consume();
115419
- const targets = result.apps.flatMap((app) => [...app.pages, ...app.apiEndpoints].map((ep) => ({
115420
- target: ep.path,
115421
- objective: ep.pentestObjectives.join("; "),
115422
- rationale: `${app.framework} ${ep.method} endpoint in ${app.name} (${ep.file}${ep.line ? `:${ep.line}` : ""})`
115423
- })));
115910
+ const targets = result.apps.flatMap((app) => [...app.pages, ...app.apiEndpoints].map((ep) => {
115911
+ const grpc = toGrpcPentestContext(ep.transport, ep.grpc, ep.path);
115912
+ return {
115913
+ target: grpcAuthorityTarget(grpc, ep.path) ?? (ep.path.startsWith("http") ? ep.path : `${target}${ep.path}`),
115914
+ objective: ep.pentestObjectives.join("; "),
115915
+ rationale: `${app.framework} ${ep.method} endpoint in ${app.name} (${ep.file}${ep.line ? `:${ep.line}` : ""})`,
115916
+ grpc
115917
+ };
115918
+ }));
115424
115919
  log8.info(`Whitebox attack surface complete: ${targets.length} targets from ${result.apps.length} apps`);
115425
115920
  ctx.eventBus?.emit("subagent-complete", {
115426
115921
  subagentId,
@@ -115452,7 +115947,7 @@ should be passed directly to spawn_pentest_swarm for deep testing.`,
115452
115947
  }
115453
115948
  }
115454
115949
  try {
115455
- const { BlackboxAttackSurfaceAgent } = await import("./blackboxAgent-32zwxt6w.js");
115950
+ const { BlackboxAttackSurfaceAgent } = await import("./blackboxAgent-d0smgqt7.js");
115456
115951
  const localBus = new AgentEventBus;
115457
115952
  AgentEventBus.attachChild(localBus, ctx.eventBus, subagentId);
115458
115953
  const agent = new BlackboxAttackSurfaceAgent({
@@ -115530,7 +116025,7 @@ Omit \`cwd\` for blackbox mode (live target probing only).`,
115530
116025
  toolCallDescription: exports_external.string().describe("A concise, human-readable description of what this tool call is doing")
115531
116026
  }),
115532
116027
  execute: async ({ target, cwd }) => {
115533
- const { runPentestWorkflow: workflow } = await import("./pentest-91bygv3h.js");
116028
+ const { runPentestWorkflow: workflow } = await import("./pentest-1cb5t0dt.js");
115534
116029
  if (!ctx.model) {
115535
116030
  return {
115536
116031
  success: false,
@@ -115548,6 +116043,7 @@ Omit \`cwd\` for blackbox mode (live target probing only).`,
115548
116043
  abortSignal: ctx.abortSignal,
115549
116044
  eventBus: ctx.eventBus,
115550
116045
  enableThinking: ctx.enableThinking,
116046
+ thinkingEffort: ctx.thinkingEffort,
115551
116047
  openAIReasoningEffort: ctx.openAIReasoningEffort,
115552
116048
  surfaceIntegrationEnabled: ctx.surfaceIntegrationEnabled
115553
116049
  });
@@ -115636,11 +116132,11 @@ init_id();
115636
116132
 
115637
116133
  // src/core/whitebox/adapters.ts
115638
116134
  import { existsSync as existsSync12 } from "node:fs";
115639
- import { join as join20 } from "node:path";
116135
+ import { join as join21 } from "node:path";
115640
116136
 
115641
116137
  // src/core/whitebox/artifacts.ts
115642
116138
  import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
115643
- import { basename as basename2, join as join19 } from "node:path";
116139
+ import { basename as basename2, join as join20 } from "node:path";
115644
116140
 
115645
116141
  // src/core/whitebox/paths.ts
115646
116142
  import { existsSync as existsSync11, realpathSync } from "node:fs";
@@ -115701,10 +116197,10 @@ function safeName(value) {
115701
116197
  return value.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/\.{2,}/g, ".").replace(/^-+|-+$/g, "").slice(0, 80);
115702
116198
  }
115703
116199
  function getWhiteboxLogsDir(session) {
115704
- return join19(session.logsPath, "whitebox");
116200
+ return join20(session.logsPath, "whitebox");
115705
116201
  }
115706
116202
  function getWhiteboxScratchDir(session) {
115707
- return join19(session.scratchpadPath, "whitebox");
116203
+ return join20(session.scratchpadPath, "whitebox");
115708
116204
  }
115709
116205
  async function ensureWhiteboxDirs(session) {
115710
116206
  await Promise.all([
@@ -115717,7 +116213,7 @@ async function writeWhiteboxArtifact(input) {
115717
116213
  const baseDir = input.area === "scratchpad" ? getWhiteboxScratchDir(input.session) : getWhiteboxLogsDir(input.session);
115718
116214
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
115719
116215
  const filename = `${timestamp}-${safeName(input.name)}${input.extension ?? ".txt"}`;
115720
- const absolutePath = join19(baseDir, filename);
116216
+ const absolutePath = join20(baseDir, filename);
115721
116217
  await writeFile2(absolutePath, input.content, "utf-8");
115722
116218
  const relativeRoot = input.area === "scratchpad" ? "scratchpad/whitebox" : "logs/whitebox";
115723
116219
  return {
@@ -115959,7 +116455,7 @@ var WHITEBOX_SCAN_ADAPTERS = [
115959
116455
  id: "npm-audit",
115960
116456
  kind: "dependencies",
115961
116457
  tool: "npm",
115962
- detect: (profile) => hasTool(profile, "npm") && (profile.packageManagers.includes("npm") || existsSync12(join20(profile.rootPath, "package-lock.json"))),
116458
+ detect: (profile) => hasTool(profile, "npm") && (profile.packageManagers.includes("npm") || existsSync12(join21(profile.rootPath, "package-lock.json"))),
115963
116459
  buildCommand: () => ["npm", "audit", "--json"],
115964
116460
  parseSummary: (raw) => simpleLineFindings("npm audit", raw)
115965
116461
  },
@@ -116068,7 +116564,7 @@ ${raw.stderr}` : "");
116068
116564
  // src/core/whitebox/candidates.ts
116069
116565
  import { existsSync as existsSync13 } from "node:fs";
116070
116566
  import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
116071
- import { join as join21 } from "node:path";
116567
+ import { join as join22 } from "node:path";
116072
116568
  var CANDIDATES_FILE = "candidates.json";
116073
116569
  var writeLocks = new Map;
116074
116570
  function withCandidateLock(session, fn) {
@@ -116095,10 +116591,10 @@ var ALLOWED_TRANSITIONS = {
116095
116591
  deferred: ["hypothesis", "investigating"]
116096
116592
  };
116097
116593
  function candidatesDir(session) {
116098
- return join21(session.scratchpadPath, "whitebox");
116594
+ return join22(session.scratchpadPath, "whitebox");
116099
116595
  }
116100
116596
  function candidatesPath(session) {
116101
- return join21(candidatesDir(session), CANDIDATES_FILE);
116597
+ return join22(candidatesDir(session), CANDIDATES_FILE);
116102
116598
  }
116103
116599
  function makeId2(title) {
116104
116600
  const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 48);
@@ -116568,7 +117064,7 @@ import {
116568
117064
  readSync as readSync2,
116569
117065
  statSync as statSync4
116570
117066
  } from "node:fs";
116571
- import { join as join22 } from "node:path";
117067
+ import { join as join23 } from "node:path";
116572
117068
  var MAX_JOB_LOG_INLINE = 40000;
116573
117069
  var MAX_JOB_LOG_BYTES = 10 * 1024 * 1024;
116574
117070
  var PRUNE_AFTER_MS = 60000;
@@ -116643,7 +117139,7 @@ function startWhiteboxJob(input) {
116643
117139
  const logsDir = getWhiteboxLogsDir(input.session);
116644
117140
  mkdirSync11(logsDir, { recursive: true });
116645
117141
  const safeName2 = (input.name ?? "job").replace(/[^a-z0-9._-]+/gi, "-").replace(/\.{2,}/g, ".");
116646
- const logPath = join22(logsDir, `${id}-${safeName2}.log`);
117142
+ const logPath = join23(logsDir, `${id}-${safeName2}.log`);
116647
117143
  const now = new Date().toISOString();
116648
117144
  writeLog(logPath, `$ ${input.command}
116649
117145
 
@@ -116772,7 +117268,7 @@ function readWhiteboxJobLog(id, sessionId) {
116772
117268
  import { execFile as execFile2 } from "node:child_process";
116773
117269
  import { existsSync as existsSync15, realpathSync as realpathSync2 } from "node:fs";
116774
117270
  import { readdir as readdir2, readFile as readFile5, stat as stat2 } from "node:fs/promises";
116775
- import { basename as basename3, extname as extname2, join as join23, relative as relative4, resolve as resolve6 } from "node:path";
117271
+ import { basename as basename3, extname as extname2, join as join24, relative as relative4, resolve as resolve6 } from "node:path";
116776
117272
  import { promisify as promisify2 } from "node:util";
116777
117273
  var execFileAsync2 = promisify2(execFile2);
116778
117274
  var MAX_PROFILE_FILES = 5000;
@@ -116881,7 +117377,7 @@ async function walkFiles(rootPath) {
116881
117377
  for (const entry of entries) {
116882
117378
  if (files.length >= MAX_PROFILE_FILES)
116883
117379
  return;
116884
- const fullPath = join23(current, entry.name);
117380
+ const fullPath = join24(current, entry.name);
116885
117381
  if (entry.isDirectory()) {
116886
117382
  if (shouldSkipDir(entry.name))
116887
117383
  continue;
@@ -116943,7 +117439,7 @@ function detectEntryHints(files) {
116943
117439
  return hints.slice(0, 100);
116944
117440
  }
116945
117441
  async function readPackageScripts(rootPath) {
116946
- const packageJsonPath = join23(rootPath, "package.json");
117442
+ const packageJsonPath = join24(rootPath, "package.json");
116947
117443
  if (!existsSync15(packageJsonPath)) {
116948
117444
  return { buildCommands: [], testCommands: [], runCommands: [] };
116949
117445
  }
@@ -117107,7 +117603,7 @@ Returns an array of results with the text output from each agent.`,
117107
117603
  });
117108
117604
  }
117109
117605
  async function runSingleCodingAgent(ctx, codebasePath, objective, agentIndex, name) {
117110
- const { CodeAgent } = await import("./agent-7ab546zf.js");
117606
+ const { CodeAgent } = await import("./agent-3cdfwxcd.js");
117111
117607
  const childSessionId = newSessionId();
117112
117608
  const subagentId = childSessionId;
117113
117609
  ctx.eventBus?.emit("subagent-spawn", {
@@ -117133,7 +117629,9 @@ async function runSingleCodingAgent(ctx, codebasePath, objective, agentIndex, na
117133
117629
  abortSignal: ctx.abortSignal,
117134
117630
  eventBus: localBus,
117135
117631
  subagentId,
117632
+ subagentName: name,
117136
117633
  enableThinking: ctx.enableThinking,
117634
+ thinkingEffort: ctx.thinkingEffort,
117137
117635
  openAIReasoningEffort: ctx.openAIReasoningEffort
117138
117636
  });
117139
117637
  try {
@@ -117164,7 +117662,7 @@ init_ai();
117164
117662
  init_structured();
117165
117663
  init_lazyLogger();
117166
117664
  import { existsSync as existsSync16, readdirSync as readdirSync4, readFileSync as readFileSync6 } from "node:fs";
117167
- import { join as join24 } from "node:path";
117665
+ import { join as join25 } from "node:path";
117168
117666
  var log9 = scopedLogger(() => createLogger("findings:registry"));
117169
117667
  var VULN_CLASS_PATTERNS = [
117170
117668
  [/sql\s*injection/i, "sql-injection"],
@@ -117356,7 +117854,7 @@ class FindingsRegistry {
117356
117854
  log9.debug(`fromDirectory: path=${findingsPath}, jsonFiles=${files.length}`);
117357
117855
  for (const file of files) {
117358
117856
  try {
117359
- const raw = readFileSync6(join24(findingsPath, file), "utf-8");
117857
+ const raw = readFileSync6(join25(findingsPath, file), "utf-8");
117360
117858
  const finding = JSON.parse(raw);
117361
117859
  if (finding && typeof finding.title === "string" && typeof finding.endpoint === "string") {
117362
117860
  registry.indexFinding(finding);
@@ -117561,13 +118059,13 @@ class FindingsRegistry {
117561
118059
 
117562
118060
  // src/core/plan/index.ts
117563
118061
  import { existsSync as existsSync17, readFileSync as readFileSync7 } from "node:fs";
117564
- import { join as join25 } from "node:path";
118062
+ import { join as join26 } from "node:path";
117565
118063
  var PLAN_FILENAME = "plan.md";
117566
118064
  function planFilePath(sessionRootPath, subagentId) {
117567
118065
  if (subagentId) {
117568
- return join25(sessionRootPath, "subagents", `${subagentId}-plan.md`);
118066
+ return join26(sessionRootPath, "subagents", `${subagentId}-plan.md`);
117569
118067
  }
117570
- return join25(sessionRootPath, PLAN_FILENAME);
118068
+ return join26(sessionRootPath, PLAN_FILENAME);
117571
118069
  }
117572
118070
  function readPlan(sessionRootPath, subagentId) {
117573
118071
  const path = planFilePath(sessionRootPath, subagentId);
@@ -117622,7 +118120,7 @@ Returns the worker's summary, objective results, and finding count — but NOT t
117622
118120
  message: "spawn_pentest_agent requires a model in the tool context."
117623
118121
  };
117624
118122
  }
117625
- const { TargetedPentestAgent } = await import("./agent-dsqssedb.js");
118123
+ const { TargetedPentestAgent } = await import("./agent-q5hwjdtb.js");
117626
118124
  const findingsRegistry = ctx.findingsRegistry ?? FindingsRegistry.fromDirectory(ctx.session.findingsPath, {
117627
118125
  model: ctx.model,
117628
118126
  authConfig: ctx.authConfig,
@@ -117652,6 +118150,7 @@ Returns the worker's summary, objective results, and finding count — but NOT t
117652
118150
  try {
117653
118151
  agent = new TargetedPentestAgent({
117654
118152
  target,
118153
+ grpc: workerInheritsGrpc(target, ctx.grpc, ctx.target) ? ctx.grpc : undefined,
117655
118154
  objectives,
117656
118155
  context: enrichedContext,
117657
118156
  model: ctx.model,
@@ -117661,8 +118160,11 @@ Returns the worker's summary, objective results, and finding count — but NOT t
117661
118160
  findingsRegistry,
117662
118161
  eventBus: childBus,
117663
118162
  subagentId,
118163
+ subagentName: name,
117664
118164
  sandbox: ctx.sandbox,
118165
+ secretValues: ctx.secretValues,
117665
118166
  enableThinking: ctx.enableThinking,
118167
+ thinkingEffort: ctx.thinkingEffort,
117666
118168
  openAIReasoningEffort: ctx.openAIReasoningEffort,
117667
118169
  role: "worker",
117668
118170
  browserSession: sharedBrowserSession,
@@ -117703,6 +118205,13 @@ Returns the worker's summary, objective results, and finding count — but NOT t
117703
118205
  }
117704
118206
  });
117705
118207
  }
118208
+ function workerInheritsGrpc(target, grpc, orchestratorTarget) {
118209
+ if (!grpc)
118210
+ return false;
118211
+ if (orchestratorTarget && target === orchestratorTarget)
118212
+ return true;
118213
+ return target.includes(`${grpc.serviceFqn}/${grpc.method}`);
118214
+ }
117706
118215
  function buildSharedBrowserSessionContext(existingContext) {
117707
118216
  const note = `## Shared Browser Session
117708
118217
  You are driving the orchestrator's live Playwright browser (NOT a private copy):
@@ -117739,12 +118248,13 @@ Pass every target you want tested — the swarm handles concurrency automaticall
117739
118248
  targets: exports_external.array(exports_external.object({
117740
118249
  name: exports_external.string().optional().describe("Short human-readable label for this agent shown in the UI (e.g. 'SQLi /api/users', 'IDOR orders')"),
117741
118250
  target: exports_external.string().describe("Full target URL including endpoint to test"),
117742
- objectives: exports_external.array(exports_external.string()).describe("Testing objectives for this target (e.g., 'Test for SQL injection on /api/users', 'Check IDOR on /api/orders/{id}')")
118251
+ objectives: exports_external.array(exports_external.string()).describe("Testing objectives for this target (e.g., 'Test for SQL injection on /api/users', 'Check IDOR on /api/orders/{id}')"),
118252
+ grpc: GrpcPentestContextSchema.optional().describe("gRPC method context for gRPC/Connect targets — pass through the `grpc` object from run_attack_surface so the agent runs the gRPC test battery instead of HTTP tests.")
117743
118253
  })).describe("Array of targets with their testing objectives"),
117744
118254
  toolCallDescription: exports_external.string().describe("A concise, human-readable description of what this tool call is doing")
117745
118255
  }),
117746
118256
  execute: async ({ targets }) => {
117747
- const { runPentestSwarm, DEFAULT_CONCURRENCY: DEFAULT_CONCURRENCY2 } = await import("./pentest-91bygv3h.js");
118257
+ const { runPentestSwarm, DEFAULT_CONCURRENCY: DEFAULT_CONCURRENCY2 } = await import("./pentest-1cb5t0dt.js");
117748
118258
  if (!ctx.model) {
117749
118259
  return {
117750
118260
  success: false,
@@ -117768,6 +118278,7 @@ Pass every target you want tested — the swarm handles concurrency automaticall
117768
118278
  eventBus: ctx.eventBus,
117769
118279
  concurrency: DEFAULT_CONCURRENCY2,
117770
118280
  enableThinking: ctx.enableThinking,
118281
+ thinkingEffort: ctx.thinkingEffort,
117771
118282
  openAIReasoningEffort: ctx.openAIReasoningEffort,
117772
118283
  display: ctx.display
117773
118284
  });
@@ -119428,6 +119939,30 @@ var ALL_TOOL_NAMES = [
119428
119939
  "submit_plan",
119429
119940
  ASK_USER_QUESTIONS_TOOL_NAME
119430
119941
  ];
119942
+ var FAST_STRIKE_EXCLUDED_TOOL_NAMES = [
119943
+ "run_attack_surface",
119944
+ "spawn_pentest_swarm",
119945
+ "spawn_pentest_agent",
119946
+ "spawn_coding_agent",
119947
+ "run_pentest_workflow",
119948
+ "delegate_to_auth_subagent",
119949
+ "run_whitebox_scan",
119950
+ "create_whitebox_candidate",
119951
+ "update_whitebox_candidate",
119952
+ "list_whitebox_candidates",
119953
+ "start_whitebox_job",
119954
+ "poll_whitebox_job",
119955
+ "stop_whitebox_job",
119956
+ "read_whitebox_artifact",
119957
+ "write_plan",
119958
+ "submit_plan",
119959
+ "create_task",
119960
+ "update_task",
119961
+ "list_tasks",
119962
+ "create_attack_surface_report",
119963
+ "provide_comparison_results",
119964
+ ASK_USER_QUESTIONS_TOOL_NAME
119965
+ ];
119431
119966
  var PLAN_MODE_TOOL_NAMES = [
119432
119967
  "browser_navigate",
119433
119968
  "browser_snapshot",
@@ -119677,7 +120212,7 @@ init_dist();
119677
120212
  init_ai();
119678
120213
  import { existsSync as existsSync18, mkdirSync as mkdirSync14, readFileSync as readFileSync8 } from "node:fs";
119679
120214
  import { writeFile as writeFile6 } from "node:fs/promises";
119680
- import { join as join26 } from "node:path";
120215
+ import { join as join27 } from "node:path";
119681
120216
  init_id();
119682
120217
  init_structured();
119683
120218
  init_observability();
@@ -119993,6 +120528,7 @@ class OffensiveSecurityAgent {
119993
120528
  _drainRejection = new Promise(() => {});
119994
120529
  drained = Promise.resolve();
119995
120530
  subagentId;
120531
+ subagentName;
119996
120532
  currentMessageId = null;
119997
120533
  persistentShell;
119998
120534
  browserSession;
@@ -120028,6 +120564,7 @@ class OffensiveSecurityAgent {
120028
120564
  constructor(input) {
120029
120565
  this._session = input.session;
120030
120566
  this.subagentId = input.subagentId;
120567
+ this.subagentName = input.subagentName;
120031
120568
  this.abortSignal = input.abortSignal;
120032
120569
  this.userPrompt = input.prompt;
120033
120570
  this.eventBus = input.eventBus ?? new AgentEventBus;
@@ -120049,23 +120586,33 @@ class OffensiveSecurityAgent {
120049
120586
  extraHttpHeaders: stripBrowserManagedHeaders(sessionHeaders),
120050
120587
  display: input.display
120051
120588
  });
120589
+ if (this.ownsBrowserSession && this.abortSignal) {
120590
+ const onAbort = () => {
120591
+ this.disconnectOwnedBrowser();
120592
+ };
120593
+ if (this.abortSignal.aborted)
120594
+ onAbort();
120595
+ else
120596
+ this.abortSignal.addEventListener("abort", onAbort, { once: true });
120597
+ }
120052
120598
  } else {
120053
120599
  this.ownsBrowserSession = false;
120054
120600
  }
120055
- const messagesDir = input.messagesDir ?? (input.subagentId ? join26(input.session.rootPath, "subagents", input.subagentId) : input.session.rootPath);
120056
- const tracePath = input.subagentId ? join26(input.session.rootPath, "subagents", `${input.subagentId}.trace.jsonl`) : join26(messagesDir, "trace.jsonl");
120601
+ const messagesDir = input.messagesDir ?? (input.subagentId ? join27(input.session.rootPath, "subagents", input.subagentId) : input.session.rootPath);
120602
+ const tracePath = input.subagentId ? join27(input.session.rootPath, "subagents", `${input.subagentId}.trace.jsonl`) : join27(messagesDir, "trace.jsonl");
120057
120603
  const traceWriter = new StepTraceWriter({
120058
120604
  tracePath,
120059
120605
  agentId: input.subagentId ?? null,
120060
120606
  eventBus: this.eventBus
120061
120607
  });
120062
120608
  const taskDriven = input.session.config?.taskDriven ?? false;
120063
- const tasksDir = input.tasksDir ?? (taskDriven ? input.subagentId ? join26(input.session.rootPath, "subagents", `${input.subagentId}-tasks`) : join26(input.session.rootPath, "tasks") : undefined);
120609
+ const tasksDir = input.tasksDir ?? (taskDriven ? input.subagentId ? join27(input.session.rootPath, "subagents", `${input.subagentId}-tasks`) : join27(input.session.rootPath, "tasks") : undefined);
120064
120610
  const credentialManager = input.credentialManager ?? input.session.credentialManager;
120065
120611
  const builtinTools = createAllTools({
120066
120612
  session: input.session,
120067
120613
  agentCwd,
120068
120614
  target: input.target,
120615
+ grpc: input.grpc,
120069
120616
  abortSignal: input.abortSignal,
120070
120617
  model: input.model,
120071
120618
  authConfig: input.authConfig,
@@ -120074,6 +120621,7 @@ class OffensiveSecurityAgent {
120074
120621
  findingsRegistry: input.findingsRegistry,
120075
120622
  attackSurfaceRegistry: input.attackSurfaceRegistry,
120076
120623
  credentialManager,
120624
+ secretValues: input.secretValues,
120077
120625
  persistentShell: this.persistentShell,
120078
120626
  skillsRegistry: input.skillsRegistry,
120079
120627
  promptInjectionLibrary: input.promptInjectionLibrary,
@@ -120081,6 +120629,7 @@ class OffensiveSecurityAgent {
120081
120629
  traceWriter,
120082
120630
  tasksDir,
120083
120631
  enableThinking: input.enableThinking,
120632
+ thinkingEffort: input.thinkingEffort,
120084
120633
  openAIReasoningEffort: input.openAIReasoningEffort,
120085
120634
  surfaceIntegrationEnabled: input.surfaceIntegrationEnabled,
120086
120635
  projectThreatModel: input.projectThreatModel,
@@ -120157,11 +120706,22 @@ class OffensiveSecurityAgent {
120157
120706
  if (input.mode === "plan") {
120158
120707
  const planSet = new Set(PLAN_MODE_TOOL_NAMES);
120159
120708
  activeTools = activeTools.filter((t) => planSet.has(t));
120709
+ } else if (input.mode === "fast-strike") {
120710
+ const excluded = new Set(FAST_STRIKE_EXCLUDED_TOOL_NAMES);
120711
+ activeTools = Object.keys(tools).filter((t) => {
120712
+ if (excluded.has(t))
120713
+ return false;
120714
+ if (!emailToolSet.has(t))
120715
+ return true;
120716
+ if (t === SEND_EMAIL_TOOL_NAME)
120717
+ return hasSmtp;
120718
+ return hasEmail;
120719
+ });
120160
120720
  }
120161
120721
  if (!existsSync18(messagesDir)) {
120162
120722
  mkdirSync14(messagesDir, { recursive: true });
120163
120723
  }
120164
- this.messagesPath = join26(messagesDir, "messages.json");
120724
+ this.messagesPath = join27(messagesDir, "messages.json");
120165
120725
  const messagesPath = this.messagesPath;
120166
120726
  const initialMessagesRef = {
120167
120727
  current: input.messages ? [...input.messages] : [
@@ -120212,7 +120772,7 @@ class OffensiveSecurityAgent {
120212
120772
  activeTools,
120213
120773
  stopWhen,
120214
120774
  toolChoice: "auto",
120215
- sessionPath: input.session.rootPath,
120775
+ sessionPath: messagesDir,
120216
120776
  sessionId: this.busSessionId,
120217
120777
  onStepFinish: async (event) => {
120218
120778
  this.latestMessages = [
@@ -120263,6 +120823,7 @@ class OffensiveSecurityAgent {
120263
120823
  input.onCacheMetrics?.(metrics);
120264
120824
  },
120265
120825
  enableThinking: input.enableThinking,
120826
+ thinkingEffort: input.thinkingEffort,
120266
120827
  openAIReasoningEffort: input.openAIReasoningEffort,
120267
120828
  silent: true
120268
120829
  });
@@ -120287,7 +120848,7 @@ class OffensiveSecurityAgent {
120287
120848
  const runConsume = async () => {
120288
120849
  const toolParts = new Map;
120289
120850
  const ids = {
120290
- subagentId: this.busSessionId,
120851
+ subagentId: this.subagentId,
120291
120852
  sessionId: this.busSessionId,
120292
120853
  messageId: undefined,
120293
120854
  textPartId: undefined,
@@ -120514,10 +121075,11 @@ class OffensiveSecurityAgent {
120514
121075
  }
120515
121076
  return;
120516
121077
  };
120517
- const drain = !sid ? runConsume() : getApexTracer().startActiveSpan(`invoke_agent ${sid}`, {
121078
+ const spanLabel = this.subagentName ?? sid;
121079
+ const drain = !sid ? runConsume() : withSubagentSessionBaggage(sid, () => getApexTracer().startActiveSpan(`invoke_agent ${spanLabel}`, {
120518
121080
  attributes: {
120519
121081
  "gen_ai.operation.name": "invoke_agent",
120520
- "gen_ai.agent.name": sid
121082
+ "gen_ai.agent.name": spanLabel
120521
121083
  }
120522
121084
  }, async (span) => {
120523
121085
  try {
@@ -120528,7 +121090,7 @@ class OffensiveSecurityAgent {
120528
121090
  } finally {
120529
121091
  span.end();
120530
121092
  }
120531
- });
121093
+ }));
120532
121094
  let rejectDrain;
120533
121095
  this._drainRejection = new Promise((_, reject) => {
120534
121096
  rejectDrain = reject;
@@ -120560,6 +121122,10 @@ class OffensiveSecurityAgent {
120560
121122
  this.browserDisconnected = true;
120561
121123
  await this.browserSession.disconnect().catch(() => {});
120562
121124
  }
121125
+ async abortAndDrain() {
121126
+ await this.disconnectOwnedBrowser();
121127
+ await this.drained.catch(() => {});
121128
+ }
120563
121129
  get response() {
120564
121130
  return this.streamResult.response;
120565
121131
  }
@@ -120698,4 +121264,4 @@ function wrapToolsWithApprovalGate(tools, gate, exemptToolNames) {
120698
121264
  return wrapped;
120699
121265
  }
120700
121266
 
120701
- export { AgentEventBus, INTERNAL_ID_PATTERN, ApprovalGate, OPERATOR_MODES, OPERATOR_MODE_CYCLE, createInitialOperatorState, buildProvidedFilesSection, buildSessionWorkspaceSection, buildBaseSystemPrompt, BASE_SYSTEM_PROMPT, isMemoryEnabled, addMemory2 as addMemory, ASK_USER_QUESTIONS_TOOL_NAME, AskUserQuestionSchema, transformScriptToFunction, parseStorageStateResult, setHeadlessMode, setUserAgent, setViewportSize, PlaywrightMcpSession, createBrowserTools, ScopeViolationError, getAllowedHosts, isHostAllowed, extractHostname2 as extractHostname, assertUrlInScope, extractHostsFromCommand, assertCommandInScope, checkSandboxPlaywright, installSandboxPlaywright, ensureSandboxPlaywright, ensureSandboxBrowser, createSandboxBrowserTools, BROWSER_TOOL_NAMES, createBrowserToolset, checkpointState, completeAuthentication, crawlAuthenticated, createAttackSurfaceReport, createFile, createTask2 as createTask, delegateAuth, detectAuthScheme, documentApp, pLimit, documentEndpoint, CweEntrySchema, ValidatedCweEntrySchema, hasCanonicalName, documentVulnerability, createEmailToolset, EMAIL_TOOL_NAMES, SEND_EMAIL_TOOL_NAME, executeCommand, extractJsEndpoints, getMemory2 as getMemory, getPage, grep, httpRequest, listFiles, listMemories2 as listMemories, listTasksTool, readTempfileCapped, getApexTmpRoot, extractFallbackStdout, PersistentShell, probeAuthEndpoints, provideComparisonResults, readFile2 as readFile, readSkill, REPORT_ERROR_TOOL_NAME, ReportErrorReasonSchema, createReportErrorTool, PentestReportedError, RESPONSE_TOOL_NAME, createResponseTool, runAttackSurface, runPentestWorkflow, runWithBoundedConcurrency, spawnCodingAgent, FindingsRegistry, planFilePath, readPlan, hasPlan, spawnPentestAgent, spawnPentestSwarm, submitPlan, testEndpointVariations, updateFile, updateTask2 as updateTask, validateDiscovery, webSearch, writePlan, createAllTools, ALL_TOOL_NAMES, PLAN_MODE_TOOL_NAMES, SKILL_TOOL_NAMES, StepTraceWriter, OffensiveSecurityAgent };
121267
+ export { AgentEventBus, INTERNAL_ID_PATTERN, ApprovalGate, OPERATOR_MODES, OPERATOR_MODE_CYCLE, createInitialOperatorState, buildProvidedFilesSection, buildSessionWorkspaceSection, buildBaseSystemPrompt, BASE_SYSTEM_PROMPT, isMemoryEnabled, addMemory2 as addMemory, ASK_USER_QUESTIONS_TOOL_NAME, AskUserQuestionSchema, transformScriptToFunction, parseStorageStateResult, setHeadlessMode, setUserAgent, setViewportSize, PlaywrightMcpSession, createBrowserTools, ScopeViolationError, getAllowedHosts, isHostAllowed, extractHostname2 as extractHostname, assertUrlInScope, extractHostsFromCommand, assertCommandInScope, checkSandboxPlaywright, installSandboxPlaywright, ensureSandboxPlaywright, ensureSandboxBrowser, createSandboxBrowserTools, BROWSER_TOOL_NAMES, createBrowserToolset, checkpointState, completeAuthentication, crawlAuthenticated, createAttackSurfaceReport, createFile, createTask2 as createTask, delegateAuth, detectAuthScheme, documentApp, EndpointTransportEnum, GrpcEndpointMetadataSchema, toGrpcPentestContext, grpcAuthorityTarget, pLimit, documentEndpoint, CweEntrySchema, ValidatedCweEntrySchema, hasCanonicalName, documentVulnerability, createEmailToolset, EMAIL_TOOL_NAMES, SEND_EMAIL_TOOL_NAME, executeCommand, extractJsEndpoints, getMemory2 as getMemory, getPage, grep, httpRequest, listFiles, listMemories2 as listMemories, listTasksTool, readTempfileCapped, getApexTmpRoot, extractFallbackStdout, PersistentShell, probeAuthEndpoints, provideComparisonResults, readFile2 as readFile, readSkill, REPORT_ERROR_TOOL_NAME, ReportErrorReasonSchema, createReportErrorTool, PentestReportedError, RESPONSE_TOOL_NAME, createResponseTool, runAttackSurface, runPentestWorkflow, runWithBoundedConcurrency, spawnCodingAgent, FindingsRegistry, planFilePath, readPlan, hasPlan, spawnPentestAgent, spawnPentestSwarm, submitPlan, testEndpointVariations, updateFile, updateTask2 as updateTask, validateDiscovery, webSearch, writePlan, createAllTools, ALL_TOOL_NAMES, FAST_STRIKE_EXCLUDED_TOOL_NAMES, PLAN_MODE_TOOL_NAMES, SKILL_TOOL_NAMES, StepTraceWriter, OffensiveSecurityAgent };