@gpzhang2001/sharpkit-proxy 0.2.1 → 0.2.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.ts","names":["Schema"],"sources":["../src/index.d.ts"],"sourcesContent":["/**\n * Caido proxy tools — TS port of strix tools/proxy/tools.py (schemas and\n * output shaping verbatim; the client half lives in client.ts, replay pure\n * helpers in replay.ts). Five tools: list_requests / view_request /\n * repeat_request / list_sitemap / view_sitemap_entry. The client resolves\n * lazily from the sandbox session's Caido bootstrap (concurrent with scan\n * start; the first tool call pays the login wait — strix lazy parity).\n * @module @gpzhang2001/sharpkit-proxy\n */\nimport type { Context } from '@deepseek-ai/cordis';\nimport type Schema from '@deepseek-ai/schemastery';\n/** Deployment-tunable configuration. */\nexport interface Config {\n /** Sandbox session cache key shared with the other tool packages. */\n readonly scanId?: string;\n /** list_requests page size default (strix first=50). */\n readonly listPageSize?: number;\n /** Replay dispatch deadline (strix 30s). */\n readonly replayTimeoutMs?: number;\n /** Client-level per-call timeout (strix @function_tool timeout=60/120). */\n readonly caidoTimeoutMs?: number;\n /** Ask-approval gate on repeat_request (fail-closed without an approval service). */\n readonly requireApproval?: boolean;\n /** Authorized target hosts/values; repeat_request refuses hosts outside this list (exact or dot-suffix subdomain). */\n readonly authorizedTargets?: string[];\n}\nexport declare const name = \"pentest-tool-proxy\";\nexport declare const inject: string[];\nexport declare const Config: Schema<Config>;\n/**\n * Whether a repeat target host is inside the authorized list. Entries may be\n * bare hosts (`example.com`), wildcard subdomains (`*.example.com`), or full\n * URLs (hostname taken); a candidate matches on exact host or a dot-suffix\n * subdomain. An empty list allows everything (fail-open; warned once).\n * @param host - the replay target hostname.\n * @param authorized - the configured target entries.\n */\nexport declare function targetHostAllowed(host: string, authorized: readonly string[]): boolean;\n/**\n * Resolve the replay target hostname: an explicit url modification wins;\n * otherwise the stored request's host.\n */\nexport declare function resolveRepeatTargetHost(storedHost: string, modifications: Readonly<Record<string, unknown>> | undefined): string;\n/** Sitemap page size (strix parity: 30 entries per page). */\nexport declare const SITEMAP_PAGE_SIZE = 30;\n/**\n * Slice one sitemap connection payload to a 1-indexed page of edges (the\n * total count is preserved). Payloads without a recognized connection pass\n * through untouched.\n */\nexport declare function pageSitemapPayload(payload: Record<string, unknown>, page: number): Record<string, unknown>;\n/** strix view_request search hits: ≤20 matches with ±40 chars of context. */\ninterface SearchHit {\n readonly match: string;\n readonly position: number;\n readonly before: string;\n readonly after: string;\n}\n/**\n * Compact regex hits over raw content (strix `_format_search_hits`).\n * @param content - the raw text.\n * @param pattern - the model-supplied regex.\n * @returns the hits payload, or an error entry when the regex is invalid.\n */\nexport declare function formatSearchHits(content: string, pattern: string): {\n readonly hits: SearchHit[];\n readonly totalHits: number;\n readonly error?: string;\n};\n/**\n * Line-paginated raw content page (strix `_format_text_page`).\n * @param content - the raw text.\n * @param page - 1-indexed page number.\n * @param pageSize - lines per page.\n */\nexport declare function formatTextPage(content: string, page: number, pageSize: number): {\n readonly content: string;\n readonly page: number;\n readonly pageSize: number;\n readonly totalLines: number;\n readonly hasMore: boolean;\n};\nexport declare function apply(ctx: Context, config?: Config): void;\nexport {};\n"],"mappings":";;;AAEA,IAAW,CAAC,UAAU;CAAC;OAAS,CAAC;CAAG;EAAC;EAAI;EAAI;EAAI;EAAI;EAAI;CAAE;AAAC;AAC5D,IAAW,CAAC,QAAQ;CAAC;OAAS,CAAC;CAAG,CAAC;AAAC;AACpC,IAAW,CAAC,UAAU;CAAC;OAAS,CAAC;CAAG,CAAC;AAAC;AACtC,IAAW,CAAC,UAAU;CAAC;OAAS,CAAC,QAAQA,CAAM;CAAG,CAAC,IAAI,EAAE;AAAC;AAC1D,IAAW,CAAC,qBAAqB;CAAC;OAAS,CAAC;CAAG,CAAC,IAAI,EAAE;AAAC;AACvD,IAAW,CAAC,2BAA2B;CAAC;OAAS,CAAC,QAAQ,QAAQ;CAAG;EAAC;EAAI;EAAI;EAAI;CAAE;AAAC;AACrF,IAAW,CAAC,qBAAqB;CAAC;OAAS,CAAC;CAAG,CAAC;AAAC;AACjD,IAAW,CAAC,sBAAsB;CAAC;OAAS,CAAC,QAAQ,MAAM;CAAG;EAAC;EAAI;EAAI;EAAI;CAAE;AAAC;AAC9E,IAAI,CAAC,aAAa;CAAC;OAAS,CAAC;CAAG;EAAC;EAAI;EAAI;EAAI;CAAE;AAAC;AAChD,IAAW,CAAC,oBAAoB;CAAC;OAAS,CAAC,SAAS;CAAG;EAAC;EAAI;EAAI;EAAI;EAAI;EAAI;CAAE;AAAC;AAC/E,IAAW,CAAC,kBAAkB;CAAC;OAAU,CAAC;CAAG;EAAC;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;CAAE;AAAC;AAC7E,IAAW,CAAC,SAAS;CAAC;OAAU,CAAC,SAAS,MAAM;CAAG;EAAC;EAAI;EAAI;EAAI;CAAE;AAAC"}
1
+ {"version":3,"file":"index.ts","names":["Schema"],"sources":["../src/index.d.ts"],"sourcesContent":["/**\n * Caido proxy tools — TS port of strix tools/proxy/tools.py (schemas and\n * output shaping verbatim; the client half lives in client.ts, replay pure\n * helpers in replay.ts). Tools: list_requests / view_request /\n * repeat_request / list_sitemap / view_sitemap_entry / scope_rules, plus\n * capture_summary (one-call capture audit so \"is my traffic being recorded\"\n * stays a cheap, verifiable question). The client resolves\n * lazily from the sandbox session's Caido bootstrap (concurrent with scan\n * start; the first tool call pays the login wait — strix lazy parity).\n * @module @gpzhang2001/sharpkit-proxy\n */\nimport type { Context } from '@deepseek-ai/cordis';\nimport type Schema from '@deepseek-ai/schemastery';\n/** Deployment-tunable configuration. */\nexport interface Config {\n /** Sandbox session cache key shared with the other tool packages. */\n readonly scanId?: string;\n /** list_requests page size default (strix first=50). */\n readonly listPageSize?: number;\n /** Replay dispatch deadline (strix 30s). */\n readonly replayTimeoutMs?: number;\n /** Client-level per-call timeout (strix @function_tool timeout=60/120). */\n readonly caidoTimeoutMs?: number;\n /**\n * Ask-approval gate on repeat_request (fail-closed without an approval\n * service). Skipped automatically while the approval policy is `'never'` —\n * an ask there is a guaranteed auto-rejection, not an approval request.\n */\n readonly requireApproval?: boolean;\n /** Authorized target hosts/values; repeat_request refuses hosts outside this list (exact or dot-suffix subdomain). */\n readonly authorizedTargets?: string[];\n}\nexport declare const name = \"pentest-tool-proxy\";\nexport declare const inject: string[];\nexport declare const Config: Schema<Config>;\n/**\n * Whether a repeat target host is inside the authorized list. Entries may be\n * bare hosts (`example.com`), wildcard subdomains (`*.example.com`), or full\n * URLs (hostname taken); a candidate matches on exact host or a dot-suffix\n * subdomain. An empty list allows everything (fail-open; warned once).\n * @param host - the replay target hostname.\n * @param authorized - the configured target entries.\n */\nexport declare function targetHostAllowed(host: string, authorized: readonly string[]): boolean;\n/**\n * Resolve the replay target hostname: an explicit url modification wins;\n * otherwise the stored request's host.\n */\nexport declare function resolveRepeatTargetHost(storedHost: string, modifications: Readonly<Record<string, unknown>> | undefined): string;\n/** Sitemap page size (strix parity: 30 entries per page). */\nexport declare const SITEMAP_PAGE_SIZE = 30;\n/**\n * Slice one sitemap connection payload to a 1-indexed page of edges (the\n * total count is preserved). Payloads without a recognized connection pass\n * through untouched.\n */\nexport declare function pageSitemapPayload(payload: Record<string, unknown>, page: number): Record<string, unknown>;\n/** strix view_request search hits: ≤20 matches with ±40 chars of context. */\ninterface SearchHit {\n readonly match: string;\n readonly position: number;\n readonly before: string;\n readonly after: string;\n}\n/**\n * Compact regex hits over raw content (strix `_format_search_hits`).\n * @param content - the raw text.\n * @param pattern - the model-supplied regex.\n * @returns the hits payload, or an error entry when the regex is invalid.\n */\nexport declare function formatSearchHits(content: string, pattern: string): {\n readonly hits: SearchHit[];\n readonly totalHits: number;\n readonly error?: string;\n};\n/**\n * Line-paginated raw content page (strix `_format_text_page`).\n * @param content - the raw text.\n * @param page - 1-indexed page number.\n * @param pageSize - lines per page.\n */\nexport declare function formatTextPage(content: string, page: number, pageSize: number): {\n readonly content: string;\n readonly page: number;\n readonly pageSize: number;\n readonly totalLines: number;\n readonly hasMore: boolean;\n};\nexport declare function apply(ctx: Context, config?: Config): void;\nexport {};\n"],"mappings":";;;AAEA,IAAW,CAAC,UAAU;CAAC;OAAS,CAAC;CAAG;EAAC;EAAI;EAAI;EAAI;EAAI;EAAI;CAAE;AAAC;AAC5D,IAAW,CAAC,QAAQ;CAAC;OAAS,CAAC;CAAG,CAAC;AAAC;AACpC,IAAW,CAAC,UAAU;CAAC;OAAS,CAAC;CAAG,CAAC;AAAC;AACtC,IAAW,CAAC,UAAU;CAAC;OAAS,CAAC,QAAQA,CAAM;CAAG,CAAC,IAAI,EAAE;AAAC;AAC1D,IAAW,CAAC,qBAAqB;CAAC;OAAS,CAAC;CAAG,CAAC,IAAI,EAAE;AAAC;AACvD,IAAW,CAAC,2BAA2B;CAAC;OAAS,CAAC,QAAQ,QAAQ;CAAG;EAAC;EAAI;EAAI;EAAI;CAAE;AAAC;AACrF,IAAW,CAAC,qBAAqB;CAAC;OAAS,CAAC;CAAG,CAAC;AAAC;AACjD,IAAW,CAAC,sBAAsB;CAAC;OAAS,CAAC,QAAQ,MAAM;CAAG;EAAC;EAAI;EAAI;EAAI;CAAE;AAAC;AAC9E,IAAI,CAAC,aAAa;CAAC;OAAS,CAAC;CAAG;EAAC;EAAI;EAAI;EAAI;CAAE;AAAC;AAChD,IAAW,CAAC,oBAAoB;CAAC;OAAS,CAAC,SAAS;CAAG;EAAC;EAAI;EAAI;EAAI;EAAI;EAAI;CAAE;AAAC;AAC/E,IAAW,CAAC,kBAAkB;CAAC;OAAU,CAAC;CAAG;EAAC;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;CAAE;AAAC;AAC7E,IAAW,CAAC,SAAS;CAAC;OAAU,CAAC,SAAS,MAAM;CAAG;EAAC;EAAI;EAAI;EAAI;CAAE;AAAC"}
package/lib/index.js CHANGED
@@ -221,6 +221,25 @@ const REQUEST_DOC = `query Request($id: ID!, $includeRequestRaw: Boolean!, $incl
221
221
  const CREATE_REPLAY_SESSION_DOC = `mutation CreateReplaySession($input: CreateReplaySessionInput!) {
222
222
  createReplaySession(input: $input) { session { id } error { __typename } }
223
223
  }`;
224
+ /** Capture audit: exact total + newest entry (count aggregate, single edge). */
225
+ const CAPTURE_SUMMARY_DOC = `query CaptureSummary($filter: HTTPQLInput, $scopeId: ID) {
226
+ requests(first: 1, filter: $filter, scopeId: $scopeId, order: {by: CREATED_AT, ordering: DESC}) {
227
+ count { value }
228
+ edges { node { host port createdAt } }
229
+ }
230
+ }`;
231
+ /** Capture audit: oldest timestamp (asc boundary of the same connection). */
232
+ const CAPTURE_OLDEST_DOC = `query CaptureOldest($filter: HTTPQLInput, $scopeId: ID) {
233
+ requests(first: 1, filter: $filter, scopeId: $scopeId, order: {by: CREATED_AT, ordering: ASC}) {
234
+ edges { node { createdAt } }
235
+ }
236
+ }`;
237
+ /** Capture audit: per-domain request counts from the sitemap roots. */
238
+ const CAPTURE_HOSTS_DOC = `query CaptureHosts {
239
+ sitemapRootEntries {
240
+ edges { node { label requests(first: 1) { count { value } } } }
241
+ }
242
+ }`;
224
243
  /** Replay dispatch (strix `replay_send_raw` via the SDK's StartReplayTask). */
225
244
  const START_REPLAY_TASK_DOC = `mutation StartReplayTask($sessionId: ID!, $input: StartReplayTaskInput!) {
226
245
  startReplayTask(sessionId: $sessionId, input: $input) { error { __typename } task { id replayEntry { id } } }
@@ -353,6 +372,42 @@ var CaidoClient = class {
353
372
  }
354
373
  };
355
374
  }
375
+ /**
376
+ * Capture audit in one call: exact total request count (Caido connection
377
+ * `count` aggregate), newest entry, oldest timestamp, and per-domain
378
+ * counts from the sitemap roots. Used by the capture_summary tool to make
379
+ * "is my traffic actually being captured" a cheap, verifiable question.
380
+ * `since` (ISO timestamp) bounds the audit window via HTTPQL
381
+ * `req.created_at.gt` — the sandbox Caido container is shared across scans
382
+ * of one host process, so a fresh scan records the Phase-0 probe's
383
+ * capture_summary as its baseline and diffs against it.
384
+ */
385
+ async captureSummary(signal, scopeId, since) {
386
+ const vars = {
387
+ ...scopeId !== void 0 && scopeId !== "" ? { scopeId } : {},
388
+ ...since !== void 0 && since !== "" ? { filter: { code: `req.created_at.gt:"${since}"` } } : {}
389
+ };
390
+ const [summary, oldest, hosts] = await Promise.all([
391
+ graphql(this.options, CAPTURE_SUMMARY_DOC, vars, signal),
392
+ graphql(this.options, CAPTURE_OLDEST_DOC, vars, signal),
393
+ graphql(this.options, CAPTURE_HOSTS_DOC, {}, signal)
394
+ ]);
395
+ const iso = (value) => value === null || value === void 0 ? null : new Date(Number(value)).toISOString();
396
+ const edge = summary.requests.edges[0]?.node;
397
+ return {
398
+ total: Number(summary.requests.count?.value ?? 0),
399
+ newest: edge === void 0 ? null : {
400
+ host: String(edge.host),
401
+ port: Number(edge.port),
402
+ createdAt: iso(edge.createdAt) ?? ""
403
+ },
404
+ oldestAt: iso(oldest.requests.edges[0]?.node.createdAt),
405
+ hosts: hosts.sitemapRootEntries.edges.map((root) => ({
406
+ host: String(root.node.label),
407
+ requests: Number(root.node.requests?.count?.value ?? 0)
408
+ })).filter((host) => host.requests > 0).sort((a, b) => b.requests - a.requests)
409
+ };
410
+ }
356
411
  /** Fetch one request with both raw halves (strix get_request_with_client parity: always request both). */
357
412
  async getRequest(requestId, signal) {
358
413
  const request = (await graphql(this.options, REQUEST_DOC, {
@@ -496,6 +551,22 @@ var CaidoClient = class {
496
551
  };
497
552
  //#endregion
498
553
  //#region src/index.ts
554
+ /**
555
+ * Whether the session's approval policy is `'never'` — the deterministic
556
+ * stance where every ask resolves `rejected` without consulting anyone (set
557
+ * by the danger-full-access preset, i.e. a user who pre-trusted the
558
+ * session). An ask under that policy can never be granted — and its denial
559
+ * is even reported as "the user rejected tool" although no user was
560
+ * consulted — so the gate treats it as pre-approval instead of asking:
561
+ * replay targets stay confined to the authorizedTargets allowlist either
562
+ * way.
563
+ */
564
+ function policyIsNever(ctx, session) {
565
+ if (session === void 0) return false;
566
+ const approval = ctx.get("approval");
567
+ if (approval === void 0) return false;
568
+ return (approval.overrideOf(session) ?? approval.config?.policy ?? "ask") === "never";
569
+ }
499
570
  const name = "pentest-tool-proxy";
500
571
  const inject = ["tools", "pentestSandbox"];
501
572
  const Config = z.object({
@@ -680,12 +751,113 @@ function apply(ctx, config = {}) {
680
751
  return Promise.race([settled, timeout]);
681
752
  };
682
753
  if (config.requireApproval !== false) ctx.on("tools/pre-execute", async (exec, next) => {
683
- if (exec.name === "repeat_request") return {
684
- kind: "ask",
685
- reason: "pentest: replay a captured request against the target"
686
- };
754
+ if (exec.name === "repeat_request") {
755
+ if (policyIsNever(ctx, exec.agent?.session)) return next();
756
+ return {
757
+ kind: "ask",
758
+ reason: "pentest: replay a captured request against the target"
759
+ };
760
+ }
687
761
  return next();
688
762
  });
763
+ ctx.tools.register(defineTool({
764
+ name: "capture_summary",
765
+ description: "One-call audit of the Caido proxy capture history: exact total request count, newest and oldest timestamps, and per-host request counts. Use it to VERIFY traffic capture — after a probe request in Phase 0, when you suspect requests bypassed the proxy, and before finish_scan reconciliation. total=0 means nothing was captured: report it as a blocked capability line instead of assuming your traffic was recorded. The sandbox Caido container is shared across scans of one host process, so history accumulates: pass the Phase-0 probe's ISO timestamp as `since` to audit only this scan's traffic (the baseline diff), or read the unfiltered count as the whole-container trail.",
766
+ parameters: { since: {
767
+ type: "string",
768
+ description: "Optional ISO timestamp: count only requests captured after it (e.g. the Phase-0 probe time, making the result this scan's traffic)."
769
+ } },
770
+ output: {
771
+ schema: {
772
+ type: "object",
773
+ properties: {
774
+ success: {
775
+ type: "boolean",
776
+ required: true
777
+ },
778
+ total: {
779
+ type: "integer",
780
+ description: "Total captured requests in the proxy history."
781
+ },
782
+ newest: {
783
+ type: "object",
784
+ properties: {
785
+ host: { type: "string" },
786
+ port: { type: "integer" },
787
+ created_at: {
788
+ type: "string",
789
+ description: "ISO timestamp."
790
+ }
791
+ },
792
+ additionalProperties: false,
793
+ description: "Most recent captured request (null-ish object fields omitted when history is empty)."
794
+ },
795
+ oldest_at: {
796
+ type: "string",
797
+ description: "ISO timestamp of the oldest captured request, when any."
798
+ },
799
+ hosts: {
800
+ type: "array",
801
+ items: {
802
+ type: "object",
803
+ properties: {
804
+ host: { type: "string" },
805
+ requests: { type: "integer" }
806
+ },
807
+ additionalProperties: false
808
+ },
809
+ description: "Per-host captured request counts (sitemap roots), descending."
810
+ },
811
+ error: { type: "string" }
812
+ },
813
+ additionalProperties: false
814
+ },
815
+ render: (_args, value) => {
816
+ const result = value;
817
+ if (!result.success) return [{
818
+ type: "text",
819
+ text: `capture_summary failed: ${result.error ?? "unknown"}`
820
+ }];
821
+ if ((result.total ?? 0) === 0) return [{
822
+ type: "text",
823
+ text: "capture history is EMPTY: 0 requests captured. If you sent HTTP traffic, it bypassed the proxy — treat proxy capture as blocked and report it."
824
+ }];
825
+ const newest = result.newest;
826
+ const newestText = newest !== void 0 && newest !== null ? `${newest.host}:${String(newest.port)}` : "unknown";
827
+ const hostsText = (result.hosts ?? []).map((host) => `${host.host}=${String(host.requests)}`).join(", ");
828
+ return [{
829
+ type: "text",
830
+ text: `capture active: ${String(result.total)} requests captured (newest ${newestText}, oldest ${result.oldest_at ?? "unknown"}; hosts: ${hostsText || "unknown"})`
831
+ }];
832
+ }
833
+ },
834
+ execute: async (args, exec) => {
835
+ try {
836
+ const caido = await client(exec.signal);
837
+ const since = typeof args.since === "string" && args.since.trim() !== "" ? args.since.trim() : void 0;
838
+ const summary = await caido.captureSummary(exec.signal, void 0, since);
839
+ return {
840
+ success: true,
841
+ total: summary.total,
842
+ ...summary.newest !== null ? { newest: {
843
+ host: summary.newest.host,
844
+ port: summary.newest.port,
845
+ created_at: summary.newest.createdAt
846
+ } } : {},
847
+ ...summary.oldestAt !== null ? { oldest_at: summary.oldestAt } : {},
848
+ hosts: summary.hosts.map((host) => ({
849
+ host: host.host,
850
+ requests: host.requests
851
+ }))
852
+ };
853
+ } catch (error) {
854
+ return {
855
+ success: false,
856
+ error: error instanceof Error ? error.message : String(error)
857
+ };
858
+ }
859
+ }
860
+ }));
689
861
  ctx.tools.register(defineTool({
690
862
  name: "list_requests",
691
863
  description: `List captured HTTP requests from the Caido proxy with HTTPQL filtering. HTTPQL: integer fields (resp.code, req.port, id, roundtrip) use eq/gt/gte/lt/lte/ne e.g. 'resp.code.gte:400'; text fields (req.method, req.host, req.path, req.query, req.ext, req.raw) use regex/cont/eq e.g. 'req.path.cont:"/api/"'; dates use gt/lt with ISO e.g. 'req.created_at.gt:"2024-01-01T00:00:00Z"'; combine with AND/OR; no NOT — use ne/ncont/nregex. String values MUST be quoted, integers MUST NOT. A bare quoted string searches req.raw and resp.raw. Pagination: pass page_info.end_cursor as after.`,
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["Buffer"],"sources":["../src/replay.ts","../src/client.ts","../src/index.ts"],"sourcesContent":["/**\n * Pure HTTP replay helpers, ported from strix tools/proxy/caido_api.py:\n * raw request parsing/rebuilding with framing-header hygiene (:173-218),\n * response parsing (:220-258), request-line parsing (:260-279), URL\n * composition (:281-292), and field modifications (:294-331). Total pure\n * functions — exhaustively unit-testable.\n * @module @gpzhang2001/sharpkit-proxy/replay\n */\n\n/** Parsed components of a raw HTTP request text. */\nexport interface RawRequestComponents {\n readonly method: string\n readonly urlPath: string\n readonly headers: Record<string, string>\n readonly body: string\n}\n\n/** A connection target for Caido's replay dispatch. */\nexport interface ConnectionInfo {\n readonly host: string\n readonly port: number\n readonly tls: boolean\n readonly sni?: string\n}\n\n/** Parsed summary of a raw HTTP response (strix list_requests shape). */\nexport interface RawResponseParts {\n readonly statusCode: number\n readonly length: number\n readonly headers: Record<string, string>\n readonly body: string\n readonly bodyTruncated: boolean\n}\n\n/** strix clips replay response bodies at 8192 chars (caido_api.py:222). */\nconst RESPONSE_BODY_MAX_CHARS = 8192\n\n/** Framing headers that must never survive into a modified replay (strix :175). */\nconst FRAMING_HEADERS = new Set(['content-length', 'transfer-encoding'])\n\n/**\n * Parse a raw HTTP request text into components (strix `parse_raw_request`).\n * @param rawContent - the raw request text.\n * @returns the parsed components.\n * @throws when the request line is malformed.\n */\nexport function parseRawRequest(rawContent: string): RawRequestComponents {\n const lines = rawContent.split('\\n')\n const requestLine = (lines[0] ?? '').trim().split(' ')\n if (requestLine.length < 2) throw new Error('Invalid request line format')\n const headers: Record<string, string> = {}\n let bodyStart = 0\n for (let index = 1; index < lines.length; index++) {\n const line = lines[index] ?? ''\n if (line.trim() === '') {\n bodyStart = index + 1\n break\n }\n const separator = line.indexOf(':')\n if (separator !== -1) headers[line.slice(0, separator).trim()] = line.slice(separator + 1).trim()\n }\n const body = bodyStart < lines.length ? lines.slice(bodyStart).join('\\n').trim() : ''\n return { method: requestLine[0] ?? '', urlPath: requestLine[1] ?? '', headers, body }\n}\n\n/**\n * Compose the full URL from the original request's connection facts, the\n * parsed components, and an optional explicit url override (strix\n * `full_url_from_components`).\n * @param original - the stored request's host/tls facts.\n * @param components - parsed raw request components.\n * @param modifications - the patch dict.\n * @returns the absolute target URL.\n */\nexport function fullUrlFromComponents(\n original: { readonly host: string; readonly tls: boolean },\n components: RawRequestComponents,\n modifications: Readonly<Record<string, unknown>>,\n): string {\n const override = modifications['url']\n if (typeof override === 'string' && override !== '') return override\n const hostHeader = components.headers['Host'] ?? original.host\n const scheme = original.tls ? 'https' : 'http'\n return `${scheme}://${hostHeader}${components.urlPath}`\n}\n\n/** Parse a query string into a first-value map (Node parity of parse_qs). */\nfunction parseQuery(query: string): Record<string, string> {\n const params: Record<string, string> = {}\n for (const pair of query.split('&')) {\n if (pair === '') continue\n const eq = pair.indexOf('=')\n const key = eq === -1 ? pair : pair.slice(0, eq)\n const value = eq === -1 ? '' : pair.slice(eq + 1)\n params[decodeURIComponent(key)] = decodeURIComponent(value)\n }\n return params\n}\n\n/** Serialize a query map back to a string (Node parity of urlencode). */\nfunction encodeQuery(params: Readonly<Record<string, string>>): string {\n return Object.entries(params)\n .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)\n .join('&')\n}\n\n/**\n * Overlay the patch dict onto parsed components (strix `apply_modifications`):\n * params merge into the query string, headers/cookies merge, body replaces.\n * @param components - parsed raw request components.\n * @param modifications - the patch dict (url/params/headers/body/cookies).\n * @param fullUrl - the composed final URL.\n * @returns the modified components keyed like strix.\n */\nexport function applyModifications(\n components: RawRequestComponents,\n modifications: Readonly<Record<string, unknown>>,\n fullUrl: string,\n): { readonly method: string; readonly url: string; readonly headers: Record<string, string>; readonly body: string } {\n const headers = { ...components.headers }\n let body = components.body\n let finalUrl = fullUrl\n\n const params = modifications['params']\n if (params !== undefined && params !== null && typeof params === 'object') {\n const question = finalUrl.indexOf('?')\n const existing = question === -1 ? {} : parseQuery(finalUrl.slice(question + 1))\n for (const [key, value] of Object.entries(params as Record<string, unknown>)) {\n existing[key] = String(value)\n }\n const base = question === -1 ? finalUrl : finalUrl.slice(0, question)\n finalUrl = `${base}?${encodeQuery(existing)}`\n }\n const headerPatch = modifications['headers']\n if (headerPatch !== undefined && headerPatch !== null && typeof headerPatch === 'object') {\n for (const [key, value] of Object.entries(headerPatch as Record<string, unknown>)) {\n headers[key] = String(value)\n }\n }\n const bodyPatch = modifications['body']\n if (typeof bodyPatch === 'string') body = bodyPatch\n const cookiePatch = modifications['cookies']\n if (cookiePatch !== undefined && cookiePatch !== null && typeof cookiePatch === 'object') {\n const cookies: Record<string, string> = {}\n const existingCookie = headers['Cookie']\n if (existingCookie !== undefined) {\n for (const cookie of existingCookie.split(';')) {\n const eq = cookie.indexOf('=')\n if (eq !== -1) cookies[cookie.slice(0, eq).trim()] = cookie.slice(eq + 1).trim()\n }\n }\n for (const [key, value] of Object.entries(cookiePatch as Record<string, unknown>)) {\n cookies[key] = String(value)\n }\n headers['Cookie'] = Object.entries(cookies).map(([key, value]) => `${key}=${value}`).join('; ')\n }\n\n return { method: components.method, url: finalUrl, headers, body }\n}\n\n/**\n * Rebuild a raw HTTP/1.1 request with connection facts (strix\n * `build_raw_request`): Host/UA defaults, framing headers dropped,\n * Content-Length recomputed from the actual body.\n * @param parts - method/url/headers/body of the replay.\n * @returns the connection target and encoded raw request bytes.\n */\nexport function buildRawRequest(parts: {\n readonly method: string\n readonly url: string\n readonly headers: Record<string, string>\n readonly body: string\n}): { readonly connection: ConnectionInfo; readonly raw: Uint8Array } {\n let parsed: URL\n try {\n parsed = new URL(parts.url)\n } catch {\n throw new Error(`Invalid URL: ${parts.url}`)\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') throw new Error(`Invalid URL: ${parts.url}`)\n const tls = parsed.protocol === 'https:'\n const port = parsed.port !== '' ? Number(parsed.port) : tls ? 443 : 80\n const path = `${parsed.pathname}${parsed.search}`\n\n const headers: Record<string, string> = { ...parts.headers }\n if (headers['Host'] === undefined) headers['Host'] = parsed.host\n if (headers['User-Agent'] === undefined) {\n headers['User-Agent'] = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36'\n }\n for (const key of Object.keys(headers)) {\n if (FRAMING_HEADERS.has(key.toLowerCase())) delete headers[key]\n }\n if (parts.body !== '') headers['Content-Length'] = String(Buffer.byteLength(parts.body, 'utf8'))\n\n const lines = [`${parts.method.toUpperCase()} ${path} HTTP/1.1`]\n for (const [key, value] of Object.entries(headers)) lines.push(`${key}: ${value}`)\n const raw = Buffer.from(`${lines.join('\\r\\n')}\\r\\n\\r\\n${parts.body}`, 'utf8')\n return { connection: { host: parsed.hostname, port, tls }, raw }\n}\n\n/**\n * Parse a raw HTTP response into the list_requests summary shape (strix\n * `parse_raw_response`); null when missing/unparseable. Body clipped at 8192\n * chars with a truncation flag.\n * @param rawBytes - the raw response bytes.\n * @returns the parsed parts, or null.\n */\nexport function parseRawResponse(rawBytes: Uint8Array | null | undefined): RawResponseParts | null {\n if (rawBytes === null || rawBytes === undefined || rawBytes.byteLength === 0) return null\n const text = Buffer.from(rawBytes).toString('latin1')\n const separator = text.indexOf('\\r\\n\\r\\n')\n if (separator === -1) return null\n const head = text.slice(0, separator)\n const lines = head.split('\\r\\n')\n const statusParts = (lines[0] ?? '').split(' ')\n if (statusParts.length < 2 || !/^\\d+$/.test(statusParts[1] ?? '')) return null\n const headers: Record<string, string> = {}\n for (const line of lines.slice(1)) {\n const colon = line.indexOf(':')\n if (colon === -1) continue\n headers[line.slice(0, colon).trim()] = line.slice(colon + 1).trim()\n }\n const bodyBytes = Buffer.from(text.slice(separator + 4), 'latin1')\n let body = bodyBytes.toString('utf8')\n const truncated = body.length > RESPONSE_BODY_MAX_CHARS\n if (truncated) body = body.slice(0, RESPONSE_BODY_MAX_CHARS)\n return { statusCode: Number(statusParts[1]), length: bodyBytes.byteLength, headers, body, bodyTruncated: truncated }\n}\n","/**\n * Caido HTTP/GraphQL client — TS port of the CLIENT half of strix\n * tools/proxy/caido_api.py against the HOST-side published endpoint (global\n * fetch + Bearer token from the sandbox session's bootstrap). Documents are\n * trimmed, schema-valid subsets of the caido_sdk_client generated operations\n * (Requests/Request/ReplayEntry/StartReplayTask/sitemap trio). The replay\n * flow adapts the SDK's subscription wait into bounded polling of the entry\n * (finished when a response or an error appears; 30s strix dispatch budget).\n * @module @gpzhang2001/sharpkit-proxy/client\n */\n\nimport { Buffer } from 'node:buffer'\nimport { buildRawRequest, fullUrlFromComponents, applyModifications, parseRawRequest, parseRawResponse, type RawResponseParts } from './replay.ts'\n\n/** Which half of a captured exchange to surface (strix RequestPart). */\nexport type RequestPart = 'request' | 'response'\n\n/** Sort keys accepted by list_requests (strix SortBy). */\nexport type SortBy = 'timestamp' | 'host' | 'method' | 'path' | 'status_code' | 'response_time' | 'response_size' | 'source'\n\n/** strix `_REQ_FIELD_MAP` resolved to Caido's RequestResponseOrderBy enums. */\nconst SORT_ENUMS: Record<SortBy, string> = {\n timestamp: 'CREATED_AT',\n host: 'HOST',\n method: 'METHOD',\n path: 'PATH',\n source: 'SOURCE',\n status_code: 'RESP_STATUS_CODE',\n response_time: 'RESP_ROUNDTRIP_TIME',\n response_size: 'RESP_LENGTH',\n}\n\n/** The one Caido port the sandbox publishes (protocol constant). */\nexport const CAIDO_PORT = 48080\n\n/** Compact Requests query (subset of the SDK's generated document). */\nconst REQUESTS_DOC = `query Requests($first: Int, $after: String, $filter: HTTPQLInput, $order: RequestResponseOrderInput, $scopeId: ID, $includeRequestRaw: Boolean!, $includeResponseRaw: Boolean!) {\n requests(first: $first, after: $after, filter: $filter, order: $order, scopeId: $scopeId) {\n edges { cursor node {\n id host port method path query isTls createdAt\n raw @include(if: $includeRequestRaw)\n response { id statusCode roundtripTime length createdAt raw @include(if: $includeResponseRaw) }\n } }\n pageInfo { hasNextPage hasPreviousPage startCursor endCursor }\n }\n}`\n\n/** Compact Request query (both raws always requested — SDK parity note). */\nconst REQUEST_DOC = `query Request($id: ID!, $includeRequestRaw: Boolean!, $includeResponseRaw: Boolean!) {\n request(id: $id) {\n id host port method path query isTls createdAt\n raw @include(if: $includeRequestRaw)\n response { id statusCode roundtripTime length createdAt raw @include(if: $includeResponseRaw) }\n }\n}`\n\n/** Empty replay session create (avoids the double history row the raw-create seeds). */\nconst CREATE_REPLAY_SESSION_DOC = `mutation CreateReplaySession($input: CreateReplaySessionInput!) {\n createReplaySession(input: $input) { session { id } error { __typename } }\n}`\n\n/** Replay dispatch (strix `replay_send_raw` via the SDK's StartReplayTask). */\nconst START_REPLAY_TASK_DOC = `mutation StartReplayTask($sessionId: ID!, $input: StartReplayTaskInput!) {\n startReplayTask(sessionId: $sessionId, input: $input) { error { __typename } task { id replayEntry { id } } }\n}`\n\n/** Replay entry poll — finished when request.response or error appears. */\nconst REPLAY_ENTRY_DOC = `query ReplayEntry($id: ID!, $includeReplayRaw: Boolean!, $includeRequestRaw: Boolean!, $includeResponseRaw: Boolean!) {\n replayEntry(id: $id) {\n id error\n request { id method path response { id statusCode length roundtripTime raw @include(if: $includeResponseRaw) } }\n }\n}`\n\n/** Scope management (strix caido_api.py scope_* via the SDK's ScopeFull set). */\nconst SCOPES_DOC = `query Scopes { scopes { id name allowlist denylist indexed } }`\n\nconst SCOPE_DOC = `query Scope($id: ID!) { scope(id: $id) { id name allowlist denylist indexed } }`\n\nconst CREATE_SCOPE_DOC = `mutation CreateScope($input: CreateScopeInput!) { createScope(input: $input) { error { __typename } scope { id name allowlist denylist indexed } } }`\n\nconst UPDATE_SCOPE_DOC = `mutation UpdateScope($id: ID!, $input: UpdateScopeInput!) { updateScope(id: $id, input: $input) { error { __typename } scope { id name allowlist denylist indexed } } }`\n\nconst DELETE_SCOPE_DOC = `mutation DeleteScope($id: ID!) { deleteScope(id: $id) { deletedId } }`\n\n/** Sitemap queries (verbatim field sets from strix caido_api.py:555-592). */\nconst SITEMAP_ROOTS_DOC = `query GetSitemapRoots($scopeId: ID) {\n sitemapRootEntries(scopeId: $scopeId) {\n edges { node {\n id kind label hasDescendants\n metadata { ... on SitemapEntryMetadataDomain { isTls port } }\n request { method path response { statusCode } }\n } }\n count { value }\n }\n}`\n\nconst SITEMAP_DESCENDANTS_DOC = `query GetSitemapDescendants($parentId: ID!, $depth: SitemapDescendantsDepth!) {\n sitemapDescendantEntries(parentId: $parentId, depth: $depth) {\n edges { node {\n id kind label hasDescendants\n request { method path response { statusCode } }\n } }\n count { value }\n }\n}`\n\nconst SITEMAP_ENTRY_DOC = `query GetSitemapEntry($id: ID!) {\n sitemapEntry(id: $id) {\n id kind label hasDescendants\n metadata { ... on SitemapEntryMetadataDomain { isTls port } }\n request { method path response { statusCode length roundtripTime } }\n requests(first: 30, order: {by: CREATED_AT, ordering: DESC}) {\n edges { node { method path response { statusCode length } } }\n count { value }\n }\n }\n}`\n\n/** Fetch contract (global fetch shape) for the GraphQL endpoint. */\nexport interface CaidoFetchFn {\n (url: string, init: { readonly method: 'POST'; readonly headers: Readonly<Record<string, string>>; readonly body: string; readonly signal: AbortSignal }): Promise<{ readonly status: number; readonly text: () => Promise<string> }>\n}\n\n/** One captured request entry projected for the model (strix shape). */\nexport interface RequestListEntry {\n readonly cursor: string\n readonly request: {\n readonly id: string\n readonly host: string\n readonly port: number\n readonly method: string\n readonly path: string\n readonly query: string | null\n readonly tls: boolean\n readonly createdAt: string\n }\n readonly response: { readonly id: string; readonly statusCode: number | null; readonly length: number | null; readonly createdAt: string | null } | null\n}\n\n/** PageInfo projection (strix page_info). */\nexport interface CaidoPageInfo {\n readonly hasNextPage: boolean\n readonly hasPreviousPage: boolean\n readonly startCursor: string | null\n readonly endCursor: string | null\n}\n\n/** The raw halves of one stored request. */\nexport interface StoredRequest {\n readonly id: string\n readonly host: string\n readonly port: number\n readonly method: string\n readonly tls: boolean\n readonly requestRaw: string | null\n readonly responseRaw: string | null\n}\n\n/** Replay outcome (strix `_format_replay_tool_result` inputs). */\nexport interface ReplayOutcome {\n readonly sessionId: string\n readonly status: string\n readonly elapsedMs: number\n readonly error?: string\n readonly response: RawResponseParts | null\n}\n\n/** Endpoint coordinates handed over by the sandbox session's bootstrap. */\nexport interface CaidoClientOptions {\n readonly baseUrl: string\n readonly token: string\n readonly fetchFn?: CaidoFetchFn\n}\n\n/** Minimal GraphQL POST with bearer auth and error normalization. */\nasync function graphql<T>(\n options: CaidoClientOptions,\n doc: string,\n variables: Record<string, unknown>,\n signal: AbortSignal,\n): Promise<T> {\n const fetchFn = options.fetchFn ?? fetch\n const response = await fetchFn(`${options.baseUrl}/graphql`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${options.token}` },\n body: JSON.stringify({ query: doc, variables }),\n signal,\n })\n const text = await response.text()\n if (response.status !== 200) throw new Error(`caido graphql HTTP ${response.status}: ${text.slice(0, 200)}`)\n let payload: unknown\n try {\n payload = JSON.parse(text)\n } catch (error) {\n throw new Error(`caido graphql unparseable response: ${String(error)}`)\n }\n const record = payload as { readonly data?: unknown; readonly errors?: unknown }\n if (record.errors !== undefined && record.errors !== null) {\n throw new Error(`caido graphql errors: ${JSON.stringify(record.errors).slice(0, 300)}`)\n }\n if (typeof record.data !== 'object' || record.data === null) {\n throw new Error('caido graphql carried no data')\n }\n return record.data as T\n}\n\n/** Project one raw requests-connection edge (strix list_requests mapping). */\nfunction projectEdge(edge: unknown): RequestListEntry {\n const node = (edge as { node: Record<string, unknown> }).node\n const response = node.response as Record<string, unknown> | null\n return {\n cursor: String((edge as { cursor: unknown }).cursor),\n request: {\n id: String(node.id),\n host: String(node.host),\n port: Number(node.port),\n method: String(node.method),\n path: String(node.path),\n query: node.query === null || node.query === undefined ? null : String(node.query),\n tls: node.isTls === true,\n createdAt: new Date(Number(node.createdAt)).toISOString(),\n },\n response: response === null || response === undefined ? null : {\n id: String(response.id),\n statusCode: response.statusCode === null || response.statusCode === undefined ? null : Number(response.statusCode),\n length: response.length === null || response.length === undefined ? null : Number(response.length),\n createdAt: response.createdAt === null || response.createdAt === undefined ? null : new Date(Number(response.createdAt)).toISOString(),\n },\n }\n}\n\n/**\n * The Caido client: five operations behind the five proxy tools. All methods\n * take an AbortSignal (tool exec.signal parity).\n */\nexport class CaidoClient {\n private readonly options: CaidoClientOptions\n\n constructor(options: CaidoClientOptions) {\n this.options = options\n }\n\n /** List captured requests with HTTPQL filter/cursor/sort/scope (strix list_requests_with_client). */\n async listRequests(options: {\n readonly httpqlFilter?: string | undefined\n readonly first?: number | undefined\n readonly after?: string | undefined\n readonly sortBy?: SortBy | undefined\n readonly sortOrder?: 'asc' | 'desc' | undefined\n readonly scopeId?: string | undefined\n }, signal: AbortSignal): Promise<{ readonly entries: RequestListEntry[]; readonly pageInfo: CaidoPageInfo }> {\n const data = await graphql<{ requests: { edges: unknown[]; pageInfo: Record<string, unknown> } }>(\n this.options,\n REQUESTS_DOC,\n {\n first: options.first ?? 50,\n ...(options.after !== undefined && options.after !== '' ? { after: options.after } : {}),\n ...(options.httpqlFilter !== undefined && options.httpqlFilter !== '' ? { filter: { code: options.httpqlFilter } } : {}),\n order: { by: SORT_ENUMS[options.sortBy ?? 'timestamp'], ordering: (options.sortOrder ?? 'desc') === 'asc' ? 'ASC' : 'DESC' },\n ...(options.scopeId !== undefined && options.scopeId !== '' ? { scopeId: options.scopeId } : {}),\n includeRequestRaw: false,\n includeResponseRaw: false,\n },\n signal,\n )\n return {\n entries: data.requests.edges.map(projectEdge),\n pageInfo: {\n hasNextPage: data.requests.pageInfo.hasNextPage === true,\n hasPreviousPage: data.requests.pageInfo.hasPreviousPage === true,\n startCursor: data.requests.pageInfo.startCursor === null || data.requests.pageInfo.startCursor === undefined ? null : String(data.requests.pageInfo.startCursor),\n endCursor: data.requests.pageInfo.endCursor === null || data.requests.pageInfo.endCursor === undefined ? null : String(data.requests.pageInfo.endCursor),\n },\n }\n }\n\n /** Fetch one request with both raw halves (strix get_request_with_client parity: always request both). */\n async getRequest(requestId: string, signal: AbortSignal): Promise<StoredRequest | null> {\n const data = await graphql<{ request: Record<string, unknown> | null }>(\n this.options,\n REQUEST_DOC,\n { id: requestId, includeRequestRaw: true, includeResponseRaw: true },\n signal,\n )\n const request = data.request\n if (request === null || request === undefined) return null\n return {\n id: String(request.id),\n host: String(request.host),\n port: Number(request.port),\n method: String(request.method),\n tls: request.isTls === true,\n requestRaw: request.raw === null || request.raw === undefined ? null : Buffer.from(String(request.raw), 'base64').toString('utf8'),\n responseRaw: (request.response as Record<string, unknown> | null | undefined)?.raw === null\n || (request.response as Record<string, unknown> | null | undefined)?.raw === undefined\n ? null\n : Buffer.from(String((request.response as Record<string, unknown>).raw), 'base64').toString('utf8'),\n }\n }\n\n /**\n * Replay one stored request with optional field patches (strix\n * `repeat_request` + `replay_send_raw`): fetch raw → parse → patch →\n * rebuild → create empty session → startReplayTask → poll the entry.\n * @param requestId - the stored request id.\n * @param modifications - patch dict (url/params/headers/body/cookies).\n * @param budget - total dispatch deadline (strix 30s) + poll interval.\n */\n async replayRequest(\n requestId: string,\n modifications: Readonly<Record<string, unknown>> | undefined,\n signal: AbortSignal,\n budget: { readonly dispatchTimeoutMs: number; readonly pollIntervalMs: number },\n ): Promise<ReplayOutcome | null> {\n const stored = await this.getRequest(requestId, signal)\n if (stored === null || stored.requestRaw === null) return null\n const components = parseRawRequest(stored.requestRaw)\n const fullUrl = fullUrlFromComponents({ host: stored.host, tls: stored.tls }, components, modifications ?? {})\n const modified = applyModifications(components, modifications ?? {}, fullUrl)\n const built = buildRawRequest(modified)\n\n const session = await graphql<{ createReplaySession: { session: { id: string } | null; error: unknown } }>(\n this.options,\n CREATE_REPLAY_SESSION_DOC,\n { input: {} },\n signal,\n )\n const createdSession = session.createReplaySession.session\n if (createdSession === null || createdSession === undefined) throw new Error('createReplaySession returned no session')\n\n const started = Date.now()\n const start = await graphql<{ startReplayTask: { error: unknown; task: { id: string; replayEntry: { id: string } | null } | null } }>(\n this.options,\n START_REPLAY_TASK_DOC,\n {\n sessionId: createdSession.id,\n input: {\n connection: { host: built.connection.host, port: built.connection.port, isTLS: built.connection.tls, SNI: null },\n raw: Buffer.from(built.raw).toString('base64'),\n settings: { connectionClose: false, updateContentLength: true, placeholders: [] },\n },\n },\n signal,\n )\n const task = start.startReplayTask.task\n if (start.startReplayTask.error !== null && start.startReplayTask.error !== undefined) {\n throw new Error(`startReplayTask failed: ${JSON.stringify(start.startReplayTask.error).slice(0, 200)}`)\n }\n if (task === null || task === undefined || task.replayEntry === null || task.replayEntry === undefined) {\n throw new Error('startReplayTask returned no task/entry')\n }\n\n // Bounded poll (SDK waits on a subscription; polling keeps us HTTP-only).\n for (;;) {\n const entry = await graphql<{ replayEntry: Record<string, unknown> | null }>(\n this.options,\n REPLAY_ENTRY_DOC,\n { id: task.replayEntry.id, includeReplayRaw: false, includeRequestRaw: false, includeResponseRaw: true },\n signal,\n )\n const node = entry.replayEntry\n if (node !== null && node !== undefined) {\n const errorText = node.error === null || node.error === undefined ? undefined : String(node.error)\n const request = node.request as { response?: Record<string, unknown> | null } | null | undefined\n const responseNode = request?.response ?? null\n if (responseNode !== null && responseNode !== undefined) {\n const rawBase64 = responseNode.raw\n const rawBytes = rawBase64 === null || rawBase64 === undefined ? null : Buffer.from(String(rawBase64), 'base64')\n return {\n sessionId: createdSession.id,\n status: 'DONE',\n elapsedMs: Date.now() - started,\n ...(errorText !== undefined ? { error: errorText } : {}),\n response: parseRawResponse(rawBytes),\n }\n }\n if (errorText !== undefined) {\n return { sessionId: createdSession.id, status: 'ERROR', elapsedMs: Date.now() - started, error: errorText, response: null }\n }\n }\n if (Date.now() - started > budget.dispatchTimeoutMs) {\n return {\n sessionId: createdSession.id,\n status: 'ERROR',\n elapsedMs: Date.now() - started,\n error: `Caido replay dispatch did not complete within ${String(Math.round(budget.dispatchTimeoutMs / 1000))}s — the target may be unroutable from the sandbox, or Caido's outbound HTTP client is stalled; check the target host/port and retry`,\n response: null,\n }\n }\n await new Promise(resolve => setTimeout(resolve, budget.pollIntervalMs))\n }\n }\n\n /** Sitemap roots or descendants (strix list_sitemap_with_client). */\n async listSitemap(options: {\n readonly scopeId?: string | undefined\n readonly parentId?: string | undefined\n readonly depth?: 'DIRECT' | 'ALL' | undefined\n }, signal: AbortSignal): Promise<unknown> {\n if (options.parentId !== undefined && options.parentId !== '') {\n return graphql(this.options, SITEMAP_DESCENDANTS_DOC, { parentId: options.parentId, depth: options.depth ?? 'DIRECT' }, signal)\n }\n return graphql(this.options, SITEMAP_ROOTS_DOC, options.scopeId !== undefined && options.scopeId !== '' ? { scopeId: options.scopeId } : {}, signal)\n }\n\n /** One sitemap entry with its 30 most recent requests (strix view_sitemap_entry_with_client). */\n async viewSitemapEntry(entryId: string, signal: AbortSignal): Promise<unknown> {\n return graphql(this.options, SITEMAP_ENTRY_DOC, { id: entryId }, signal)\n }\n\n /** All Caido scopes (strix scope_list). */\n async scopeList(signal: AbortSignal): Promise<unknown> {\n return graphql(this.options, SCOPES_DOC, {}, signal)\n }\n\n /** One scope by id (strix scope_get). */\n async scopeGet(scopeId: string, signal: AbortSignal): Promise<unknown> {\n return graphql(this.options, SCOPE_DOC, { id: scopeId }, signal)\n }\n\n /** Create a scope; empty lists allow-all/deny-none (strix scope_create). */\n async scopeCreate(name: string, allowlist: string[], denylist: string[], signal: AbortSignal): Promise<unknown> {\n return graphql(this.options, CREATE_SCOPE_DOC, { input: { name, allowlist, denylist } }, signal)\n }\n\n /** Update a scope; allow/deny lists FULLY REPLACE the previous values (strix parity). */\n async scopeUpdate(scopeId: string, name: string, allowlist: string[], denylist: string[], signal: AbortSignal): Promise<unknown> {\n return graphql(this.options, UPDATE_SCOPE_DOC, { id: scopeId, input: { name, allowlist, denylist } }, signal)\n }\n\n /** Delete a scope (strix scope_delete). */\n async scopeDelete(scopeId: string, signal: AbortSignal): Promise<unknown> {\n return graphql(this.options, DELETE_SCOPE_DOC, { id: scopeId }, signal)\n }\n}\n","/**\n * Caido proxy tools — TS port of strix tools/proxy/tools.py (schemas and\n * output shaping verbatim; the client half lives in client.ts, replay pure\n * helpers in replay.ts). Five tools: list_requests / view_request /\n * repeat_request / list_sitemap / view_sitemap_entry. The client resolves\n * lazily from the sandbox session's Caido bootstrap (concurrent with scan\n * start; the first tool call pays the login wait — strix lazy parity).\n * @module @gpzhang2001/sharpkit-proxy\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\nimport type Schema from '@deepseek-ai/schemastery'\nimport z from '@deepseek-ai/schemastery'\nimport { defineTool } from '@deepseek-ai/dsh-tools'\nimport { CaidoClient, type SortBy } from './client.ts'\n\n/** Deployment-tunable configuration. */\nexport interface Config {\n /** Sandbox session cache key shared with the other tool packages. */\n readonly scanId?: string\n /** list_requests page size default (strix first=50). */\n readonly listPageSize?: number\n /** Replay dispatch deadline (strix 30s). */\n readonly replayTimeoutMs?: number\n /** Client-level per-call timeout (strix @function_tool timeout=60/120). */\n readonly caidoTimeoutMs?: number\n /** Ask-approval gate on repeat_request (fail-closed without an approval service). */\n readonly requireApproval?: boolean\n /** Authorized target hosts/values; repeat_request refuses hosts outside this list (exact or dot-suffix subdomain). */\n readonly authorizedTargets?: string[]\n}\n\nexport const name = 'pentest-tool-proxy'\n\nexport const inject = ['tools', 'pentestSandbox']\n\nexport const Config: Schema<Config> = z.object({\n scanId: z.string().default('pentest'),\n listPageSize: z.number().default(50),\n replayTimeoutMs: z.number().default(30_000),\n caidoTimeoutMs: z.number().default(60_000),\n requireApproval: z.boolean().default(true),\n authorizedTargets: z.array(z.string()),\n})\n\n/**\n * Whether a repeat target host is inside the authorized list. Entries may be\n * bare hosts (`example.com`), wildcard subdomains (`*.example.com`), or full\n * URLs (hostname taken); a candidate matches on exact host or a dot-suffix\n * subdomain. An empty list allows everything (fail-open; warned once).\n * @param host - the replay target hostname.\n * @param authorized - the configured target entries.\n */\nexport function targetHostAllowed(host: string, authorized: readonly string[]): boolean {\n if (authorized.length === 0) return true\n const candidate = host.toLowerCase()\n return authorized.some(entry => {\n let allowed = entry.trim().toLowerCase()\n if (allowed === '') return false\n if (allowed.includes('://')) {\n try {\n allowed = new URL(allowed).hostname\n } catch {\n // keep the raw entry\n }\n }\n allowed = allowed.replace(/^\\*\\./, '').replace(/\\/.*$/, '')\n return allowed !== '' && (candidate === allowed || candidate.endsWith(`.${allowed}`))\n })\n}\n\n/**\n * Resolve the replay target hostname: an explicit url modification wins;\n * otherwise the stored request's host.\n */\nexport function resolveRepeatTargetHost(storedHost: string, modifications: Readonly<Record<string, unknown>> | undefined): string {\n const override = modifications?.['url']\n if (typeof override === 'string' && override !== '') {\n try {\n return new URL(override).hostname\n } catch {\n // fall through to the stored host\n }\n }\n return storedHost\n}\n\n/** Sitemap page size (strix parity: 30 entries per page). */\nexport const SITEMAP_PAGE_SIZE = 30\n\n/**\n * Slice one sitemap connection payload to a 1-indexed page of edges (the\n * total count is preserved). Payloads without a recognized connection pass\n * through untouched.\n */\nexport function pageSitemapPayload(payload: Record<string, unknown>, page: number): Record<string, unknown> {\n for (const key of ['sitemapRootEntries', 'sitemapDescendantEntries']) {\n const connection = payload[key]\n if (typeof connection !== 'object' || connection === null) continue\n const edges = (connection as { edges?: unknown }).edges\n if (!Array.isArray(edges)) continue\n const start = Math.max(0, (page - 1) * SITEMAP_PAGE_SIZE)\n const sliced = edges.slice(start, start + SITEMAP_PAGE_SIZE)\n return { ...payload, [key]: { ...(connection as Record<string, unknown>), edges: sliced, page, page_size: SITEMAP_PAGE_SIZE, has_more: start + SITEMAP_PAGE_SIZE < edges.length } }\n }\n return payload\n}\n\n/** strix view_request search hits: ≤20 matches with ±40 chars of context. */\ninterface SearchHit {\n readonly match: string\n readonly position: number\n readonly before: string\n readonly after: string\n}\n\n/**\n * Compact regex hits over raw content (strix `_format_search_hits`).\n * @param content - the raw text.\n * @param pattern - the model-supplied regex.\n * @returns the hits payload, or an error entry when the regex is invalid.\n */\nexport function formatSearchHits(content: string, pattern: string): { readonly hits: SearchHit[]; readonly totalHits: number; readonly error?: string } {\n let regex: RegExp\n try {\n regex = new RegExp(pattern, 'g')\n } catch (error) {\n return { hits: [], totalHits: 0, error: `Invalid regex: ${String(error)}` }\n }\n const hits: SearchHit[] = []\n for (const match of content.matchAll(regex)) {\n const start = match.index ?? 0\n const end = start + match[0].length\n hits.push({ match: match[0], position: start, before: content.slice(Math.max(0, start - 40), start), after: content.slice(end, end + 40) })\n if (hits.length >= 20) break\n }\n return { hits, totalHits: hits.length }\n}\n\n/**\n * Line-paginated raw content page (strix `_format_text_page`).\n * @param content - the raw text.\n * @param page - 1-indexed page number.\n * @param pageSize - lines per page.\n */\nexport function formatTextPage(content: string, page: number, pageSize: number): {\n readonly content: string\n readonly page: number\n readonly pageSize: number\n readonly totalLines: number\n readonly hasMore: boolean\n} {\n const lines = content.split('\\n')\n const start = Math.max(0, (page - 1) * pageSize)\n const end = start + pageSize\n return { content: lines.slice(start, end).join('\\n'), page, pageSize, totalLines: lines.length, hasMore: end < lines.length }\n}\n\n/** Strip undefined values so an object satisfies the JsonValue index contract. */\nfunction structuredToPlain(value: Record<string, unknown>): { [key: string]: JsonValueLike } {\n const plain: { [key: string]: JsonValueLike } = JSON.parse(JSON.stringify(value)) as { [key: string]: JsonValueLike }\n return plain\n}\n\n/** Local structural twin of the tool-runtime JsonValue (type-only import). */\ntype JsonValueLike = string | number | boolean | null | JsonValueLike[] | { [key: string]: JsonValueLike }\n\ninterface ToolRunContextLike {\n readonly signal: AbortSignal\n}\n\n/** Unwrap the GraphQL envelope for one scope row. */\nfunction plainScope(envelope: unknown): Record<string, unknown> {\n const record = envelope as Record<string, unknown>\n for (const key of ['scope', 'createScope', 'updateScope']) {\n const inner = record[key]\n if (typeof inner === 'object' && inner !== null) {\n const scope = (inner as Record<string, unknown>)['scope']\n if (typeof scope === 'object' && scope !== null) return scope as Record<string, unknown>\n }\n }\n return record\n}\n\n/** Unwrap a scopes list envelope. */\nfunction plainScopes(rows: unknown[]): Record<string, unknown>[] {\n return rows.filter((row): row is Record<string, unknown> => typeof row === 'object' && row !== null)\n}\n\n/** Canonical value of view_request (oneOf the two modes + error). */\ntype ViewRequestValue = {\n readonly kind: 'hits'\n readonly hits: SearchHit[]\n readonly total_hits: number\n} | {\n readonly kind: 'page'\n readonly content: string\n readonly page: number\n readonly page_size: number\n readonly total_lines: number\n readonly has_more: boolean\n} | {\n readonly kind: 'error'\n readonly error: string\n}\n\nexport function apply(ctx: Context, config: Config = {}): void {\n const scanId = config.scanId ?? 'pentest'\n const listPageSize = config.listPageSize ?? 50\n const replayTimeoutMs = config.replayTimeoutMs ?? 30_000\n const caidoTimeoutMs = config.caidoTimeoutMs ?? 60_000\n let clientPromise: Promise<CaidoClient> | undefined\n let warnedNoAllowlist = false\n\n /** Authorized targets: Config first, else the preset's scan targets. Empty = fail-open (warned once). */\n const authorizedTargets = (): string[] => {\n if (config.authorizedTargets !== undefined) return config.authorizedTargets\n const preset = ctx.get('pentestPreset') as { authorizedTargets?: Array<{ value: string }> } | undefined\n const values = preset?.authorizedTargets?.map(target => target.value) ?? []\n if (values.length === 0 && !warnedNoAllowlist) {\n warnedNoAllowlist = true\n ctx.logger.warn('pentest-proxy: no authorizedTargets configured; repeat_request target enforcement is inactive (fail-open)')\n }\n return values\n }\n\n /** Lazy client: resolves the session's Caido bootstrap on first use (strix parity). */\n const client = (signal: AbortSignal): Promise<CaidoClient> => {\n // A failed bootstrap stays failed (strix caido_handle parity: every caller\n // sees the same degraded state); the wording is stable so the model gets\n // consistent guidance instead of the raw exception.\n clientPromise ??= (async () => {\n const session = await ctx.pentestSandbox.createSession({ scanId })\n const endpoint = await session.caidoEndpoint()\n return new CaidoClient({ baseUrl: endpoint.baseUrl, token: endpoint.token })\n })().catch(error => {\n throw new Error(`Caido client not available in run context: ${String(error instanceof Error ? error.message : error)}`)\n })\n const settled = clientPromise\n if (settled === undefined) throw new Error('unreachable: client promise just assigned')\n const timeout = new Promise<never>((_resolve, reject) => {\n signal.addEventListener('abort', () => reject(new Error('caido call aborted')), { once: true })\n const timer = setTimeout(() => reject(new Error(`caido call timed out after ${String(caidoTimeoutMs)}ms`)), caidoTimeoutMs)\n void settled.then(() => clearTimeout(timer), () => clearTimeout(timer))\n })\n return Promise.race([settled, timeout])\n }\n\n if (config.requireApproval !== false) {\n void ctx.on('tools/pre-execute', async (exec, next) => {\n if (exec.name === 'repeat_request') return { kind: 'ask' as const, reason: 'pentest: replay a captured request against the target' }\n return next()\n })\n }\n\n ctx.tools.register(defineTool({\n name: 'list_requests',\n description: `List captured HTTP requests from the Caido proxy with HTTPQL filtering. HTTPQL: integer fields (resp.code, req.port, id, roundtrip) use eq/gt/gte/lt/lte/ne e.g. 'resp.code.gte:400'; text fields (req.method, req.host, req.path, req.query, req.ext, req.raw) use regex/cont/eq e.g. 'req.path.cont:\"/api/\"'; dates use gt/lt with ISO e.g. 'req.created_at.gt:\"2024-01-01T00:00:00Z\"'; combine with AND/OR; no NOT — use ne/ncont/nregex. String values MUST be quoted, integers MUST NOT. A bare quoted string searches req.raw and resp.raw. Pagination: pass page_info.end_cursor as after.`,\n parameters: {\n httpql_filter: { type: 'string', description: 'Caido HTTPQL query (optional).' },\n first: { type: 'integer', description: `Entries per page (default ${String(listPageSize)}).` },\n after: { type: 'string', description: \"Cursor from a previous response's page_info.end_cursor.\" },\n sort_by: { type: 'string', enum: ['timestamp', 'host', 'method', 'path', 'status_code', 'response_time', 'response_size', 'source'], description: 'Sort key (default timestamp).' },\n sort_order: { type: 'string', enum: ['asc', 'desc'], description: 'Sort order (default desc).' },\n scope_id: { type: 'string', description: 'Restrict to a Caido scope.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n entries: { type: 'array', items: { type: 'object', properties: {}, additionalProperties: true } },\n page_info: { type: 'object', properties: {}, additionalProperties: true },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; entries: unknown[]; error?: string }\n if (!result.success) return [{ type: 'text', text: `list_requests failed: ${result.error ?? 'unknown'}` }]\n return [{ type: 'text', text: `${String(result.entries.length)} entries (cursor pagination via page_info)` }]\n },\n },\n execute: async (args, exec) => {\n try {\n const caido = await client(exec.signal)\n const connection = await caido.listRequests({\n httpqlFilter: args.httpql_filter,\n first: args.first ?? listPageSize,\n after: args.after,\n sortBy: args.sort_by as SortBy | undefined,\n sortOrder: args.sort_order,\n scopeId: args.scope_id,\n }, exec.signal)\n return {\n success: true,\n entries: connection.entries.map(entry => ({\n cursor: entry.cursor,\n request: {\n id: entry.request.id,\n host: entry.request.host,\n port: entry.request.port,\n method: entry.request.method,\n path: entry.request.path,\n query: entry.request.query,\n is_tls: entry.request.tls,\n created_at: entry.request.createdAt,\n },\n response: entry.response === null ? null : {\n id: entry.response.id,\n status_code: entry.response.statusCode,\n length: entry.response.length,\n created_at: entry.response.createdAt,\n },\n })),\n page_info: {\n has_next_page: connection.pageInfo.hasNextPage,\n has_previous_page: connection.pageInfo.hasPreviousPage,\n start_cursor: connection.pageInfo.startCursor,\n end_cursor: connection.pageInfo.endCursor,\n },\n }\n } catch (error) {\n return { success: false, entries: [], page_info: {}, error: String(error instanceof Error ? error.message : error) }\n }\n },\n }))\n\n ctx.tools.register(defineTool({\n name: 'view_request',\n description: `View a captured request or its response raw content, optionally regex-searched. With search_pattern: up to 20 compact hits with before/after context (hunting reflections, leaked URLs, hidden parameters). Without: full content line-paginated (page, page_size). Patterns like '/api/[a-zA-Z0-9._/-]+' (endpoints), 'https?://[^\\\\s<>\"]+' (URLs), '[?&][a-zA-Z0-9_]+=([^&\\\\s]+)' (query params).`,\n parameters: {\n request_id: { type: 'string', required: true, description: 'Request ID from list_requests.' },\n part: { type: 'string', enum: ['request', 'response'], description: 'Which raw half to view (default request).' },\n search_pattern: { type: 'string', description: 'Optional regex; switches to compact hits mode.' },\n page: { type: 'integer', description: '1-indexed page (only without search_pattern).' },\n page_size: { type: 'integer', description: 'Lines per page (default 50).' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n kind: { type: 'string', required: true, enum: ['hits', 'page', 'error'] },\n hits: { type: 'array', items: { type: 'object', properties: {}, additionalProperties: true } },\n total_hits: { type: 'integer' },\n content: { type: 'string' },\n page: { type: 'integer' },\n page_size: { type: 'integer' },\n total_lines: { type: 'integer' },\n has_more: { type: 'boolean' },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as ViewRequestValue\n if (result.kind === 'error') return [{ type: 'text', text: `view_request failed: ${result.error}` }]\n if (result.kind === 'hits') return [{ type: 'text', text: `${String(result.total_hits)} regex hit(s)` }]\n return [{ type: 'text', text: `page ${String(result.page)}/${String(Math.ceil(result.total_lines / Math.max(1, result.page_size)))} of ${String(result.total_lines)} lines` }]\n },\n },\n execute: async (args, exec) => {\n try {\n const caido = await client(exec.signal)\n const stored = await caido.getRequest(args.request_id, exec.signal)\n if (stored === null) return { kind: 'error' as const, error: `Request ${args.request_id} not found` }\n const raw = args.part === 'response' ? stored.responseRaw : stored.requestRaw\n if (raw === null) return { kind: 'error' as const, error: `No raw ${args.part === 'response' ? 'response' : 'request'} for ${args.request_id}` }\n if (args.search_pattern !== undefined && args.search_pattern !== '') {\n const hits = formatSearchHits(raw, args.search_pattern)\n if (hits.error !== undefined) return { kind: 'error' as const, error: hits.error }\n return {\n kind: 'hits' as const,\n hits: hits.hits.map(hit => ({ match: hit.match, position: hit.position, before: hit.before, after: hit.after })),\n total_hits: hits.totalHits,\n }\n }\n const textPage = formatTextPage(raw, args.page ?? 1, args.page_size ?? 50)\n return {\n kind: 'page' as const,\n content: textPage.content,\n page: textPage.page,\n page_size: textPage.pageSize,\n total_lines: textPage.totalLines,\n has_more: textPage.hasMore,\n }\n } catch (error) {\n return { kind: 'error' as const, error: String(error instanceof Error ? error.message : error) }\n }\n },\n }))\n\n ctx.tools.register(defineTool({\n name: 'repeat_request',\n description: `Repeat a captured request, optionally patching fields — the browse→capture→modify→test flow. modifications keys: url (replace), params (query additions), headers (additions), body (replace), cookies (additions). Inherits everything else from the original.`,\n parameters: {\n request_id: { type: 'string', required: true, description: 'ID of the original request (from list_requests).' },\n modifications: {\n type: 'object',\n properties: {\n url: { type: 'string', description: 'Replace the URL.' },\n params: { type: 'object', properties: {}, additionalProperties: true, description: 'Query-string keys to add/update.' },\n headers: { type: 'object', properties: {}, additionalProperties: true, description: 'Headers to add/update.' },\n body: { type: 'string', description: 'Replace the body.' },\n cookies: { type: 'object', properties: {}, additionalProperties: true, description: 'Cookies to add/update.' },\n },\n additionalProperties: false,\n description: 'Patch dict overlaying the original request.',\n },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n status: { type: 'string' },\n session_id: { type: 'string' },\n elapsed_ms: { type: 'integer' },\n response: { type: 'object', properties: {}, additionalProperties: true },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; status?: string; error?: string; response?: { status_code?: number } | null }\n if (!result.success) return [{ type: 'text', text: `repeat_request failed: ${result.error ?? result.status ?? 'unknown'}` }]\n return [{ type: 'text', text: `replay ${result.status ?? 'DONE'}${result.response?.status_code === undefined ? '' : ` → HTTP ${String(result.response.status_code)}`}` }]\n },\n },\n execute: async (args, exec) => {\n try {\n const caido = await client(exec.signal)\n const stored = await caido.getRequest(args.request_id, exec.signal)\n if (stored === null) return { success: false, status: 'ERROR', error: `Request ${args.request_id} not found` }\n // Scope enforcement (beyond strix parity): the replay target must be\n // inside the authorized target list before anything is dispatched.\n const allowlist = authorizedTargets()\n const targetHost = resolveRepeatTargetHost(stored.host, args.modifications as Readonly<Record<string, unknown>> | undefined)\n if (!targetHostAllowed(targetHost, allowlist)) {\n return { success: false, status: 'ERROR', error: `repeat_request refused: target host '${targetHost}' is outside this scan's authorized targets` }\n }\n const replay = await caido.replayRequest(args.request_id, args.modifications, exec.signal, { dispatchTimeoutMs: replayTimeoutMs, pollIntervalMs: 500 })\n if (replay === null) return { success: false, status: 'ERROR', error: `Request ${args.request_id} not found` }\n return {\n success: replay.status === 'DONE',\n status: replay.status,\n session_id: replay.sessionId,\n elapsed_ms: replay.elapsedMs,\n response: replay.response === null ? {} : {\n status_code: replay.response.statusCode,\n length: replay.response.length,\n headers: replay.response.headers,\n body: replay.response.body,\n body_truncated: replay.response.bodyTruncated,\n },\n ...(replay.error !== undefined ? { error: replay.error } : {}),\n }\n } catch (error) {\n return { success: false, status: 'ERROR', error: String(error instanceof Error ? error.message : error) }\n }\n },\n }))\n\n ctx.tools.register(defineTool({\n name: 'list_sitemap',\n description: `Browse Caido's hierarchical sitemap: DOMAIN → DIRECTORY → REQUEST → REQUEST_BODY/REQUEST_QUERY. Start with no parent_id for root domains (optionally scope-filtered); drill in by passing an entry's id as parent_id (depth DIRECT or ALL). Pair with view_sitemap_entry.`,\n parameters: {\n scope_id: { type: 'string', description: 'Limit roots to a Caido scope (only without parent_id).' },\n parent_id: { type: 'string', description: 'Entry ID to expand; omit for root domains.' },\n depth: { type: 'string', enum: ['DIRECT', 'ALL'], description: 'DIRECT children or the full subtree (default DIRECT).' },\n page: { type: 'integer', description: '1-indexed page (30 entries per page, strix parity).' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n payload: { type: 'object', properties: {}, additionalProperties: true },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string }\n return [{ type: 'text', text: result.success ? 'sitemap payload returned' : `list_sitemap failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: async (args, exec) => {\n try {\n const caido = await client(exec.signal)\n const payload = await caido.listSitemap({ scopeId: args.scope_id, parentId: args.parent_id, depth: args.depth }, exec.signal) as Record<string, unknown>\n return { success: true as const, payload: structuredToPlain(pageSitemapPayload(payload, args.page ?? 1)) }\n } catch (error) {\n return { success: false as const, payload: {}, error: String(error instanceof Error ? error.message : error) }\n }\n },\n }))\n\n ctx.tools.register(defineTool({\n name: 'scope_rules',\n description: \"Manage Caido scope rules: get/list/create/update/delete. Glob patterns (*, ?, [abc], [a-z], [^abc]); an EMPTY allowlist ALLOWS ALL; the denylist overrides the allowlist. Scopes feed list_requests' scope_id.\",\n parameters: {\n action: { type: 'string', required: true, enum: ['get', 'list', 'create', 'update', 'delete'], description: 'Scope action.' },\n allowlist: { type: 'array', items: { type: 'string' }, description: 'Allow glob patterns (create/update).' },\n denylist: { type: 'array', items: { type: 'string' }, description: 'Deny glob patterns; overrides the allowlist (create/update).' },\n scope_id: { type: 'string', description: 'Target scope id (get/update/delete).' },\n scope_name: { type: 'string', description: 'Scope name (create/update).' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n scopes: { type: 'array', items: { type: 'object', properties: {}, additionalProperties: true } },\n scope: { type: 'object', properties: {}, additionalProperties: true },\n deleted: { type: 'string' },\n message: { type: 'string' },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string }\n return [{ type: 'text', text: result.success ? 'scope action succeeded' : `scope_rules failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: (async (rawArgs: never, rawExec: never) => {\n const args = rawArgs as never as { action: string; allowlist?: string[]; denylist?: string[]; scope_id?: string; scope_name?: string }\n const exec = rawExec as never as ToolRunContextLike\n try {\n const caido = await client(exec.signal)\n const allowlist = args.allowlist ?? []\n const denylist = args.denylist ?? []\n switch (args.action) {\n case 'list': {\n const payload = await caido.scopeList(exec.signal) as { scopes?: unknown[] }\n return { success: true, scopes: plainScopes(payload.scopes ?? []) }\n }\n case 'get': {\n if (args.scope_id === undefined || args.scope_id === '') return { success: false, error: \"Scope_id is required for action='get'\" }\n return { success: true, scope: plainScope(await caido.scopeGet(args.scope_id, exec.signal)) }\n }\n case 'create': {\n if (args.scope_name === undefined || args.scope_name === '') return { success: false, error: \"Scope_name is required for action='create'\" }\n return { success: true, scope: plainScope(await caido.scopeCreate(args.scope_name, allowlist, denylist, exec.signal)) }\n }\n case 'update': {\n if (args.scope_id === undefined || args.scope_id === '' || args.scope_name === undefined || args.scope_name === '') {\n return { success: false, error: \"Scope_id and scope_name are required for action='update'\" }\n }\n return { success: true, scope: plainScope(await caido.scopeUpdate(args.scope_id, args.scope_name, allowlist, denylist, exec.signal)) }\n }\n case 'delete': {\n if (args.scope_id === undefined || args.scope_id === '') return { success: false, error: \"Scope_id is required for action='delete'\" }\n const payload = await caido.scopeDelete(args.scope_id, exec.signal) as { deleteScope?: { deletedId?: string } }\n return { success: true, deleted: payload.deleteScope?.deletedId ?? args.scope_id, message: `Scope ${args.scope_id} deleted` }\n }\n default:\n return { success: false, error: `Unknown action: ${String(args.action)}` }\n }\n } catch (error) {\n return { success: false, error: `scope_rules failed: ${String(error instanceof Error ? error.message : error)}` }\n }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'view_sitemap_entry',\n description: \"Full detail for a sitemap entry plus its most recent 30 related requests. Pick entry_id from list_sitemap.\",\n parameters: {\n entry_id: { type: 'string', required: true, description: 'ID from list_sitemap (or any nested entry).' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n payload: { type: 'object', properties: {}, additionalProperties: true },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string }\n return [{ type: 'text', text: result.success ? 'sitemap entry payload returned' : `view_sitemap_entry failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: async (args, exec) => {\n try {\n const caido = await client(exec.signal)\n const payload = await caido.viewSitemapEntry(args.entry_id, exec.signal) as Record<string, unknown>\n return { success: true as const, payload: structuredToPlain(payload) }\n } catch (error) {\n return { success: false as const, payload: {}, error: String(error instanceof Error ? error.message : error) }\n }\n },\n }))\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["Buffer"],"sources":["../src/replay.ts","../src/client.ts","../src/index.ts"],"sourcesContent":["/**\n * Pure HTTP replay helpers, ported from strix tools/proxy/caido_api.py:\n * raw request parsing/rebuilding with framing-header hygiene (:173-218),\n * response parsing (:220-258), request-line parsing (:260-279), URL\n * composition (:281-292), and field modifications (:294-331). Total pure\n * functions — exhaustively unit-testable.\n * @module @gpzhang2001/sharpkit-proxy/replay\n */\n\n/** Parsed components of a raw HTTP request text. */\nexport interface RawRequestComponents {\n readonly method: string\n readonly urlPath: string\n readonly headers: Record<string, string>\n readonly body: string\n}\n\n/** A connection target for Caido's replay dispatch. */\nexport interface ConnectionInfo {\n readonly host: string\n readonly port: number\n readonly tls: boolean\n readonly sni?: string\n}\n\n/** Parsed summary of a raw HTTP response (strix list_requests shape). */\nexport interface RawResponseParts {\n readonly statusCode: number\n readonly length: number\n readonly headers: Record<string, string>\n readonly body: string\n readonly bodyTruncated: boolean\n}\n\n/** strix clips replay response bodies at 8192 chars (caido_api.py:222). */\nconst RESPONSE_BODY_MAX_CHARS = 8192\n\n/** Framing headers that must never survive into a modified replay (strix :175). */\nconst FRAMING_HEADERS = new Set(['content-length', 'transfer-encoding'])\n\n/**\n * Parse a raw HTTP request text into components (strix `parse_raw_request`).\n * @param rawContent - the raw request text.\n * @returns the parsed components.\n * @throws when the request line is malformed.\n */\nexport function parseRawRequest(rawContent: string): RawRequestComponents {\n const lines = rawContent.split('\\n')\n const requestLine = (lines[0] ?? '').trim().split(' ')\n if (requestLine.length < 2) throw new Error('Invalid request line format')\n const headers: Record<string, string> = {}\n let bodyStart = 0\n for (let index = 1; index < lines.length; index++) {\n const line = lines[index] ?? ''\n if (line.trim() === '') {\n bodyStart = index + 1\n break\n }\n const separator = line.indexOf(':')\n if (separator !== -1) headers[line.slice(0, separator).trim()] = line.slice(separator + 1).trim()\n }\n const body = bodyStart < lines.length ? lines.slice(bodyStart).join('\\n').trim() : ''\n return { method: requestLine[0] ?? '', urlPath: requestLine[1] ?? '', headers, body }\n}\n\n/**\n * Compose the full URL from the original request's connection facts, the\n * parsed components, and an optional explicit url override (strix\n * `full_url_from_components`).\n * @param original - the stored request's host/tls facts.\n * @param components - parsed raw request components.\n * @param modifications - the patch dict.\n * @returns the absolute target URL.\n */\nexport function fullUrlFromComponents(\n original: { readonly host: string; readonly tls: boolean },\n components: RawRequestComponents,\n modifications: Readonly<Record<string, unknown>>,\n): string {\n const override = modifications['url']\n if (typeof override === 'string' && override !== '') return override\n const hostHeader = components.headers['Host'] ?? original.host\n const scheme = original.tls ? 'https' : 'http'\n return `${scheme}://${hostHeader}${components.urlPath}`\n}\n\n/** Parse a query string into a first-value map (Node parity of parse_qs). */\nfunction parseQuery(query: string): Record<string, string> {\n const params: Record<string, string> = {}\n for (const pair of query.split('&')) {\n if (pair === '') continue\n const eq = pair.indexOf('=')\n const key = eq === -1 ? pair : pair.slice(0, eq)\n const value = eq === -1 ? '' : pair.slice(eq + 1)\n params[decodeURIComponent(key)] = decodeURIComponent(value)\n }\n return params\n}\n\n/** Serialize a query map back to a string (Node parity of urlencode). */\nfunction encodeQuery(params: Readonly<Record<string, string>>): string {\n return Object.entries(params)\n .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)\n .join('&')\n}\n\n/**\n * Overlay the patch dict onto parsed components (strix `apply_modifications`):\n * params merge into the query string, headers/cookies merge, body replaces.\n * @param components - parsed raw request components.\n * @param modifications - the patch dict (url/params/headers/body/cookies).\n * @param fullUrl - the composed final URL.\n * @returns the modified components keyed like strix.\n */\nexport function applyModifications(\n components: RawRequestComponents,\n modifications: Readonly<Record<string, unknown>>,\n fullUrl: string,\n): { readonly method: string; readonly url: string; readonly headers: Record<string, string>; readonly body: string } {\n const headers = { ...components.headers }\n let body = components.body\n let finalUrl = fullUrl\n\n const params = modifications['params']\n if (params !== undefined && params !== null && typeof params === 'object') {\n const question = finalUrl.indexOf('?')\n const existing = question === -1 ? {} : parseQuery(finalUrl.slice(question + 1))\n for (const [key, value] of Object.entries(params as Record<string, unknown>)) {\n existing[key] = String(value)\n }\n const base = question === -1 ? finalUrl : finalUrl.slice(0, question)\n finalUrl = `${base}?${encodeQuery(existing)}`\n }\n const headerPatch = modifications['headers']\n if (headerPatch !== undefined && headerPatch !== null && typeof headerPatch === 'object') {\n for (const [key, value] of Object.entries(headerPatch as Record<string, unknown>)) {\n headers[key] = String(value)\n }\n }\n const bodyPatch = modifications['body']\n if (typeof bodyPatch === 'string') body = bodyPatch\n const cookiePatch = modifications['cookies']\n if (cookiePatch !== undefined && cookiePatch !== null && typeof cookiePatch === 'object') {\n const cookies: Record<string, string> = {}\n const existingCookie = headers['Cookie']\n if (existingCookie !== undefined) {\n for (const cookie of existingCookie.split(';')) {\n const eq = cookie.indexOf('=')\n if (eq !== -1) cookies[cookie.slice(0, eq).trim()] = cookie.slice(eq + 1).trim()\n }\n }\n for (const [key, value] of Object.entries(cookiePatch as Record<string, unknown>)) {\n cookies[key] = String(value)\n }\n headers['Cookie'] = Object.entries(cookies).map(([key, value]) => `${key}=${value}`).join('; ')\n }\n\n return { method: components.method, url: finalUrl, headers, body }\n}\n\n/**\n * Rebuild a raw HTTP/1.1 request with connection facts (strix\n * `build_raw_request`): Host/UA defaults, framing headers dropped,\n * Content-Length recomputed from the actual body.\n * @param parts - method/url/headers/body of the replay.\n * @returns the connection target and encoded raw request bytes.\n */\nexport function buildRawRequest(parts: {\n readonly method: string\n readonly url: string\n readonly headers: Record<string, string>\n readonly body: string\n}): { readonly connection: ConnectionInfo; readonly raw: Uint8Array } {\n let parsed: URL\n try {\n parsed = new URL(parts.url)\n } catch {\n throw new Error(`Invalid URL: ${parts.url}`)\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') throw new Error(`Invalid URL: ${parts.url}`)\n const tls = parsed.protocol === 'https:'\n const port = parsed.port !== '' ? Number(parsed.port) : tls ? 443 : 80\n const path = `${parsed.pathname}${parsed.search}`\n\n const headers: Record<string, string> = { ...parts.headers }\n if (headers['Host'] === undefined) headers['Host'] = parsed.host\n if (headers['User-Agent'] === undefined) {\n headers['User-Agent'] = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36'\n }\n for (const key of Object.keys(headers)) {\n if (FRAMING_HEADERS.has(key.toLowerCase())) delete headers[key]\n }\n if (parts.body !== '') headers['Content-Length'] = String(Buffer.byteLength(parts.body, 'utf8'))\n\n const lines = [`${parts.method.toUpperCase()} ${path} HTTP/1.1`]\n for (const [key, value] of Object.entries(headers)) lines.push(`${key}: ${value}`)\n const raw = Buffer.from(`${lines.join('\\r\\n')}\\r\\n\\r\\n${parts.body}`, 'utf8')\n return { connection: { host: parsed.hostname, port, tls }, raw }\n}\n\n/**\n * Parse a raw HTTP response into the list_requests summary shape (strix\n * `parse_raw_response`); null when missing/unparseable. Body clipped at 8192\n * chars with a truncation flag.\n * @param rawBytes - the raw response bytes.\n * @returns the parsed parts, or null.\n */\nexport function parseRawResponse(rawBytes: Uint8Array | null | undefined): RawResponseParts | null {\n if (rawBytes === null || rawBytes === undefined || rawBytes.byteLength === 0) return null\n const text = Buffer.from(rawBytes).toString('latin1')\n const separator = text.indexOf('\\r\\n\\r\\n')\n if (separator === -1) return null\n const head = text.slice(0, separator)\n const lines = head.split('\\r\\n')\n const statusParts = (lines[0] ?? '').split(' ')\n if (statusParts.length < 2 || !/^\\d+$/.test(statusParts[1] ?? '')) return null\n const headers: Record<string, string> = {}\n for (const line of lines.slice(1)) {\n const colon = line.indexOf(':')\n if (colon === -1) continue\n headers[line.slice(0, colon).trim()] = line.slice(colon + 1).trim()\n }\n const bodyBytes = Buffer.from(text.slice(separator + 4), 'latin1')\n let body = bodyBytes.toString('utf8')\n const truncated = body.length > RESPONSE_BODY_MAX_CHARS\n if (truncated) body = body.slice(0, RESPONSE_BODY_MAX_CHARS)\n return { statusCode: Number(statusParts[1]), length: bodyBytes.byteLength, headers, body, bodyTruncated: truncated }\n}\n","/**\n * Caido HTTP/GraphQL client — TS port of the CLIENT half of strix\n * tools/proxy/caido_api.py against the HOST-side published endpoint (global\n * fetch + Bearer token from the sandbox session's bootstrap). Documents are\n * trimmed, schema-valid subsets of the caido_sdk_client generated operations\n * (Requests/Request/ReplayEntry/StartReplayTask/sitemap trio). The replay\n * flow adapts the SDK's subscription wait into bounded polling of the entry\n * (finished when a response or an error appears; 30s strix dispatch budget).\n * @module @gpzhang2001/sharpkit-proxy/client\n */\n\nimport { Buffer } from 'node:buffer'\nimport { buildRawRequest, fullUrlFromComponents, applyModifications, parseRawRequest, parseRawResponse, type RawResponseParts } from './replay.ts'\n\n/** Which half of a captured exchange to surface (strix RequestPart). */\nexport type RequestPart = 'request' | 'response'\n\n/** Sort keys accepted by list_requests (strix SortBy). */\nexport type SortBy = 'timestamp' | 'host' | 'method' | 'path' | 'status_code' | 'response_time' | 'response_size' | 'source'\n\n/** strix `_REQ_FIELD_MAP` resolved to Caido's RequestResponseOrderBy enums. */\nconst SORT_ENUMS: Record<SortBy, string> = {\n timestamp: 'CREATED_AT',\n host: 'HOST',\n method: 'METHOD',\n path: 'PATH',\n source: 'SOURCE',\n status_code: 'RESP_STATUS_CODE',\n response_time: 'RESP_ROUNDTRIP_TIME',\n response_size: 'RESP_LENGTH',\n}\n\n/** The one Caido port the sandbox publishes (protocol constant). */\nexport const CAIDO_PORT = 48080\n\n/** Compact Requests query (subset of the SDK's generated document). */\nconst REQUESTS_DOC = `query Requests($first: Int, $after: String, $filter: HTTPQLInput, $order: RequestResponseOrderInput, $scopeId: ID, $includeRequestRaw: Boolean!, $includeResponseRaw: Boolean!) {\n requests(first: $first, after: $after, filter: $filter, order: $order, scopeId: $scopeId) {\n edges { cursor node {\n id host port method path query isTls createdAt\n raw @include(if: $includeRequestRaw)\n response { id statusCode roundtripTime length createdAt raw @include(if: $includeResponseRaw) }\n } }\n pageInfo { hasNextPage hasPreviousPage startCursor endCursor }\n }\n}`\n\n/** Compact Request query (both raws always requested — SDK parity note). */\nconst REQUEST_DOC = `query Request($id: ID!, $includeRequestRaw: Boolean!, $includeResponseRaw: Boolean!) {\n request(id: $id) {\n id host port method path query isTls createdAt\n raw @include(if: $includeRequestRaw)\n response { id statusCode roundtripTime length createdAt raw @include(if: $includeResponseRaw) }\n }\n}`\n\n/** Empty replay session create (avoids the double history row the raw-create seeds). */\nconst CREATE_REPLAY_SESSION_DOC = `mutation CreateReplaySession($input: CreateReplaySessionInput!) {\n createReplaySession(input: $input) { session { id } error { __typename } }\n}`\n\n/** Capture audit: exact total + newest entry (count aggregate, single edge). */\nconst CAPTURE_SUMMARY_DOC = `query CaptureSummary($filter: HTTPQLInput, $scopeId: ID) {\n requests(first: 1, filter: $filter, scopeId: $scopeId, order: {by: CREATED_AT, ordering: DESC}) {\n count { value }\n edges { node { host port createdAt } }\n }\n}`\n\n/** Capture audit: oldest timestamp (asc boundary of the same connection). */\nconst CAPTURE_OLDEST_DOC = `query CaptureOldest($filter: HTTPQLInput, $scopeId: ID) {\n requests(first: 1, filter: $filter, scopeId: $scopeId, order: {by: CREATED_AT, ordering: ASC}) {\n edges { node { createdAt } }\n }\n}`\n\n/** Capture audit: per-domain request counts from the sitemap roots. */\nconst CAPTURE_HOSTS_DOC = `query CaptureHosts {\n sitemapRootEntries {\n edges { node { label requests(first: 1) { count { value } } } }\n }\n}`\n\n/** Replay dispatch (strix `replay_send_raw` via the SDK's StartReplayTask). */\nconst START_REPLAY_TASK_DOC = `mutation StartReplayTask($sessionId: ID!, $input: StartReplayTaskInput!) {\n startReplayTask(sessionId: $sessionId, input: $input) { error { __typename } task { id replayEntry { id } } }\n}`\n\n/** Replay entry poll — finished when request.response or error appears. */\nconst REPLAY_ENTRY_DOC = `query ReplayEntry($id: ID!, $includeReplayRaw: Boolean!, $includeRequestRaw: Boolean!, $includeResponseRaw: Boolean!) {\n replayEntry(id: $id) {\n id error\n request { id method path response { id statusCode length roundtripTime raw @include(if: $includeResponseRaw) } }\n }\n}`\n\n/** Scope management (strix caido_api.py scope_* via the SDK's ScopeFull set). */\nconst SCOPES_DOC = `query Scopes { scopes { id name allowlist denylist indexed } }`\n\nconst SCOPE_DOC = `query Scope($id: ID!) { scope(id: $id) { id name allowlist denylist indexed } }`\n\nconst CREATE_SCOPE_DOC = `mutation CreateScope($input: CreateScopeInput!) { createScope(input: $input) { error { __typename } scope { id name allowlist denylist indexed } } }`\n\nconst UPDATE_SCOPE_DOC = `mutation UpdateScope($id: ID!, $input: UpdateScopeInput!) { updateScope(id: $id, input: $input) { error { __typename } scope { id name allowlist denylist indexed } } }`\n\nconst DELETE_SCOPE_DOC = `mutation DeleteScope($id: ID!) { deleteScope(id: $id) { deletedId } }`\n\n/** Sitemap queries (verbatim field sets from strix caido_api.py:555-592). */\nconst SITEMAP_ROOTS_DOC = `query GetSitemapRoots($scopeId: ID) {\n sitemapRootEntries(scopeId: $scopeId) {\n edges { node {\n id kind label hasDescendants\n metadata { ... on SitemapEntryMetadataDomain { isTls port } }\n request { method path response { statusCode } }\n } }\n count { value }\n }\n}`\n\nconst SITEMAP_DESCENDANTS_DOC = `query GetSitemapDescendants($parentId: ID!, $depth: SitemapDescendantsDepth!) {\n sitemapDescendantEntries(parentId: $parentId, depth: $depth) {\n edges { node {\n id kind label hasDescendants\n request { method path response { statusCode } }\n } }\n count { value }\n }\n}`\n\nconst SITEMAP_ENTRY_DOC = `query GetSitemapEntry($id: ID!) {\n sitemapEntry(id: $id) {\n id kind label hasDescendants\n metadata { ... on SitemapEntryMetadataDomain { isTls port } }\n request { method path response { statusCode length roundtripTime } }\n requests(first: 30, order: {by: CREATED_AT, ordering: DESC}) {\n edges { node { method path response { statusCode length } } }\n count { value }\n }\n }\n}`\n\n/** Fetch contract (global fetch shape) for the GraphQL endpoint. */\nexport interface CaidoFetchFn {\n (url: string, init: { readonly method: 'POST'; readonly headers: Readonly<Record<string, string>>; readonly body: string; readonly signal: AbortSignal }): Promise<{ readonly status: number; readonly text: () => Promise<string> }>\n}\n\n/** One captured request entry projected for the model (strix shape). */\nexport interface RequestListEntry {\n readonly cursor: string\n readonly request: {\n readonly id: string\n readonly host: string\n readonly port: number\n readonly method: string\n readonly path: string\n readonly query: string | null\n readonly tls: boolean\n readonly createdAt: string\n }\n readonly response: { readonly id: string; readonly statusCode: number | null; readonly length: number | null; readonly createdAt: string | null } | null\n}\n\n/** PageInfo projection (strix page_info). */\nexport interface CaidoPageInfo {\n readonly hasNextPage: boolean\n readonly hasPreviousPage: boolean\n readonly startCursor: string | null\n readonly endCursor: string | null\n}\n\n/** The raw halves of one stored request. */\nexport interface StoredRequest {\n readonly id: string\n readonly host: string\n readonly port: number\n readonly method: string\n readonly tls: boolean\n readonly requestRaw: string | null\n readonly responseRaw: string | null\n}\n\n/** Replay outcome (strix `_format_replay_tool_result` inputs). */\nexport interface ReplayOutcome {\n readonly sessionId: string\n readonly status: string\n readonly elapsedMs: number\n readonly error?: string\n readonly response: RawResponseParts | null\n}\n\n/** Endpoint coordinates handed over by the sandbox session's bootstrap. */\nexport interface CaidoClientOptions {\n readonly baseUrl: string\n readonly token: string\n readonly fetchFn?: CaidoFetchFn\n}\n\n/** Minimal GraphQL POST with bearer auth and error normalization. */\nasync function graphql<T>(\n options: CaidoClientOptions,\n doc: string,\n variables: Record<string, unknown>,\n signal: AbortSignal,\n): Promise<T> {\n const fetchFn = options.fetchFn ?? fetch\n const response = await fetchFn(`${options.baseUrl}/graphql`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${options.token}` },\n body: JSON.stringify({ query: doc, variables }),\n signal,\n })\n const text = await response.text()\n if (response.status !== 200) throw new Error(`caido graphql HTTP ${response.status}: ${text.slice(0, 200)}`)\n let payload: unknown\n try {\n payload = JSON.parse(text)\n } catch (error) {\n throw new Error(`caido graphql unparseable response: ${String(error)}`)\n }\n const record = payload as { readonly data?: unknown; readonly errors?: unknown }\n if (record.errors !== undefined && record.errors !== null) {\n throw new Error(`caido graphql errors: ${JSON.stringify(record.errors).slice(0, 300)}`)\n }\n if (typeof record.data !== 'object' || record.data === null) {\n throw new Error('caido graphql carried no data')\n }\n return record.data as T\n}\n\n/** Project one raw requests-connection edge (strix list_requests mapping). */\nfunction projectEdge(edge: unknown): RequestListEntry {\n const node = (edge as { node: Record<string, unknown> }).node\n const response = node.response as Record<string, unknown> | null\n return {\n cursor: String((edge as { cursor: unknown }).cursor),\n request: {\n id: String(node.id),\n host: String(node.host),\n port: Number(node.port),\n method: String(node.method),\n path: String(node.path),\n query: node.query === null || node.query === undefined ? null : String(node.query),\n tls: node.isTls === true,\n createdAt: new Date(Number(node.createdAt)).toISOString(),\n },\n response: response === null || response === undefined ? null : {\n id: String(response.id),\n statusCode: response.statusCode === null || response.statusCode === undefined ? null : Number(response.statusCode),\n length: response.length === null || response.length === undefined ? null : Number(response.length),\n createdAt: response.createdAt === null || response.createdAt === undefined ? null : new Date(Number(response.createdAt)).toISOString(),\n },\n }\n}\n\n/**\n * The Caido client: five operations behind the five proxy tools. All methods\n * take an AbortSignal (tool exec.signal parity).\n */\nexport class CaidoClient {\n private readonly options: CaidoClientOptions\n\n constructor(options: CaidoClientOptions) {\n this.options = options\n }\n\n /** List captured requests with HTTPQL filter/cursor/sort/scope (strix list_requests_with_client). */\n async listRequests(options: {\n readonly httpqlFilter?: string | undefined\n readonly first?: number | undefined\n readonly after?: string | undefined\n readonly sortBy?: SortBy | undefined\n readonly sortOrder?: 'asc' | 'desc' | undefined\n readonly scopeId?: string | undefined\n }, signal: AbortSignal): Promise<{ readonly entries: RequestListEntry[]; readonly pageInfo: CaidoPageInfo }> {\n const data = await graphql<{ requests: { edges: unknown[]; pageInfo: Record<string, unknown> } }>(\n this.options,\n REQUESTS_DOC,\n {\n first: options.first ?? 50,\n ...(options.after !== undefined && options.after !== '' ? { after: options.after } : {}),\n ...(options.httpqlFilter !== undefined && options.httpqlFilter !== '' ? { filter: { code: options.httpqlFilter } } : {}),\n order: { by: SORT_ENUMS[options.sortBy ?? 'timestamp'], ordering: (options.sortOrder ?? 'desc') === 'asc' ? 'ASC' : 'DESC' },\n ...(options.scopeId !== undefined && options.scopeId !== '' ? { scopeId: options.scopeId } : {}),\n includeRequestRaw: false,\n includeResponseRaw: false,\n },\n signal,\n )\n return {\n entries: data.requests.edges.map(projectEdge),\n pageInfo: {\n hasNextPage: data.requests.pageInfo.hasNextPage === true,\n hasPreviousPage: data.requests.pageInfo.hasPreviousPage === true,\n startCursor: data.requests.pageInfo.startCursor === null || data.requests.pageInfo.startCursor === undefined ? null : String(data.requests.pageInfo.startCursor),\n endCursor: data.requests.pageInfo.endCursor === null || data.requests.pageInfo.endCursor === undefined ? null : String(data.requests.pageInfo.endCursor),\n },\n }\n }\n\n /**\n * Capture audit in one call: exact total request count (Caido connection\n * `count` aggregate), newest entry, oldest timestamp, and per-domain\n * counts from the sitemap roots. Used by the capture_summary tool to make\n * \"is my traffic actually being captured\" a cheap, verifiable question.\n * `since` (ISO timestamp) bounds the audit window via HTTPQL\n * `req.created_at.gt` — the sandbox Caido container is shared across scans\n * of one host process, so a fresh scan records the Phase-0 probe's\n * capture_summary as its baseline and diffs against it.\n */\n async captureSummary(signal: AbortSignal, scopeId?: string | undefined, since?: string | undefined): Promise<{\n readonly total: number\n readonly newest: { readonly host: string; readonly port: number; readonly createdAt: string } | null\n readonly oldestAt: string | null\n readonly hosts: ReadonlyArray<{ readonly host: string; readonly requests: number }>\n }> {\n const vars = {\n ...(scopeId !== undefined && scopeId !== '' ? { scopeId } : {}),\n ...(since !== undefined && since !== '' ? { filter: { code: `req.created_at.gt:\"${since}\"` } } : {}),\n }\n const [summary, oldest, hosts] = await Promise.all([\n graphql<{ requests: { count: { value: unknown } | null; edges: Array<{ node: { host: unknown; port: unknown; createdAt: unknown } }> } }>(\n this.options, CAPTURE_SUMMARY_DOC, vars, signal,\n ),\n graphql<{ requests: { edges: Array<{ node: { createdAt: unknown } }> } }>(\n this.options, CAPTURE_OLDEST_DOC, vars, signal,\n ),\n graphql<{ sitemapRootEntries: { edges: Array<{ node: { label: unknown; requests: { count: { value: unknown } | null } | null } }> } }>(\n this.options, CAPTURE_HOSTS_DOC, {}, signal,\n ),\n ])\n const iso = (value: unknown): string | null =>\n value === null || value === undefined ? null : new Date(Number(value)).toISOString()\n const edge = summary.requests.edges[0]?.node\n return {\n total: Number(summary.requests.count?.value ?? 0),\n newest: edge === undefined ? null : {\n host: String(edge.host),\n port: Number(edge.port),\n createdAt: iso(edge.createdAt) ?? '',\n },\n oldestAt: iso(oldest.requests.edges[0]?.node.createdAt),\n hosts: hosts.sitemapRootEntries.edges\n .map(root => ({ host: String(root.node.label), requests: Number(root.node.requests?.count?.value ?? 0) }))\n .filter(host => host.requests > 0)\n .sort((a, b) => b.requests - a.requests),\n }\n }\n\n /** Fetch one request with both raw halves (strix get_request_with_client parity: always request both). */\n async getRequest(requestId: string, signal: AbortSignal): Promise<StoredRequest | null> {\n const data = await graphql<{ request: Record<string, unknown> | null }>(\n this.options,\n REQUEST_DOC,\n { id: requestId, includeRequestRaw: true, includeResponseRaw: true },\n signal,\n )\n const request = data.request\n if (request === null || request === undefined) return null\n return {\n id: String(request.id),\n host: String(request.host),\n port: Number(request.port),\n method: String(request.method),\n tls: request.isTls === true,\n requestRaw: request.raw === null || request.raw === undefined ? null : Buffer.from(String(request.raw), 'base64').toString('utf8'),\n responseRaw: (request.response as Record<string, unknown> | null | undefined)?.raw === null\n || (request.response as Record<string, unknown> | null | undefined)?.raw === undefined\n ? null\n : Buffer.from(String((request.response as Record<string, unknown>).raw), 'base64').toString('utf8'),\n }\n }\n\n /**\n * Replay one stored request with optional field patches (strix\n * `repeat_request` + `replay_send_raw`): fetch raw → parse → patch →\n * rebuild → create empty session → startReplayTask → poll the entry.\n * @param requestId - the stored request id.\n * @param modifications - patch dict (url/params/headers/body/cookies).\n * @param budget - total dispatch deadline (strix 30s) + poll interval.\n */\n async replayRequest(\n requestId: string,\n modifications: Readonly<Record<string, unknown>> | undefined,\n signal: AbortSignal,\n budget: { readonly dispatchTimeoutMs: number; readonly pollIntervalMs: number },\n ): Promise<ReplayOutcome | null> {\n const stored = await this.getRequest(requestId, signal)\n if (stored === null || stored.requestRaw === null) return null\n const components = parseRawRequest(stored.requestRaw)\n const fullUrl = fullUrlFromComponents({ host: stored.host, tls: stored.tls }, components, modifications ?? {})\n const modified = applyModifications(components, modifications ?? {}, fullUrl)\n const built = buildRawRequest(modified)\n\n const session = await graphql<{ createReplaySession: { session: { id: string } | null; error: unknown } }>(\n this.options,\n CREATE_REPLAY_SESSION_DOC,\n { input: {} },\n signal,\n )\n const createdSession = session.createReplaySession.session\n if (createdSession === null || createdSession === undefined) throw new Error('createReplaySession returned no session')\n\n const started = Date.now()\n const start = await graphql<{ startReplayTask: { error: unknown; task: { id: string; replayEntry: { id: string } | null } | null } }>(\n this.options,\n START_REPLAY_TASK_DOC,\n {\n sessionId: createdSession.id,\n input: {\n connection: { host: built.connection.host, port: built.connection.port, isTLS: built.connection.tls, SNI: null },\n raw: Buffer.from(built.raw).toString('base64'),\n settings: { connectionClose: false, updateContentLength: true, placeholders: [] },\n },\n },\n signal,\n )\n const task = start.startReplayTask.task\n if (start.startReplayTask.error !== null && start.startReplayTask.error !== undefined) {\n throw new Error(`startReplayTask failed: ${JSON.stringify(start.startReplayTask.error).slice(0, 200)}`)\n }\n if (task === null || task === undefined || task.replayEntry === null || task.replayEntry === undefined) {\n throw new Error('startReplayTask returned no task/entry')\n }\n\n // Bounded poll (SDK waits on a subscription; polling keeps us HTTP-only).\n for (;;) {\n const entry = await graphql<{ replayEntry: Record<string, unknown> | null }>(\n this.options,\n REPLAY_ENTRY_DOC,\n { id: task.replayEntry.id, includeReplayRaw: false, includeRequestRaw: false, includeResponseRaw: true },\n signal,\n )\n const node = entry.replayEntry\n if (node !== null && node !== undefined) {\n const errorText = node.error === null || node.error === undefined ? undefined : String(node.error)\n const request = node.request as { response?: Record<string, unknown> | null } | null | undefined\n const responseNode = request?.response ?? null\n if (responseNode !== null && responseNode !== undefined) {\n const rawBase64 = responseNode.raw\n const rawBytes = rawBase64 === null || rawBase64 === undefined ? null : Buffer.from(String(rawBase64), 'base64')\n return {\n sessionId: createdSession.id,\n status: 'DONE',\n elapsedMs: Date.now() - started,\n ...(errorText !== undefined ? { error: errorText } : {}),\n response: parseRawResponse(rawBytes),\n }\n }\n if (errorText !== undefined) {\n return { sessionId: createdSession.id, status: 'ERROR', elapsedMs: Date.now() - started, error: errorText, response: null }\n }\n }\n if (Date.now() - started > budget.dispatchTimeoutMs) {\n return {\n sessionId: createdSession.id,\n status: 'ERROR',\n elapsedMs: Date.now() - started,\n error: `Caido replay dispatch did not complete within ${String(Math.round(budget.dispatchTimeoutMs / 1000))}s — the target may be unroutable from the sandbox, or Caido's outbound HTTP client is stalled; check the target host/port and retry`,\n response: null,\n }\n }\n await new Promise(resolve => setTimeout(resolve, budget.pollIntervalMs))\n }\n }\n\n /** Sitemap roots or descendants (strix list_sitemap_with_client). */\n async listSitemap(options: {\n readonly scopeId?: string | undefined\n readonly parentId?: string | undefined\n readonly depth?: 'DIRECT' | 'ALL' | undefined\n }, signal: AbortSignal): Promise<unknown> {\n if (options.parentId !== undefined && options.parentId !== '') {\n return graphql(this.options, SITEMAP_DESCENDANTS_DOC, { parentId: options.parentId, depth: options.depth ?? 'DIRECT' }, signal)\n }\n return graphql(this.options, SITEMAP_ROOTS_DOC, options.scopeId !== undefined && options.scopeId !== '' ? { scopeId: options.scopeId } : {}, signal)\n }\n\n /** One sitemap entry with its 30 most recent requests (strix view_sitemap_entry_with_client). */\n async viewSitemapEntry(entryId: string, signal: AbortSignal): Promise<unknown> {\n return graphql(this.options, SITEMAP_ENTRY_DOC, { id: entryId }, signal)\n }\n\n /** All Caido scopes (strix scope_list). */\n async scopeList(signal: AbortSignal): Promise<unknown> {\n return graphql(this.options, SCOPES_DOC, {}, signal)\n }\n\n /** One scope by id (strix scope_get). */\n async scopeGet(scopeId: string, signal: AbortSignal): Promise<unknown> {\n return graphql(this.options, SCOPE_DOC, { id: scopeId }, signal)\n }\n\n /** Create a scope; empty lists allow-all/deny-none (strix scope_create). */\n async scopeCreate(name: string, allowlist: string[], denylist: string[], signal: AbortSignal): Promise<unknown> {\n return graphql(this.options, CREATE_SCOPE_DOC, { input: { name, allowlist, denylist } }, signal)\n }\n\n /** Update a scope; allow/deny lists FULLY REPLACE the previous values (strix parity). */\n async scopeUpdate(scopeId: string, name: string, allowlist: string[], denylist: string[], signal: AbortSignal): Promise<unknown> {\n return graphql(this.options, UPDATE_SCOPE_DOC, { id: scopeId, input: { name, allowlist, denylist } }, signal)\n }\n\n /** Delete a scope (strix scope_delete). */\n async scopeDelete(scopeId: string, signal: AbortSignal): Promise<unknown> {\n return graphql(this.options, DELETE_SCOPE_DOC, { id: scopeId }, signal)\n }\n}\n","/**\n * Caido proxy tools — TS port of strix tools/proxy/tools.py (schemas and\n * output shaping verbatim; the client half lives in client.ts, replay pure\n * helpers in replay.ts). Tools: list_requests / view_request /\n * repeat_request / list_sitemap / view_sitemap_entry / scope_rules, plus\n * capture_summary (one-call capture audit so \"is my traffic being recorded\"\n * stays a cheap, verifiable question). The client resolves\n * lazily from the sandbox session's Caido bootstrap (concurrent with scan\n * start; the first tool call pays the login wait — strix lazy parity).\n * @module @gpzhang2001/sharpkit-proxy\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\nimport type Schema from '@deepseek-ai/schemastery'\nimport z from '@deepseek-ai/schemastery'\nimport { defineTool } from '@deepseek-ai/dsh-tools'\nimport { CaidoClient, type SortBy } from './client.ts'\n\n/** Deployment-tunable configuration. */\nexport interface Config {\n /** Sandbox session cache key shared with the other tool packages. */\n readonly scanId?: string\n /** list_requests page size default (strix first=50). */\n readonly listPageSize?: number\n /** Replay dispatch deadline (strix 30s). */\n readonly replayTimeoutMs?: number\n /** Client-level per-call timeout (strix @function_tool timeout=60/120). */\n readonly caidoTimeoutMs?: number\n /**\n * Ask-approval gate on repeat_request (fail-closed without an approval\n * service). Skipped automatically while the approval policy is `'never'` —\n * an ask there is a guaranteed auto-rejection, not an approval request.\n */\n readonly requireApproval?: boolean\n /** Authorized target hosts/values; repeat_request refuses hosts outside this list (exact or dot-suffix subdomain). */\n readonly authorizedTargets?: string[]\n}\n\n/**\n * Structural view of the host approval service (`ctx.approval`) — reads the\n * session's effective approval policy without a hard dependency on\n * `@deepseek-ai/dsh-user-approval`, using only stock public API members\n * (`overrideOf` + the plugin's configured default; same pattern as the\n * preset lookup below).\n */\ninterface ApprovalPolicySource {\n overrideOf: (session: object) => 'ask' | 'never' | undefined\n config?: { policy?: 'ask' | 'never' }\n}\n\n/**\n * Whether the session's approval policy is `'never'` — the deterministic\n * stance where every ask resolves `rejected` without consulting anyone (set\n * by the danger-full-access preset, i.e. a user who pre-trusted the\n * session). An ask under that policy can never be granted — and its denial\n * is even reported as \"the user rejected tool\" although no user was\n * consulted — so the gate treats it as pre-approval instead of asking:\n * replay targets stay confined to the authorizedTargets allowlist either\n * way.\n */\nfunction policyIsNever(ctx: Context, session: object | undefined): boolean {\n if (session === undefined) return false\n const approval = ctx.get('approval') as ApprovalPolicySource | undefined\n if (approval === undefined) return false\n return (approval.overrideOf(session) ?? approval.config?.policy ?? 'ask') === 'never'\n}\n\nexport const name = 'pentest-tool-proxy'\n\nexport const inject = ['tools', 'pentestSandbox']\n\nexport const Config: Schema<Config> = z.object({\n scanId: z.string().default('pentest'),\n listPageSize: z.number().default(50),\n replayTimeoutMs: z.number().default(30_000),\n caidoTimeoutMs: z.number().default(60_000),\n requireApproval: z.boolean().default(true),\n authorizedTargets: z.array(z.string()),\n})\n\n/**\n * Whether a repeat target host is inside the authorized list. Entries may be\n * bare hosts (`example.com`), wildcard subdomains (`*.example.com`), or full\n * URLs (hostname taken); a candidate matches on exact host or a dot-suffix\n * subdomain. An empty list allows everything (fail-open; warned once).\n * @param host - the replay target hostname.\n * @param authorized - the configured target entries.\n */\nexport function targetHostAllowed(host: string, authorized: readonly string[]): boolean {\n if (authorized.length === 0) return true\n const candidate = host.toLowerCase()\n return authorized.some(entry => {\n let allowed = entry.trim().toLowerCase()\n if (allowed === '') return false\n if (allowed.includes('://')) {\n try {\n allowed = new URL(allowed).hostname\n } catch {\n // keep the raw entry\n }\n }\n allowed = allowed.replace(/^\\*\\./, '').replace(/\\/.*$/, '')\n return allowed !== '' && (candidate === allowed || candidate.endsWith(`.${allowed}`))\n })\n}\n\n/**\n * Resolve the replay target hostname: an explicit url modification wins;\n * otherwise the stored request's host.\n */\nexport function resolveRepeatTargetHost(storedHost: string, modifications: Readonly<Record<string, unknown>> | undefined): string {\n const override = modifications?.['url']\n if (typeof override === 'string' && override !== '') {\n try {\n return new URL(override).hostname\n } catch {\n // fall through to the stored host\n }\n }\n return storedHost\n}\n\n/** Sitemap page size (strix parity: 30 entries per page). */\nexport const SITEMAP_PAGE_SIZE = 30\n\n/**\n * Slice one sitemap connection payload to a 1-indexed page of edges (the\n * total count is preserved). Payloads without a recognized connection pass\n * through untouched.\n */\nexport function pageSitemapPayload(payload: Record<string, unknown>, page: number): Record<string, unknown> {\n for (const key of ['sitemapRootEntries', 'sitemapDescendantEntries']) {\n const connection = payload[key]\n if (typeof connection !== 'object' || connection === null) continue\n const edges = (connection as { edges?: unknown }).edges\n if (!Array.isArray(edges)) continue\n const start = Math.max(0, (page - 1) * SITEMAP_PAGE_SIZE)\n const sliced = edges.slice(start, start + SITEMAP_PAGE_SIZE)\n return { ...payload, [key]: { ...(connection as Record<string, unknown>), edges: sliced, page, page_size: SITEMAP_PAGE_SIZE, has_more: start + SITEMAP_PAGE_SIZE < edges.length } }\n }\n return payload\n}\n\n/** strix view_request search hits: ≤20 matches with ±40 chars of context. */\ninterface SearchHit {\n readonly match: string\n readonly position: number\n readonly before: string\n readonly after: string\n}\n\n/**\n * Compact regex hits over raw content (strix `_format_search_hits`).\n * @param content - the raw text.\n * @param pattern - the model-supplied regex.\n * @returns the hits payload, or an error entry when the regex is invalid.\n */\nexport function formatSearchHits(content: string, pattern: string): { readonly hits: SearchHit[]; readonly totalHits: number; readonly error?: string } {\n let regex: RegExp\n try {\n regex = new RegExp(pattern, 'g')\n } catch (error) {\n return { hits: [], totalHits: 0, error: `Invalid regex: ${String(error)}` }\n }\n const hits: SearchHit[] = []\n for (const match of content.matchAll(regex)) {\n const start = match.index ?? 0\n const end = start + match[0].length\n hits.push({ match: match[0], position: start, before: content.slice(Math.max(0, start - 40), start), after: content.slice(end, end + 40) })\n if (hits.length >= 20) break\n }\n return { hits, totalHits: hits.length }\n}\n\n/**\n * Line-paginated raw content page (strix `_format_text_page`).\n * @param content - the raw text.\n * @param page - 1-indexed page number.\n * @param pageSize - lines per page.\n */\nexport function formatTextPage(content: string, page: number, pageSize: number): {\n readonly content: string\n readonly page: number\n readonly pageSize: number\n readonly totalLines: number\n readonly hasMore: boolean\n} {\n const lines = content.split('\\n')\n const start = Math.max(0, (page - 1) * pageSize)\n const end = start + pageSize\n return { content: lines.slice(start, end).join('\\n'), page, pageSize, totalLines: lines.length, hasMore: end < lines.length }\n}\n\n/** Strip undefined values so an object satisfies the JsonValue index contract. */\nfunction structuredToPlain(value: Record<string, unknown>): { [key: string]: JsonValueLike } {\n const plain: { [key: string]: JsonValueLike } = JSON.parse(JSON.stringify(value)) as { [key: string]: JsonValueLike }\n return plain\n}\n\n/** Local structural twin of the tool-runtime JsonValue (type-only import). */\ntype JsonValueLike = string | number | boolean | null | JsonValueLike[] | { [key: string]: JsonValueLike }\n\ninterface ToolRunContextLike {\n readonly signal: AbortSignal\n}\n\n/** Unwrap the GraphQL envelope for one scope row. */\nfunction plainScope(envelope: unknown): Record<string, unknown> {\n const record = envelope as Record<string, unknown>\n for (const key of ['scope', 'createScope', 'updateScope']) {\n const inner = record[key]\n if (typeof inner === 'object' && inner !== null) {\n const scope = (inner as Record<string, unknown>)['scope']\n if (typeof scope === 'object' && scope !== null) return scope as Record<string, unknown>\n }\n }\n return record\n}\n\n/** Unwrap a scopes list envelope. */\nfunction plainScopes(rows: unknown[]): Record<string, unknown>[] {\n return rows.filter((row): row is Record<string, unknown> => typeof row === 'object' && row !== null)\n}\n\n/** Canonical value of view_request (oneOf the two modes + error). */\ntype ViewRequestValue = {\n readonly kind: 'hits'\n readonly hits: SearchHit[]\n readonly total_hits: number\n} | {\n readonly kind: 'page'\n readonly content: string\n readonly page: number\n readonly page_size: number\n readonly total_lines: number\n readonly has_more: boolean\n} | {\n readonly kind: 'error'\n readonly error: string\n}\n\nexport function apply(ctx: Context, config: Config = {}): void {\n const scanId = config.scanId ?? 'pentest'\n const listPageSize = config.listPageSize ?? 50\n const replayTimeoutMs = config.replayTimeoutMs ?? 30_000\n const caidoTimeoutMs = config.caidoTimeoutMs ?? 60_000\n let clientPromise: Promise<CaidoClient> | undefined\n let warnedNoAllowlist = false\n\n /** Authorized targets: Config first, else the preset's scan targets. Empty = fail-open (warned once). */\n const authorizedTargets = (): string[] => {\n if (config.authorizedTargets !== undefined) return config.authorizedTargets\n const preset = ctx.get('pentestPreset') as { authorizedTargets?: Array<{ value: string }> } | undefined\n const values = preset?.authorizedTargets?.map(target => target.value) ?? []\n if (values.length === 0 && !warnedNoAllowlist) {\n warnedNoAllowlist = true\n ctx.logger.warn('pentest-proxy: no authorizedTargets configured; repeat_request target enforcement is inactive (fail-open)')\n }\n return values\n }\n\n /** Lazy client: resolves the session's Caido bootstrap on first use (strix parity). */\n const client = (signal: AbortSignal): Promise<CaidoClient> => {\n // A failed bootstrap stays failed (strix caido_handle parity: every caller\n // sees the same degraded state); the wording is stable so the model gets\n // consistent guidance instead of the raw exception.\n clientPromise ??= (async () => {\n const session = await ctx.pentestSandbox.createSession({ scanId })\n const endpoint = await session.caidoEndpoint()\n return new CaidoClient({ baseUrl: endpoint.baseUrl, token: endpoint.token })\n })().catch(error => {\n throw new Error(`Caido client not available in run context: ${String(error instanceof Error ? error.message : error)}`)\n })\n const settled = clientPromise\n if (settled === undefined) throw new Error('unreachable: client promise just assigned')\n const timeout = new Promise<never>((_resolve, reject) => {\n signal.addEventListener('abort', () => reject(new Error('caido call aborted')), { once: true })\n const timer = setTimeout(() => reject(new Error(`caido call timed out after ${String(caidoTimeoutMs)}ms`)), caidoTimeoutMs)\n void settled.then(() => clearTimeout(timer), () => clearTimeout(timer))\n })\n return Promise.race([settled, timeout])\n }\n\n if (config.requireApproval !== false) {\n void ctx.on('tools/pre-execute', async (exec, next) => {\n if (exec.name === 'repeat_request') {\n // Asking under the 'never' policy is a guaranteed rejection (no user\n // is ever consulted, yet the denial is worded as a user rejection);\n // the authorizedTargets allowlist keeps guarding replay even when\n // the ask is skipped.\n if (policyIsNever(ctx, exec.agent?.session)) return next()\n return { kind: 'ask' as const, reason: 'pentest: replay a captured request against the target' }\n }\n return next()\n })\n }\n\n ctx.tools.register(defineTool({\n name: 'capture_summary',\n description: 'One-call audit of the Caido proxy capture history: exact total request count, newest and oldest timestamps, and per-host request counts. Use it to VERIFY traffic capture — after a probe request in Phase 0, when you suspect requests bypassed the proxy, and before finish_scan reconciliation. total=0 means nothing was captured: report it as a blocked capability line instead of assuming your traffic was recorded. The sandbox Caido container is shared across scans of one host process, so history accumulates: pass the Phase-0 probe\\'s ISO timestamp as `since` to audit only this scan\\'s traffic (the baseline diff), or read the unfiltered count as the whole-container trail.',\n parameters: {\n since: { type: 'string', description: 'Optional ISO timestamp: count only requests captured after it (e.g. the Phase-0 probe time, making the result this scan\\'s traffic).' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n total: { type: 'integer', description: 'Total captured requests in the proxy history.' },\n newest: {\n type: 'object',\n properties: {\n host: { type: 'string' },\n port: { type: 'integer' },\n created_at: { type: 'string', description: 'ISO timestamp.' },\n },\n additionalProperties: false,\n description: 'Most recent captured request (null-ish object fields omitted when history is empty).',\n },\n oldest_at: { type: 'string', description: 'ISO timestamp of the oldest captured request, when any.' },\n hosts: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n host: { type: 'string' },\n requests: { type: 'integer' },\n },\n additionalProperties: false,\n },\n description: 'Per-host captured request counts (sitemap roots), descending.',\n },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; total?: number; newest?: { host: string; port: number } | null; oldest_at?: string; hosts?: Array<{ host: string; requests: number }>; error?: string }\n if (!result.success) return [{ type: 'text', text: `capture_summary failed: ${result.error ?? 'unknown'}` }]\n if ((result.total ?? 0) === 0) {\n return [{ type: 'text', text: 'capture history is EMPTY: 0 requests captured. If you sent HTTP traffic, it bypassed the proxy — treat proxy capture as blocked and report it.' }]\n }\n const newest = result.newest\n const newestText = newest !== undefined && newest !== null ? `${newest.host}:${String(newest.port)}` : 'unknown'\n const hostsText = (result.hosts ?? []).map(host => `${host.host}=${String(host.requests)}`).join(', ')\n return [{\n type: 'text',\n text: `capture active: ${String(result.total)} requests captured (newest ${newestText}, oldest ${result.oldest_at ?? 'unknown'}; hosts: ${hostsText || 'unknown'})`,\n }]\n },\n },\n execute: async (args, exec) => {\n try {\n const caido = await client(exec.signal)\n const since = typeof args.since === 'string' && args.since.trim() !== '' ? args.since.trim() : undefined\n const summary = await caido.captureSummary(exec.signal, undefined, since)\n return {\n success: true,\n total: summary.total,\n ...(summary.newest !== null ? { newest: { host: summary.newest.host, port: summary.newest.port, created_at: summary.newest.createdAt } } : {}),\n ...(summary.oldestAt !== null ? { oldest_at: summary.oldestAt } : {}),\n hosts: summary.hosts.map(host => ({ host: host.host, requests: host.requests })),\n }\n } catch (error) {\n return { success: false, error: error instanceof Error ? error.message : String(error) }\n }\n },\n }))\n\n ctx.tools.register(defineTool({\n name: 'list_requests',\n description: `List captured HTTP requests from the Caido proxy with HTTPQL filtering. HTTPQL: integer fields (resp.code, req.port, id, roundtrip) use eq/gt/gte/lt/lte/ne e.g. 'resp.code.gte:400'; text fields (req.method, req.host, req.path, req.query, req.ext, req.raw) use regex/cont/eq e.g. 'req.path.cont:\"/api/\"'; dates use gt/lt with ISO e.g. 'req.created_at.gt:\"2024-01-01T00:00:00Z\"'; combine with AND/OR; no NOT — use ne/ncont/nregex. String values MUST be quoted, integers MUST NOT. A bare quoted string searches req.raw and resp.raw. Pagination: pass page_info.end_cursor as after.`,\n parameters: {\n httpql_filter: { type: 'string', description: 'Caido HTTPQL query (optional).' },\n first: { type: 'integer', description: `Entries per page (default ${String(listPageSize)}).` },\n after: { type: 'string', description: \"Cursor from a previous response's page_info.end_cursor.\" },\n sort_by: { type: 'string', enum: ['timestamp', 'host', 'method', 'path', 'status_code', 'response_time', 'response_size', 'source'], description: 'Sort key (default timestamp).' },\n sort_order: { type: 'string', enum: ['asc', 'desc'], description: 'Sort order (default desc).' },\n scope_id: { type: 'string', description: 'Restrict to a Caido scope.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n entries: { type: 'array', items: { type: 'object', properties: {}, additionalProperties: true } },\n page_info: { type: 'object', properties: {}, additionalProperties: true },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; entries: unknown[]; error?: string }\n if (!result.success) return [{ type: 'text', text: `list_requests failed: ${result.error ?? 'unknown'}` }]\n return [{ type: 'text', text: `${String(result.entries.length)} entries (cursor pagination via page_info)` }]\n },\n },\n execute: async (args, exec) => {\n try {\n const caido = await client(exec.signal)\n const connection = await caido.listRequests({\n httpqlFilter: args.httpql_filter,\n first: args.first ?? listPageSize,\n after: args.after,\n sortBy: args.sort_by as SortBy | undefined,\n sortOrder: args.sort_order,\n scopeId: args.scope_id,\n }, exec.signal)\n return {\n success: true,\n entries: connection.entries.map(entry => ({\n cursor: entry.cursor,\n request: {\n id: entry.request.id,\n host: entry.request.host,\n port: entry.request.port,\n method: entry.request.method,\n path: entry.request.path,\n query: entry.request.query,\n is_tls: entry.request.tls,\n created_at: entry.request.createdAt,\n },\n response: entry.response === null ? null : {\n id: entry.response.id,\n status_code: entry.response.statusCode,\n length: entry.response.length,\n created_at: entry.response.createdAt,\n },\n })),\n page_info: {\n has_next_page: connection.pageInfo.hasNextPage,\n has_previous_page: connection.pageInfo.hasPreviousPage,\n start_cursor: connection.pageInfo.startCursor,\n end_cursor: connection.pageInfo.endCursor,\n },\n }\n } catch (error) {\n return { success: false, entries: [], page_info: {}, error: String(error instanceof Error ? error.message : error) }\n }\n },\n }))\n\n ctx.tools.register(defineTool({\n name: 'view_request',\n description: `View a captured request or its response raw content, optionally regex-searched. With search_pattern: up to 20 compact hits with before/after context (hunting reflections, leaked URLs, hidden parameters). Without: full content line-paginated (page, page_size). Patterns like '/api/[a-zA-Z0-9._/-]+' (endpoints), 'https?://[^\\\\s<>\"]+' (URLs), '[?&][a-zA-Z0-9_]+=([^&\\\\s]+)' (query params).`,\n parameters: {\n request_id: { type: 'string', required: true, description: 'Request ID from list_requests.' },\n part: { type: 'string', enum: ['request', 'response'], description: 'Which raw half to view (default request).' },\n search_pattern: { type: 'string', description: 'Optional regex; switches to compact hits mode.' },\n page: { type: 'integer', description: '1-indexed page (only without search_pattern).' },\n page_size: { type: 'integer', description: 'Lines per page (default 50).' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n kind: { type: 'string', required: true, enum: ['hits', 'page', 'error'] },\n hits: { type: 'array', items: { type: 'object', properties: {}, additionalProperties: true } },\n total_hits: { type: 'integer' },\n content: { type: 'string' },\n page: { type: 'integer' },\n page_size: { type: 'integer' },\n total_lines: { type: 'integer' },\n has_more: { type: 'boolean' },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as ViewRequestValue\n if (result.kind === 'error') return [{ type: 'text', text: `view_request failed: ${result.error}` }]\n if (result.kind === 'hits') return [{ type: 'text', text: `${String(result.total_hits)} regex hit(s)` }]\n return [{ type: 'text', text: `page ${String(result.page)}/${String(Math.ceil(result.total_lines / Math.max(1, result.page_size)))} of ${String(result.total_lines)} lines` }]\n },\n },\n execute: async (args, exec) => {\n try {\n const caido = await client(exec.signal)\n const stored = await caido.getRequest(args.request_id, exec.signal)\n if (stored === null) return { kind: 'error' as const, error: `Request ${args.request_id} not found` }\n const raw = args.part === 'response' ? stored.responseRaw : stored.requestRaw\n if (raw === null) return { kind: 'error' as const, error: `No raw ${args.part === 'response' ? 'response' : 'request'} for ${args.request_id}` }\n if (args.search_pattern !== undefined && args.search_pattern !== '') {\n const hits = formatSearchHits(raw, args.search_pattern)\n if (hits.error !== undefined) return { kind: 'error' as const, error: hits.error }\n return {\n kind: 'hits' as const,\n hits: hits.hits.map(hit => ({ match: hit.match, position: hit.position, before: hit.before, after: hit.after })),\n total_hits: hits.totalHits,\n }\n }\n const textPage = formatTextPage(raw, args.page ?? 1, args.page_size ?? 50)\n return {\n kind: 'page' as const,\n content: textPage.content,\n page: textPage.page,\n page_size: textPage.pageSize,\n total_lines: textPage.totalLines,\n has_more: textPage.hasMore,\n }\n } catch (error) {\n return { kind: 'error' as const, error: String(error instanceof Error ? error.message : error) }\n }\n },\n }))\n\n ctx.tools.register(defineTool({\n name: 'repeat_request',\n description: `Repeat a captured request, optionally patching fields — the browse→capture→modify→test flow. modifications keys: url (replace), params (query additions), headers (additions), body (replace), cookies (additions). Inherits everything else from the original.`,\n parameters: {\n request_id: { type: 'string', required: true, description: 'ID of the original request (from list_requests).' },\n modifications: {\n type: 'object',\n properties: {\n url: { type: 'string', description: 'Replace the URL.' },\n params: { type: 'object', properties: {}, additionalProperties: true, description: 'Query-string keys to add/update.' },\n headers: { type: 'object', properties: {}, additionalProperties: true, description: 'Headers to add/update.' },\n body: { type: 'string', description: 'Replace the body.' },\n cookies: { type: 'object', properties: {}, additionalProperties: true, description: 'Cookies to add/update.' },\n },\n additionalProperties: false,\n description: 'Patch dict overlaying the original request.',\n },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n status: { type: 'string' },\n session_id: { type: 'string' },\n elapsed_ms: { type: 'integer' },\n response: { type: 'object', properties: {}, additionalProperties: true },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; status?: string; error?: string; response?: { status_code?: number } | null }\n if (!result.success) return [{ type: 'text', text: `repeat_request failed: ${result.error ?? result.status ?? 'unknown'}` }]\n return [{ type: 'text', text: `replay ${result.status ?? 'DONE'}${result.response?.status_code === undefined ? '' : ` → HTTP ${String(result.response.status_code)}`}` }]\n },\n },\n execute: async (args, exec) => {\n try {\n const caido = await client(exec.signal)\n const stored = await caido.getRequest(args.request_id, exec.signal)\n if (stored === null) return { success: false, status: 'ERROR', error: `Request ${args.request_id} not found` }\n // Scope enforcement (beyond strix parity): the replay target must be\n // inside the authorized target list before anything is dispatched.\n const allowlist = authorizedTargets()\n const targetHost = resolveRepeatTargetHost(stored.host, args.modifications as Readonly<Record<string, unknown>> | undefined)\n if (!targetHostAllowed(targetHost, allowlist)) {\n return { success: false, status: 'ERROR', error: `repeat_request refused: target host '${targetHost}' is outside this scan's authorized targets` }\n }\n const replay = await caido.replayRequest(args.request_id, args.modifications, exec.signal, { dispatchTimeoutMs: replayTimeoutMs, pollIntervalMs: 500 })\n if (replay === null) return { success: false, status: 'ERROR', error: `Request ${args.request_id} not found` }\n return {\n success: replay.status === 'DONE',\n status: replay.status,\n session_id: replay.sessionId,\n elapsed_ms: replay.elapsedMs,\n response: replay.response === null ? {} : {\n status_code: replay.response.statusCode,\n length: replay.response.length,\n headers: replay.response.headers,\n body: replay.response.body,\n body_truncated: replay.response.bodyTruncated,\n },\n ...(replay.error !== undefined ? { error: replay.error } : {}),\n }\n } catch (error) {\n return { success: false, status: 'ERROR', error: String(error instanceof Error ? error.message : error) }\n }\n },\n }))\n\n ctx.tools.register(defineTool({\n name: 'list_sitemap',\n description: `Browse Caido's hierarchical sitemap: DOMAIN → DIRECTORY → REQUEST → REQUEST_BODY/REQUEST_QUERY. Start with no parent_id for root domains (optionally scope-filtered); drill in by passing an entry's id as parent_id (depth DIRECT or ALL). Pair with view_sitemap_entry.`,\n parameters: {\n scope_id: { type: 'string', description: 'Limit roots to a Caido scope (only without parent_id).' },\n parent_id: { type: 'string', description: 'Entry ID to expand; omit for root domains.' },\n depth: { type: 'string', enum: ['DIRECT', 'ALL'], description: 'DIRECT children or the full subtree (default DIRECT).' },\n page: { type: 'integer', description: '1-indexed page (30 entries per page, strix parity).' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n payload: { type: 'object', properties: {}, additionalProperties: true },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string }\n return [{ type: 'text', text: result.success ? 'sitemap payload returned' : `list_sitemap failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: async (args, exec) => {\n try {\n const caido = await client(exec.signal)\n const payload = await caido.listSitemap({ scopeId: args.scope_id, parentId: args.parent_id, depth: args.depth }, exec.signal) as Record<string, unknown>\n return { success: true as const, payload: structuredToPlain(pageSitemapPayload(payload, args.page ?? 1)) }\n } catch (error) {\n return { success: false as const, payload: {}, error: String(error instanceof Error ? error.message : error) }\n }\n },\n }))\n\n ctx.tools.register(defineTool({\n name: 'scope_rules',\n description: \"Manage Caido scope rules: get/list/create/update/delete. Glob patterns (*, ?, [abc], [a-z], [^abc]); an EMPTY allowlist ALLOWS ALL; the denylist overrides the allowlist. Scopes feed list_requests' scope_id.\",\n parameters: {\n action: { type: 'string', required: true, enum: ['get', 'list', 'create', 'update', 'delete'], description: 'Scope action.' },\n allowlist: { type: 'array', items: { type: 'string' }, description: 'Allow glob patterns (create/update).' },\n denylist: { type: 'array', items: { type: 'string' }, description: 'Deny glob patterns; overrides the allowlist (create/update).' },\n scope_id: { type: 'string', description: 'Target scope id (get/update/delete).' },\n scope_name: { type: 'string', description: 'Scope name (create/update).' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n scopes: { type: 'array', items: { type: 'object', properties: {}, additionalProperties: true } },\n scope: { type: 'object', properties: {}, additionalProperties: true },\n deleted: { type: 'string' },\n message: { type: 'string' },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string }\n return [{ type: 'text', text: result.success ? 'scope action succeeded' : `scope_rules failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: (async (rawArgs: never, rawExec: never) => {\n const args = rawArgs as never as { action: string; allowlist?: string[]; denylist?: string[]; scope_id?: string; scope_name?: string }\n const exec = rawExec as never as ToolRunContextLike\n try {\n const caido = await client(exec.signal)\n const allowlist = args.allowlist ?? []\n const denylist = args.denylist ?? []\n switch (args.action) {\n case 'list': {\n const payload = await caido.scopeList(exec.signal) as { scopes?: unknown[] }\n return { success: true, scopes: plainScopes(payload.scopes ?? []) }\n }\n case 'get': {\n if (args.scope_id === undefined || args.scope_id === '') return { success: false, error: \"Scope_id is required for action='get'\" }\n return { success: true, scope: plainScope(await caido.scopeGet(args.scope_id, exec.signal)) }\n }\n case 'create': {\n if (args.scope_name === undefined || args.scope_name === '') return { success: false, error: \"Scope_name is required for action='create'\" }\n return { success: true, scope: plainScope(await caido.scopeCreate(args.scope_name, allowlist, denylist, exec.signal)) }\n }\n case 'update': {\n if (args.scope_id === undefined || args.scope_id === '' || args.scope_name === undefined || args.scope_name === '') {\n return { success: false, error: \"Scope_id and scope_name are required for action='update'\" }\n }\n return { success: true, scope: plainScope(await caido.scopeUpdate(args.scope_id, args.scope_name, allowlist, denylist, exec.signal)) }\n }\n case 'delete': {\n if (args.scope_id === undefined || args.scope_id === '') return { success: false, error: \"Scope_id is required for action='delete'\" }\n const payload = await caido.scopeDelete(args.scope_id, exec.signal) as { deleteScope?: { deletedId?: string } }\n return { success: true, deleted: payload.deleteScope?.deletedId ?? args.scope_id, message: `Scope ${args.scope_id} deleted` }\n }\n default:\n return { success: false, error: `Unknown action: ${String(args.action)}` }\n }\n } catch (error) {\n return { success: false, error: `scope_rules failed: ${String(error instanceof Error ? error.message : error)}` }\n }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'view_sitemap_entry',\n description: \"Full detail for a sitemap entry plus its most recent 30 related requests. Pick entry_id from list_sitemap.\",\n parameters: {\n entry_id: { type: 'string', required: true, description: 'ID from list_sitemap (or any nested entry).' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n payload: { type: 'object', properties: {}, additionalProperties: true },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string }\n return [{ type: 'text', text: result.success ? 'sitemap entry payload returned' : `view_sitemap_entry failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: async (args, exec) => {\n try {\n const caido = await client(exec.signal)\n const payload = await caido.viewSitemapEntry(args.entry_id, exec.signal) as Record<string, unknown>\n return { success: true as const, payload: structuredToPlain(payload) }\n } catch (error) {\n return { success: false as const, payload: {}, error: String(error instanceof Error ? error.message : error) }\n }\n },\n }))\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gpzhang2001/sharpkit-proxy",
3
3
  "description": "Caido proxy client + five traffic tools (list_requests/view_request/repeat_request/list_sitemap/view_sitemap_entry) over the sandbox session's lazy bootstrap",
4
- "version": "0.2.1",
4
+ "version": "0.2.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -28,7 +28,7 @@
28
28
  ],
29
29
  "dependencies": {
30
30
  "@deepseek-ai/schemastery": "3.18.2",
31
- "@gpzhang2001/sharpkit-sandbox": "^0.2.1"
31
+ "@gpzhang2001/sharpkit-sandbox": "^0.2.2"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "@deepseek-ai/cordis": "^4.0.2",
package/src/client.ts CHANGED
@@ -59,6 +59,28 @@ const CREATE_REPLAY_SESSION_DOC = `mutation CreateReplaySession($input: CreateRe
59
59
  createReplaySession(input: $input) { session { id } error { __typename } }
60
60
  }`
61
61
 
62
+ /** Capture audit: exact total + newest entry (count aggregate, single edge). */
63
+ const CAPTURE_SUMMARY_DOC = `query CaptureSummary($filter: HTTPQLInput, $scopeId: ID) {
64
+ requests(first: 1, filter: $filter, scopeId: $scopeId, order: {by: CREATED_AT, ordering: DESC}) {
65
+ count { value }
66
+ edges { node { host port createdAt } }
67
+ }
68
+ }`
69
+
70
+ /** Capture audit: oldest timestamp (asc boundary of the same connection). */
71
+ const CAPTURE_OLDEST_DOC = `query CaptureOldest($filter: HTTPQLInput, $scopeId: ID) {
72
+ requests(first: 1, filter: $filter, scopeId: $scopeId, order: {by: CREATED_AT, ordering: ASC}) {
73
+ edges { node { createdAt } }
74
+ }
75
+ }`
76
+
77
+ /** Capture audit: per-domain request counts from the sitemap roots. */
78
+ const CAPTURE_HOSTS_DOC = `query CaptureHosts {
79
+ sitemapRootEntries {
80
+ edges { node { label requests(first: 1) { count { value } } } }
81
+ }
82
+ }`
83
+
62
84
  /** Replay dispatch (strix `replay_send_raw` via the SDK's StartReplayTask). */
63
85
  const START_REPLAY_TASK_DOC = `mutation StartReplayTask($sessionId: ID!, $input: StartReplayTaskInput!) {
64
86
  startReplayTask(sessionId: $sessionId, input: $input) { error { __typename } task { id replayEntry { id } } }
@@ -275,6 +297,55 @@ export class CaidoClient {
275
297
  }
276
298
  }
277
299
 
300
+ /**
301
+ * Capture audit in one call: exact total request count (Caido connection
302
+ * `count` aggregate), newest entry, oldest timestamp, and per-domain
303
+ * counts from the sitemap roots. Used by the capture_summary tool to make
304
+ * "is my traffic actually being captured" a cheap, verifiable question.
305
+ * `since` (ISO timestamp) bounds the audit window via HTTPQL
306
+ * `req.created_at.gt` — the sandbox Caido container is shared across scans
307
+ * of one host process, so a fresh scan records the Phase-0 probe's
308
+ * capture_summary as its baseline and diffs against it.
309
+ */
310
+ async captureSummary(signal: AbortSignal, scopeId?: string | undefined, since?: string | undefined): Promise<{
311
+ readonly total: number
312
+ readonly newest: { readonly host: string; readonly port: number; readonly createdAt: string } | null
313
+ readonly oldestAt: string | null
314
+ readonly hosts: ReadonlyArray<{ readonly host: string; readonly requests: number }>
315
+ }> {
316
+ const vars = {
317
+ ...(scopeId !== undefined && scopeId !== '' ? { scopeId } : {}),
318
+ ...(since !== undefined && since !== '' ? { filter: { code: `req.created_at.gt:"${since}"` } } : {}),
319
+ }
320
+ const [summary, oldest, hosts] = await Promise.all([
321
+ graphql<{ requests: { count: { value: unknown } | null; edges: Array<{ node: { host: unknown; port: unknown; createdAt: unknown } }> } }>(
322
+ this.options, CAPTURE_SUMMARY_DOC, vars, signal,
323
+ ),
324
+ graphql<{ requests: { edges: Array<{ node: { createdAt: unknown } }> } }>(
325
+ this.options, CAPTURE_OLDEST_DOC, vars, signal,
326
+ ),
327
+ graphql<{ sitemapRootEntries: { edges: Array<{ node: { label: unknown; requests: { count: { value: unknown } | null } | null } }> } }>(
328
+ this.options, CAPTURE_HOSTS_DOC, {}, signal,
329
+ ),
330
+ ])
331
+ const iso = (value: unknown): string | null =>
332
+ value === null || value === undefined ? null : new Date(Number(value)).toISOString()
333
+ const edge = summary.requests.edges[0]?.node
334
+ return {
335
+ total: Number(summary.requests.count?.value ?? 0),
336
+ newest: edge === undefined ? null : {
337
+ host: String(edge.host),
338
+ port: Number(edge.port),
339
+ createdAt: iso(edge.createdAt) ?? '',
340
+ },
341
+ oldestAt: iso(oldest.requests.edges[0]?.node.createdAt),
342
+ hosts: hosts.sitemapRootEntries.edges
343
+ .map(root => ({ host: String(root.node.label), requests: Number(root.node.requests?.count?.value ?? 0) }))
344
+ .filter(host => host.requests > 0)
345
+ .sort((a, b) => b.requests - a.requests),
346
+ }
347
+ }
348
+
278
349
  /** Fetch one request with both raw halves (strix get_request_with_client parity: always request both). */
279
350
  async getRequest(requestId: string, signal: AbortSignal): Promise<StoredRequest | null> {
280
351
  const data = await graphql<{ request: Record<string, unknown> | null }>(
package/src/index.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * Caido proxy tools — TS port of strix tools/proxy/tools.py (schemas and
3
3
  * output shaping verbatim; the client half lives in client.ts, replay pure
4
- * helpers in replay.ts). Five tools: list_requests / view_request /
5
- * repeat_request / list_sitemap / view_sitemap_entry. The client resolves
4
+ * helpers in replay.ts). Tools: list_requests / view_request /
5
+ * repeat_request / list_sitemap / view_sitemap_entry / scope_rules, plus
6
+ * capture_summary (one-call capture audit so "is my traffic being recorded"
7
+ * stays a cheap, verifiable question). The client resolves
6
8
  * lazily from the sandbox session's Caido bootstrap (concurrent with scan
7
9
  * start; the first tool call pays the login wait — strix lazy parity).
8
10
  * @module @gpzhang2001/sharpkit-proxy
@@ -24,12 +26,45 @@ export interface Config {
24
26
  readonly replayTimeoutMs?: number
25
27
  /** Client-level per-call timeout (strix @function_tool timeout=60/120). */
26
28
  readonly caidoTimeoutMs?: number
27
- /** Ask-approval gate on repeat_request (fail-closed without an approval service). */
29
+ /**
30
+ * Ask-approval gate on repeat_request (fail-closed without an approval
31
+ * service). Skipped automatically while the approval policy is `'never'` —
32
+ * an ask there is a guaranteed auto-rejection, not an approval request.
33
+ */
28
34
  readonly requireApproval?: boolean
29
35
  /** Authorized target hosts/values; repeat_request refuses hosts outside this list (exact or dot-suffix subdomain). */
30
36
  readonly authorizedTargets?: string[]
31
37
  }
32
38
 
39
+ /**
40
+ * Structural view of the host approval service (`ctx.approval`) — reads the
41
+ * session's effective approval policy without a hard dependency on
42
+ * `@deepseek-ai/dsh-user-approval`, using only stock public API members
43
+ * (`overrideOf` + the plugin's configured default; same pattern as the
44
+ * preset lookup below).
45
+ */
46
+ interface ApprovalPolicySource {
47
+ overrideOf: (session: object) => 'ask' | 'never' | undefined
48
+ config?: { policy?: 'ask' | 'never' }
49
+ }
50
+
51
+ /**
52
+ * Whether the session's approval policy is `'never'` — the deterministic
53
+ * stance where every ask resolves `rejected` without consulting anyone (set
54
+ * by the danger-full-access preset, i.e. a user who pre-trusted the
55
+ * session). An ask under that policy can never be granted — and its denial
56
+ * is even reported as "the user rejected tool" although no user was
57
+ * consulted — so the gate treats it as pre-approval instead of asking:
58
+ * replay targets stay confined to the authorizedTargets allowlist either
59
+ * way.
60
+ */
61
+ function policyIsNever(ctx: Context, session: object | undefined): boolean {
62
+ if (session === undefined) return false
63
+ const approval = ctx.get('approval') as ApprovalPolicySource | undefined
64
+ if (approval === undefined) return false
65
+ return (approval.overrideOf(session) ?? approval.config?.policy ?? 'ask') === 'never'
66
+ }
67
+
33
68
  export const name = 'pentest-tool-proxy'
34
69
 
35
70
  export const inject = ['tools', 'pentestSandbox']
@@ -248,11 +283,90 @@ export function apply(ctx: Context, config: Config = {}): void {
248
283
 
249
284
  if (config.requireApproval !== false) {
250
285
  void ctx.on('tools/pre-execute', async (exec, next) => {
251
- if (exec.name === 'repeat_request') return { kind: 'ask' as const, reason: 'pentest: replay a captured request against the target' }
286
+ if (exec.name === 'repeat_request') {
287
+ // Asking under the 'never' policy is a guaranteed rejection (no user
288
+ // is ever consulted, yet the denial is worded as a user rejection);
289
+ // the authorizedTargets allowlist keeps guarding replay even when
290
+ // the ask is skipped.
291
+ if (policyIsNever(ctx, exec.agent?.session)) return next()
292
+ return { kind: 'ask' as const, reason: 'pentest: replay a captured request against the target' }
293
+ }
252
294
  return next()
253
295
  })
254
296
  }
255
297
 
298
+ ctx.tools.register(defineTool({
299
+ name: 'capture_summary',
300
+ description: 'One-call audit of the Caido proxy capture history: exact total request count, newest and oldest timestamps, and per-host request counts. Use it to VERIFY traffic capture — after a probe request in Phase 0, when you suspect requests bypassed the proxy, and before finish_scan reconciliation. total=0 means nothing was captured: report it as a blocked capability line instead of assuming your traffic was recorded. The sandbox Caido container is shared across scans of one host process, so history accumulates: pass the Phase-0 probe\'s ISO timestamp as `since` to audit only this scan\'s traffic (the baseline diff), or read the unfiltered count as the whole-container trail.',
301
+ parameters: {
302
+ since: { type: 'string', description: 'Optional ISO timestamp: count only requests captured after it (e.g. the Phase-0 probe time, making the result this scan\'s traffic).' },
303
+ },
304
+ output: {
305
+ schema: {
306
+ type: 'object',
307
+ properties: {
308
+ success: { type: 'boolean', required: true },
309
+ total: { type: 'integer', description: 'Total captured requests in the proxy history.' },
310
+ newest: {
311
+ type: 'object',
312
+ properties: {
313
+ host: { type: 'string' },
314
+ port: { type: 'integer' },
315
+ created_at: { type: 'string', description: 'ISO timestamp.' },
316
+ },
317
+ additionalProperties: false,
318
+ description: 'Most recent captured request (null-ish object fields omitted when history is empty).',
319
+ },
320
+ oldest_at: { type: 'string', description: 'ISO timestamp of the oldest captured request, when any.' },
321
+ hosts: {
322
+ type: 'array',
323
+ items: {
324
+ type: 'object',
325
+ properties: {
326
+ host: { type: 'string' },
327
+ requests: { type: 'integer' },
328
+ },
329
+ additionalProperties: false,
330
+ },
331
+ description: 'Per-host captured request counts (sitemap roots), descending.',
332
+ },
333
+ error: { type: 'string' },
334
+ },
335
+ additionalProperties: false,
336
+ },
337
+ render: (_args, value) => {
338
+ const result = value as { success: boolean; total?: number; newest?: { host: string; port: number } | null; oldest_at?: string; hosts?: Array<{ host: string; requests: number }>; error?: string }
339
+ if (!result.success) return [{ type: 'text', text: `capture_summary failed: ${result.error ?? 'unknown'}` }]
340
+ if ((result.total ?? 0) === 0) {
341
+ return [{ type: 'text', text: 'capture history is EMPTY: 0 requests captured. If you sent HTTP traffic, it bypassed the proxy — treat proxy capture as blocked and report it.' }]
342
+ }
343
+ const newest = result.newest
344
+ const newestText = newest !== undefined && newest !== null ? `${newest.host}:${String(newest.port)}` : 'unknown'
345
+ const hostsText = (result.hosts ?? []).map(host => `${host.host}=${String(host.requests)}`).join(', ')
346
+ return [{
347
+ type: 'text',
348
+ text: `capture active: ${String(result.total)} requests captured (newest ${newestText}, oldest ${result.oldest_at ?? 'unknown'}; hosts: ${hostsText || 'unknown'})`,
349
+ }]
350
+ },
351
+ },
352
+ execute: async (args, exec) => {
353
+ try {
354
+ const caido = await client(exec.signal)
355
+ const since = typeof args.since === 'string' && args.since.trim() !== '' ? args.since.trim() : undefined
356
+ const summary = await caido.captureSummary(exec.signal, undefined, since)
357
+ return {
358
+ success: true,
359
+ total: summary.total,
360
+ ...(summary.newest !== null ? { newest: { host: summary.newest.host, port: summary.newest.port, created_at: summary.newest.createdAt } } : {}),
361
+ ...(summary.oldestAt !== null ? { oldest_at: summary.oldestAt } : {}),
362
+ hosts: summary.hosts.map(host => ({ host: host.host, requests: host.requests })),
363
+ }
364
+ } catch (error) {
365
+ return { success: false, error: error instanceof Error ? error.message : String(error) }
366
+ }
367
+ },
368
+ }))
369
+
256
370
  ctx.tools.register(defineTool({
257
371
  name: 'list_requests',
258
372
  description: `List captured HTTP requests from the Caido proxy with HTTPQL filtering. HTTPQL: integer fields (resp.code, req.port, id, roundtrip) use eq/gt/gte/lt/lte/ne e.g. 'resp.code.gte:400'; text fields (req.method, req.host, req.path, req.query, req.ext, req.raw) use regex/cont/eq e.g. 'req.path.cont:"/api/"'; dates use gt/lt with ISO e.g. 'req.created_at.gt:"2024-01-01T00:00:00Z"'; combine with AND/OR; no NOT — use ne/ncont/nregex. String values MUST be quoted, integers MUST NOT. A bare quoted string searches req.raw and resp.raw. Pagination: pass page_info.end_cursor as after.`,