@gpzhang2001/sharpkit-analysis 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.
- package/lib/index.d.ts +4 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +149 -35
- package/lib/index.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +145 -27
- package/src/stores.ts +31 -12
package/lib/index.d.ts
CHANGED
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.ts","names":[],"sources":["../src/stores.d.ts","../src/index.d.ts"],"sourcesContent":["/**\n * Run-scoped JSON mirror stores (strix .state/ mirrors): atomic writes\n * (temp-in-dir + rename), hydrate-on-start, per-store in-memory maps.\n * One instance per scan, owned by the analysis service.\n * @module @gpzhang2001/sharpkit-analysis/stores\n */\n/** A JSON file mirror with atomic write and hydrate. */\nexport declare class JsonMirrorStore {\n private readonly
|
|
1
|
+
{"version":3,"file":"index.ts","names":[],"sources":["../src/stores.d.ts","../src/index.d.ts"],"sourcesContent":["/**\n * Run-scoped JSON mirror stores (strix .state/ mirrors): atomic writes\n * (temp-in-dir + rename), hydrate-on-start, per-store in-memory maps.\n * One instance per scan, owned by the analysis service.\n * @module @gpzhang2001/sharpkit-analysis/stores\n */\n/** A JSON file mirror with atomic write and hydrate. The path may be lazy: a\n * provider is resolved (and re-read) on every persist/hydrate so a store can\n * follow the per-scan run directory resolved at tool-execute time. */\nexport declare class JsonMirrorStore {\n private readonly pathOrProvider;\n private readonly map;\n /** Monotonic per-store counter: unique temp names within one process even\n * under concurrent persist() calls (same pid would otherwise collide and\n * one rename would fail with ENOENT — observed once on 2026-09-22). */\n private tempCounter;\n constructor(path: string | null | (() => string | null | Promise<string | null>));\n private path;\n /** Load the mirror file; corrupt JSON is tolerated as empty on hydrate (resume aid only). */\n hydrate(): Promise<void>;\n /** Persist the whole map atomically (strix `_persist_locked`). A rename\n * loss to a concurrent writer of the same store is tolerated: the in-memory\n * map stays authoritative and the mirror is a resume aid. */\n persist(): Promise<void>;\n get(key: string): Record<string, unknown> | undefined;\n set(key: string, value: Record<string, unknown>): void;\n delete(key: string): boolean;\n values(): Record<string, unknown>[];\n get size(): number;\n}\n/** Generate a 6-hex id with bounded collision retries (strix parity). */\nexport declare function generateId(existing: ReadonlySet<string>): string | null;\n/**\n * Normalize a remote target to its identity (threat_model/tools.py\n * `_normalize_remote_target` + `_normalize_git_remote`): scp-style and\n * scheme'd git remotes map to https URLs, `.git` stripped, trailing slash\n * stripped, host lowercased with default ports collapsed.\n * @param target - the raw target string.\n */\nexport declare function normalizeTargetIdentity(target: string): string;\n","/**\n * Analysis tools — port of strix tools/{threat_model,coverage,notes,\n * thinking}/ + finish_scan: get/save/amend_threat_model (validated\n * sections, size gates, append-only amendments), record/update/\n * list_coverage (duplicate guard, evidence requirements, history),\n * create/list/get/update/delete_note, think, and finish_scan (root-only,\n * four required sections, coverage summary, final artifacts). State lives\n * in per-scan mirror stores under the run dir (.state parity) and is\n * provided as `pentestAnalysis` for the reporting package's coverage\n * document and SARIF bridge.\n * @module @gpzhang2001/sharpkit-analysis\n */\nimport type { Context } from '@deepseek-ai/cordis';\nimport { JsonMirrorStore } from './stores.ts';\nexport { normalizeTargetIdentity } from './stores.ts';\n/** Coverage outcome values (strix VALID_OUTCOMES). */\nexport declare const VALID_OUTCOMES: readonly [\"reported\", \"no_issue_found\", \"ruled_out\", \"not_applicable\", \"needs_follow_up\"];\n/** Note categories (strix `_VALID_NOTE_CATEGORIES`). */\nexport declare const VALID_NOTE_CATEGORIES: readonly [\"general\", \"findings\", \"methodology\", \"questions\", \"plan\", \"wiki\"];\n/** Deployment-tunable configuration. */\nexport interface Config {\n /** Sandbox session cache key shared with the other tool packages. */\n readonly scanId?: string;\n /** Scan targets (threat-model identity snapping). */\n readonly scanTargets?: readonly string[];\n /** Run directory for the .state mirrors (default: the reporting package's\n * per-scan runDirFor() — session cwd + multi-scan isolation; the mirrors sit\n * in <that>/.state alongside the artifacts). */\n readonly runDir?: string;\n /** Whether this composition's agent may call finish_scan (root guard). */\n readonly allowFinish?: boolean;\n}\nexport declare const name = \"pentest-tool-analysis\";\nexport declare const inject: string[];\n/** The per-scan analysis stores exposed to other packages. */\nexport interface AnalysisHandle {\n readonly coverage: JsonMirrorStore;\n readonly threatModels: JsonMirrorStore;\n readonly notes: JsonMirrorStore;\n /** Coverage entries in list order with ids (reporting package consumes). */\n coverageEntries(): Array<Record<string, unknown> & {\n entry_id: string;\n }>;\n outcomeCounts(): Record<string, number>;\n}\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n pentestAnalysis: AnalysisHandle;\n }\n}\nexport declare function apply(ctx: Context, config?: Config): AnalysisHandle;\n"],"mappings":";;AAAA,IAAW,CAAC,mBAAmB;CAAC;OAAS;EAAC;EAAS;EAAS;EAAS;EAAQ;EAAQ;CAAM;CAAG;EAAC;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;CAAE;AAAC;AAE1L,IAAW,CAAC,2BAA2B;CAAC;OAAU,CAAC;CAAG,CAAC,EAAE;AAAC;;;ACC1D,IAAW,CAAC,kBAAkB;CAAC;OAAS,CAAC;CAAG,CAAC;AAAC;AAC9C,IAAW,CAAC,yBAAyB;CAAC;OAAS,CAAC;CAAG,CAAC;AAAC;AACrD,IAAW,CAAC,UAAU;CAAC;OAAS,CAAC;CAAG;EAAC;EAAI;EAAI;EAAI;CAAE;AAAC;AACpD,IAAW,CAAC,QAAQ;CAAC;OAAS,CAAC;CAAG,CAAC;AAAC;AACpC,IAAW,CAAC,UAAU;CAAC;OAAS,CAAC;CAAG,CAAC;AAAC;AACtC,IAAW,CAAC,kBAAkB;CAAC;OAAS;EAAC;EAAiB;EAAiB;EAAiB;EAAQ;EAAO;CAAM;CAAG;EAAC;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;CAAE;AAAC;AACpK,IAAI,CAAC,MAAM;CAAC;OAAS,CAAC,cAAc;CAAG;EAAC;EAAI;EAAI;CAAE;CAAG,WAAW,EAAE;AAAC;AACnE,IAAW,CAAC,SAAS;CAAC;OAAS;EAAC;EAAS;EAAQ;CAAc;CAAG;EAAC;EAAI;EAAI;EAAI;EAAI;CAAE;AAAC"}
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { dirname, join } from "node:path";
|
|
1
|
+
import { dirname, join, resolve } from "node:path";
|
|
2
2
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
3
|
-
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
3
|
+
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
4
4
|
//#region src/stores.ts
|
|
5
5
|
/**
|
|
6
6
|
* Run-scoped JSON mirror stores (strix .state/ mirrors): atomic writes
|
|
@@ -8,31 +8,49 @@ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
|
8
8
|
* One instance per scan, owned by the analysis service.
|
|
9
9
|
* @module @gpzhang2001/sharpkit-analysis/stores
|
|
10
10
|
*/
|
|
11
|
-
/** A JSON file mirror with atomic write and hydrate.
|
|
11
|
+
/** A JSON file mirror with atomic write and hydrate. The path may be lazy: a
|
|
12
|
+
* provider is resolved (and re-read) on every persist/hydrate so a store can
|
|
13
|
+
* follow the per-scan run directory resolved at tool-execute time. */
|
|
12
14
|
var JsonMirrorStore = class {
|
|
13
|
-
|
|
15
|
+
pathOrProvider;
|
|
14
16
|
map = /* @__PURE__ */ new Map();
|
|
17
|
+
/** Monotonic per-store counter: unique temp names within one process even
|
|
18
|
+
* under concurrent persist() calls (same pid would otherwise collide and
|
|
19
|
+
* one rename would fail with ENOENT — observed once on 2026-09-22). */
|
|
20
|
+
tempCounter = 0;
|
|
15
21
|
constructor(path) {
|
|
16
|
-
this.
|
|
22
|
+
this.pathOrProvider = path;
|
|
23
|
+
}
|
|
24
|
+
async path() {
|
|
25
|
+
return typeof this.pathOrProvider === "function" ? await Promise.resolve(this.pathOrProvider()) : this.pathOrProvider;
|
|
17
26
|
}
|
|
18
27
|
/** Load the mirror file; corrupt JSON is tolerated as empty on hydrate (resume aid only). */
|
|
19
28
|
async hydrate() {
|
|
20
|
-
|
|
29
|
+
const path = await this.path();
|
|
30
|
+
if (path === null) return;
|
|
21
31
|
try {
|
|
22
|
-
const raw = await readFile(
|
|
32
|
+
const raw = await readFile(path, "utf8");
|
|
23
33
|
const parsed = JSON.parse(raw);
|
|
24
34
|
if (typeof parsed !== "object" || parsed === null) return;
|
|
25
35
|
for (const [key, value] of Object.entries(parsed)) if (typeof value === "object" && value !== null) this.map.set(key, value);
|
|
26
36
|
} catch {}
|
|
27
37
|
}
|
|
28
|
-
/** Persist the whole map atomically (strix `_persist_locked`).
|
|
38
|
+
/** Persist the whole map atomically (strix `_persist_locked`). A rename
|
|
39
|
+
* loss to a concurrent writer of the same store is tolerated: the in-memory
|
|
40
|
+
* map stays authoritative and the mirror is a resume aid. */
|
|
29
41
|
async persist() {
|
|
30
|
-
|
|
42
|
+
const path = await this.path();
|
|
43
|
+
if (path === null) return;
|
|
31
44
|
const payload = JSON.stringify(Object.fromEntries(this.map), null, 2);
|
|
32
|
-
await mkdir(dirname(
|
|
33
|
-
const temp = `${dirname(
|
|
45
|
+
await mkdir(dirname(path), { recursive: true });
|
|
46
|
+
const temp = `${dirname(path)}/.${join("", path.split("/").pop() ?? "store")}.${process.pid}.${this.tempCounter++}.tmp`;
|
|
34
47
|
await writeFile(temp, payload, "utf8");
|
|
35
|
-
|
|
48
|
+
try {
|
|
49
|
+
await rename(temp, path);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
await rm(temp, { force: true }).catch(() => void 0);
|
|
52
|
+
if (this.map.size > 0) throw error;
|
|
53
|
+
}
|
|
36
54
|
}
|
|
37
55
|
get(key) {
|
|
38
56
|
return this.map.get(key);
|
|
@@ -143,14 +161,35 @@ function isoNow() {
|
|
|
143
161
|
}
|
|
144
162
|
function apply(ctx, config = {}) {
|
|
145
163
|
const scanId = config.scanId ?? "pentest";
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
164
|
+
let activeSession;
|
|
165
|
+
let hydratedFor;
|
|
166
|
+
let hydratePromise;
|
|
167
|
+
const noteExec = async (rawExec) => {
|
|
168
|
+
const session = rawExec?.agent?.session;
|
|
169
|
+
if (session === void 0) return;
|
|
170
|
+
activeSession = session;
|
|
171
|
+
if (hydratedFor === session || hydratePromise !== void 0) return;
|
|
172
|
+
hydratedFor = session;
|
|
173
|
+
hydratePromise = Promise.all([
|
|
174
|
+
coverage.hydrate(),
|
|
175
|
+
threatModels.hydrate(),
|
|
176
|
+
notes.hydrate()
|
|
177
|
+
]).then(() => void 0);
|
|
178
|
+
await hydratePromise;
|
|
179
|
+
hydratePromise = void 0;
|
|
180
|
+
};
|
|
181
|
+
const stateDirOf = async () => {
|
|
182
|
+
if (config.runDir !== void 0) return join(resolve(config.runDir), ".state");
|
|
183
|
+
const runDir = await ctx.pentestReporting?.runDirFor?.(activeSession);
|
|
184
|
+
return runDir === void 0 ? null : join(runDir, ".state");
|
|
185
|
+
};
|
|
186
|
+
const mirrorPath = (name) => async () => {
|
|
187
|
+
const dir = await stateDirOf();
|
|
188
|
+
return dir === null ? null : join(dir, name);
|
|
189
|
+
};
|
|
190
|
+
const coverage = new JsonMirrorStore(mirrorPath("coverage.json"));
|
|
191
|
+
const threatModels = new JsonMirrorStore(mirrorPath("threat_models.json"));
|
|
192
|
+
const notes = new JsonMirrorStore(mirrorPath("notes.json"));
|
|
154
193
|
const coverageEntries = () => coverage.values().map((entry) => ({
|
|
155
194
|
...entry,
|
|
156
195
|
entry_id: String(entry["id"] ?? "")
|
|
@@ -224,7 +263,8 @@ function apply(ctx, config = {}) {
|
|
|
224
263
|
}];
|
|
225
264
|
}
|
|
226
265
|
},
|
|
227
|
-
execute: (async (rawArgs) => {
|
|
266
|
+
execute: (async (rawArgs, rawExec) => {
|
|
267
|
+
await noteExec(rawExec);
|
|
228
268
|
const resolved = resolveTarget(rawArgs.target);
|
|
229
269
|
if ("error" in resolved) return {
|
|
230
270
|
success: false,
|
|
@@ -287,7 +327,8 @@ function apply(ctx, config = {}) {
|
|
|
287
327
|
}];
|
|
288
328
|
}
|
|
289
329
|
},
|
|
290
|
-
execute: (async (rawArgs) => {
|
|
330
|
+
execute: (async (rawArgs, rawExec) => {
|
|
331
|
+
await noteExec(rawExec);
|
|
291
332
|
const args = rawArgs;
|
|
292
333
|
const resolved = resolveTarget(args.target);
|
|
293
334
|
if ("error" in resolved) return {
|
|
@@ -363,7 +404,8 @@ function apply(ctx, config = {}) {
|
|
|
363
404
|
}];
|
|
364
405
|
}
|
|
365
406
|
},
|
|
366
|
-
execute: (async (rawArgs) => {
|
|
407
|
+
execute: (async (rawArgs, rawExec) => {
|
|
408
|
+
await noteExec(rawExec);
|
|
367
409
|
const args = rawArgs;
|
|
368
410
|
const resolved = resolveTarget(args.target);
|
|
369
411
|
if ("error" in resolved) return {
|
|
@@ -470,7 +512,8 @@ function apply(ctx, config = {}) {
|
|
|
470
512
|
}];
|
|
471
513
|
}
|
|
472
514
|
},
|
|
473
|
-
execute: (async (rawArgs) => {
|
|
515
|
+
execute: (async (rawArgs, rawExec) => {
|
|
516
|
+
await noteExec(rawExec);
|
|
474
517
|
const args = rawArgs;
|
|
475
518
|
const surface = (args.surface ?? "").trim();
|
|
476
519
|
const riskArea = (args.risk_area ?? "").trim();
|
|
@@ -564,7 +607,8 @@ function apply(ctx, config = {}) {
|
|
|
564
607
|
}];
|
|
565
608
|
}
|
|
566
609
|
},
|
|
567
|
-
execute: (async (rawArgs) => {
|
|
610
|
+
execute: (async (rawArgs, rawExec) => {
|
|
611
|
+
await noteExec(rawExec);
|
|
568
612
|
const args = rawArgs;
|
|
569
613
|
const entryId = (args.entry_id ?? "").trim();
|
|
570
614
|
const entry = coverage.get(entryId);
|
|
@@ -668,7 +712,8 @@ function apply(ctx, config = {}) {
|
|
|
668
712
|
}];
|
|
669
713
|
}
|
|
670
714
|
},
|
|
671
|
-
execute: (async (rawArgs) => {
|
|
715
|
+
execute: (async (rawArgs, rawExec) => {
|
|
716
|
+
await noteExec(rawExec);
|
|
672
717
|
const args = rawArgs;
|
|
673
718
|
let outcomeFilter;
|
|
674
719
|
if (args.outcome !== void 0 && args.outcome !== "") {
|
|
@@ -758,7 +803,8 @@ function apply(ctx, config = {}) {
|
|
|
758
803
|
}];
|
|
759
804
|
}
|
|
760
805
|
},
|
|
761
|
-
execute: (async (rawArgs) => {
|
|
806
|
+
execute: (async (rawArgs, rawExec) => {
|
|
807
|
+
await noteExec(rawExec);
|
|
762
808
|
const args = rawArgs;
|
|
763
809
|
const title = (args.title ?? "").trim();
|
|
764
810
|
const content = (args.content ?? "").trim();
|
|
@@ -856,7 +902,8 @@ function apply(ctx, config = {}) {
|
|
|
856
902
|
}];
|
|
857
903
|
}
|
|
858
904
|
},
|
|
859
|
-
execute: (async (rawArgs) => {
|
|
905
|
+
execute: (async (rawArgs, rawExec) => {
|
|
906
|
+
await noteExec(rawExec);
|
|
860
907
|
const args = rawArgs;
|
|
861
908
|
const category = args.category?.trim() ?? "";
|
|
862
909
|
const search = args.search?.toLowerCase() ?? "";
|
|
@@ -915,7 +962,8 @@ function apply(ctx, config = {}) {
|
|
|
915
962
|
}];
|
|
916
963
|
}
|
|
917
964
|
},
|
|
918
|
-
execute: (async (rawArgs) => {
|
|
965
|
+
execute: (async (rawArgs, rawExec) => {
|
|
966
|
+
await noteExec(rawExec);
|
|
919
967
|
const noteId = (rawArgs.note_id ?? "").trim();
|
|
920
968
|
if (noteId === "") return {
|
|
921
969
|
success: false,
|
|
@@ -981,7 +1029,8 @@ function apply(ctx, config = {}) {
|
|
|
981
1029
|
}];
|
|
982
1030
|
}
|
|
983
1031
|
},
|
|
984
|
-
execute: (async (rawArgs) => {
|
|
1032
|
+
execute: (async (rawArgs, rawExec) => {
|
|
1033
|
+
await noteExec(rawExec);
|
|
985
1034
|
const args = rawArgs;
|
|
986
1035
|
const noteId = (args.note_id ?? "").trim();
|
|
987
1036
|
const note = notes.get(noteId);
|
|
@@ -1041,7 +1090,8 @@ function apply(ctx, config = {}) {
|
|
|
1041
1090
|
}];
|
|
1042
1091
|
}
|
|
1043
1092
|
},
|
|
1044
|
-
execute: (async (rawArgs) => {
|
|
1093
|
+
execute: (async (rawArgs, rawExec) => {
|
|
1094
|
+
await noteExec(rawExec);
|
|
1045
1095
|
const noteId = (rawArgs.note_id ?? "").trim();
|
|
1046
1096
|
const note = notes.get(noteId);
|
|
1047
1097
|
if (note === void 0) return {
|
|
@@ -1087,7 +1137,8 @@ function apply(ctx, config = {}) {
|
|
|
1087
1137
|
}];
|
|
1088
1138
|
}
|
|
1089
1139
|
},
|
|
1090
|
-
execute: (async (rawArgs) => {
|
|
1140
|
+
execute: (async (rawArgs, rawExec) => {
|
|
1141
|
+
await noteExec(rawExec);
|
|
1091
1142
|
if ((rawArgs.thought ?? "").trim() === "") return {
|
|
1092
1143
|
success: false,
|
|
1093
1144
|
error: "Thought cannot be empty"
|
|
@@ -1098,6 +1149,45 @@ function apply(ctx, config = {}) {
|
|
|
1098
1149
|
};
|
|
1099
1150
|
})
|
|
1100
1151
|
}));
|
|
1152
|
+
/**
|
|
1153
|
+
* Best-effort capture reconciliation at scan close: count the Caido proxy
|
|
1154
|
+
* history for this scan's sandbox session. Structural access (no hard
|
|
1155
|
+
* dependency on the proxy package): pentestSandbox → caidoEndpoint → one
|
|
1156
|
+
* GraphQL count query. Reconciliation must never block or fail scan
|
|
1157
|
+
* completion — but a failure is never silent either: the reason travels
|
|
1158
|
+
* back in `note` (into the tool result, the finish_scan render, and
|
|
1159
|
+
* run.json). The previous `catch → undefined` swallowed a live breakage
|
|
1160
|
+
* for a full day unnoticed (2026-09-22: run.json carried no capture
|
|
1161
|
+
* fields at all while capture_summary worked seconds earlier).
|
|
1162
|
+
*/
|
|
1163
|
+
const proxyCapturedRequests = async () => {
|
|
1164
|
+
const fail = (reason) => {
|
|
1165
|
+
ctx.logger?.warn(`pentest-analysis: capture reconcile failed: ${reason}`);
|
|
1166
|
+
return { note: `Proxy capture count unknown: ${reason}` };
|
|
1167
|
+
};
|
|
1168
|
+
try {
|
|
1169
|
+
const sandbox = ctx.get("pentestSandbox");
|
|
1170
|
+
if (sandbox === void 0) return fail("sandbox service unavailable");
|
|
1171
|
+
const endpoint = await (await sandbox.createSession({ scanId })).caidoEndpoint();
|
|
1172
|
+
const response = await fetch(`${endpoint.baseUrl.replace(/\/$/, "")}/graphql`, {
|
|
1173
|
+
method: "POST",
|
|
1174
|
+
headers: {
|
|
1175
|
+
"content-type": "application/json",
|
|
1176
|
+
authorization: `Bearer ${endpoint.token}`
|
|
1177
|
+
},
|
|
1178
|
+
body: JSON.stringify({ query: "query CaptureReconcile { requests(first: 1, order: {by: CREATED_AT, ordering: DESC}) { count { value } } }" }),
|
|
1179
|
+
signal: AbortSignal.timeout(5e3)
|
|
1180
|
+
});
|
|
1181
|
+
if (!response.ok) return fail(`Caido answered HTTP ${String(response.status)}`);
|
|
1182
|
+
const payload = await response.json();
|
|
1183
|
+
if (payload.errors !== void 0 && payload.errors !== null && payload.errors.length > 0) return fail(`Caido: ${String(payload.errors[0]?.message ?? "GraphQL error")}`);
|
|
1184
|
+
const value = payload.data?.requests?.count?.value;
|
|
1185
|
+
if (value === null || value === void 0) return fail("Caido returned no request count");
|
|
1186
|
+
return { captured: Number(value) };
|
|
1187
|
+
} catch (error) {
|
|
1188
|
+
return fail(error instanceof Error ? error.message : String(error));
|
|
1189
|
+
}
|
|
1190
|
+
};
|
|
1101
1191
|
ctx.tools.register(defineTool({
|
|
1102
1192
|
name: "finish_scan",
|
|
1103
1193
|
description: "Complete the scan: validates the four report sections, records the coverage summary, writes the final artifacts, and marks the scan completed. Root agent only.",
|
|
@@ -1149,6 +1239,12 @@ function apply(ctx, config = {}) {
|
|
|
1149
1239
|
additionalProperties: true
|
|
1150
1240
|
}
|
|
1151
1241
|
},
|
|
1242
|
+
proxy_captured_requests: {
|
|
1243
|
+
type: "integer",
|
|
1244
|
+
description: "Requests in the Caido proxy history at close time (the queryable evidence trail)."
|
|
1245
|
+
},
|
|
1246
|
+
capture_warning: { type: "string" },
|
|
1247
|
+
capture_note: { type: "string" },
|
|
1152
1248
|
warning: { type: "string" },
|
|
1153
1249
|
error: { type: "string" },
|
|
1154
1250
|
errors: {
|
|
@@ -1161,13 +1257,18 @@ function apply(ctx, config = {}) {
|
|
|
1161
1257
|
render: (_args, value) => {
|
|
1162
1258
|
const result = value;
|
|
1163
1259
|
const reason = result.error ?? result.errors?.join("; ") ?? "unknown";
|
|
1260
|
+
if (!result.success) return [{
|
|
1261
|
+
type: "text",
|
|
1262
|
+
text: `finish_scan failed: ${reason}`
|
|
1263
|
+
}];
|
|
1164
1264
|
return [{
|
|
1165
1265
|
type: "text",
|
|
1166
|
-
text: result.
|
|
1266
|
+
text: `scan completed; ${result.capture_warning !== void 0 ? result.capture_warning : result.proxy_captured_requests !== void 0 ? `proxy capture history holds ${String(result.proxy_captured_requests)} requests` : result.capture_note ?? "proxy capture count unknown"}`
|
|
1167
1267
|
}];
|
|
1168
1268
|
}
|
|
1169
1269
|
},
|
|
1170
|
-
execute: (async (rawArgs) => {
|
|
1270
|
+
execute: (async (rawArgs, rawExec) => {
|
|
1271
|
+
await noteExec(rawExec);
|
|
1171
1272
|
const args = rawArgs;
|
|
1172
1273
|
if (config.allowFinish === false) return {
|
|
1173
1274
|
success: false,
|
|
@@ -1205,6 +1306,19 @@ function apply(ctx, config = {}) {
|
|
|
1205
1306
|
summary["unresolved_surfaces"] = unresolved;
|
|
1206
1307
|
}
|
|
1207
1308
|
}
|
|
1309
|
+
const reconcile = await proxyCapturedRequests();
|
|
1310
|
+
const captureExtras = {};
|
|
1311
|
+
if (reconcile.captured !== void 0) {
|
|
1312
|
+
summary["proxy_captured_requests"] = reconcile.captured;
|
|
1313
|
+
captureExtras["proxy_captured_requests"] = reconcile.captured;
|
|
1314
|
+
if (reconcile.captured === 0) {
|
|
1315
|
+
summary["capture_warning"] = "The Caido proxy captured 0 requests for this scan: no queryable traffic history exists, so PoC requests cannot be replayed from history. Report this as a capture-line gap; plaintext HTTP should be captured transparently in sandbox images >= 1.1.0.";
|
|
1316
|
+
captureExtras["capture_warning"] = summary["capture_warning"];
|
|
1317
|
+
}
|
|
1318
|
+
} else {
|
|
1319
|
+
summary["capture_note"] = reconcile.note ?? "Proxy capture count unknown.";
|
|
1320
|
+
captureExtras["capture_note"] = summary["capture_note"];
|
|
1321
|
+
}
|
|
1208
1322
|
const reporting = ctx.pentestReporting;
|
|
1209
1323
|
if (reporting === void 0) return {
|
|
1210
1324
|
success: true,
|
|
@@ -1213,7 +1327,7 @@ function apply(ctx, config = {}) {
|
|
|
1213
1327
|
warning: "Results could not be persisted - report state unavailable",
|
|
1214
1328
|
...summary
|
|
1215
1329
|
};
|
|
1216
|
-
await reporting.finishScan(sections);
|
|
1330
|
+
await reporting.finishScan(sections, "completed", captureExtras);
|
|
1217
1331
|
return {
|
|
1218
1332
|
success: true,
|
|
1219
1333
|
scan_completed: true,
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["args","result"],"sources":["../src/stores.ts","../src/index.ts"],"sourcesContent":["/**\n * Run-scoped JSON mirror stores (strix .state/ mirrors): atomic writes\n * (temp-in-dir + rename), hydrate-on-start, per-store in-memory maps.\n * One instance per scan, owned by the analysis service.\n * @module @gpzhang2001/sharpkit-analysis/stores\n */\n\nimport { mkdir, readFile, rename, writeFile } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\n\n/** A JSON file mirror with atomic write and hydrate. */\nexport class JsonMirrorStore {\n private readonly path: string | null\n private readonly map = new Map<string, Record<string, unknown>>()\n\n constructor(path: string | null) {\n this.path = path\n }\n\n /** Load the mirror file; corrupt JSON is tolerated as empty on hydrate (resume aid only). */\n async hydrate(): Promise<void> {\n if (this.path === null) return\n try {\n const raw = await readFile(this.path, 'utf8')\n const parsed = JSON.parse(raw) as unknown\n if (typeof parsed !== 'object' || parsed === null) return\n for (const [key, value] of Object.entries(parsed as Record<string, unknown>)) {\n if (typeof value === 'object' && value !== null) this.map.set(key, value as Record<string, unknown>)\n }\n } catch {\n // Missing or unreadable mirror: start empty.\n }\n }\n\n /** Persist the whole map atomically (strix `_persist_locked`). */\n async persist(): Promise<void> {\n if (this.path === null) return\n const payload = JSON.stringify(Object.fromEntries(this.map), null, 2)\n await mkdir(dirname(this.path), { recursive: true })\n const temp = `${dirname(this.path)}/.${join('', this.path.split('/').pop() ?? 'store')}.${process.pid}.tmp`\n await writeFile(temp, payload, 'utf8')\n await rename(temp, this.path)\n }\n\n get(key: string): Record<string, unknown> | undefined {\n return this.map.get(key)\n }\n\n set(key: string, value: Record<string, unknown>): void {\n this.map.set(key, value)\n }\n\n delete(key: string): boolean {\n return this.map.delete(key)\n }\n\n values(): Record<string, unknown>[] {\n return [...this.map.values()]\n }\n\n get size(): number {\n return this.map.size\n }\n}\n\n/** Generate a 6-hex id with bounded collision retries (strix parity). */\nexport function generateId(existing: ReadonlySet<string>): string | null {\n for (let attempt = 0; attempt < 1024; attempt++) {\n const id = Math.random().toString(16).slice(2, 8).padEnd(6, '0')\n if (!existing.has(id)) return id\n }\n return null\n}\n\n/**\n * Normalize a remote target to its identity (threat_model/tools.py\n * `_normalize_remote_target` + `_normalize_git_remote`): scp-style and\n * scheme'd git remotes map to https URLs, `.git` stripped, trailing slash\n * stripped, host lowercased with default ports collapsed.\n * @param target - the raw target string.\n */\nexport function normalizeTargetIdentity(target: string): string {\n const trimmed = target.trim()\n const scp = /^git@([^:]+):(.+?)(?:\\.git)?$/.exec(trimmed)\n if (scp !== null) return `https://${scp[1]?.toLowerCase()}/${scp[2]}`\n const withScheme = /^([a-z][a-z0-9+.-]*):\\/\\/([^/?#]+)([^#]*)/i.exec(trimmed)\n if (withScheme !== null) {\n const scheme = withScheme[1] ?? ''\n const authority = withScheme[2] ?? ''\n const path = withScheme[3] ?? ''\n const defaultPort = scheme.toLowerCase() === 'https' ? '443' : scheme.toLowerCase() === 'http' ? '80' : null\n let host = authority.toLowerCase()\n if (defaultPort !== null && host.endsWith(`:${defaultPort}`)) host = host.slice(0, -defaultPort.length - 1)\n return `https://${host}${path.replace(/\\/$/, '')}`.replace(/\\.git$/, '')\n }\n return trimmed.replace(/\\/$/, '').replace(/\\.git$/, '')\n}\n","/**\n * Analysis tools — port of strix tools/{threat_model,coverage,notes,\n * thinking}/ + finish_scan: get/save/amend_threat_model (validated\n * sections, size gates, append-only amendments), record/update/\n * list_coverage (duplicate guard, evidence requirements, history),\n * create/list/get/update/delete_note, think, and finish_scan (root-only,\n * four required sections, coverage summary, final artifacts). State lives\n * in per-scan mirror stores under the run dir (.state parity) and is\n * provided as `pentestAnalysis` for the reporting package's coverage\n * document and SARIF bridge.\n * @module @gpzhang2001/sharpkit-analysis\n */\n\nimport { join } from 'node:path'\nimport type { Context } from '@deepseek-ai/cordis'\nimport { defineTool } from '@deepseek-ai/dsh-tools'\nimport { JsonMirrorStore, generateId, normalizeTargetIdentity } from './stores.ts'\n\nexport { normalizeTargetIdentity } from './stores.ts'\n\n/** Coverage outcome values (strix VALID_OUTCOMES). */\nexport const VALID_OUTCOMES = ['reported', 'no_issue_found', 'ruled_out', 'not_applicable', 'needs_follow_up'] as const\n\n/** Outcomes that require evidence text (strix `_OUTCOMES_REQUIRING_EVIDENCE`). */\nconst OUTCOMES_REQUIRING_EVIDENCE = new Set(['ruled_out', 'not_applicable', 'needs_follow_up'])\n\n/** Note categories (strix `_VALID_NOTE_CATEGORIES`). */\nexport const VALID_NOTE_CATEGORIES = ['general', 'findings', 'methodology', 'questions', 'plan', 'wiki'] as const\n\n/** Required threat-model sections, matched as lowercase substrings (strix parity). */\nconst REQUIRED_SECTIONS = ['overview', 'trust boundaries', 'attack surface', 'severity calibration'] as const\n\n/** Threat model gates (strix constants). */\nconst MAX_MODEL_BYTES = 512 * 1024\nconst MIN_MODEL_CHARS = 400\nconst MIN_AMENDMENT_CHARS = 80\nconst MAX_AMENDMENTS = 40\n\n/** Display timestamp in the coverage format. */\nfunction displayTimestamp(date: Date): string {\n const pad = (value: number): string => String(value).padStart(2, '0')\n return `${date.getUTCFullYear()}-${pad(date.getUTCMonth() + 1)}-${pad(date.getUTCDate())} ${pad(date.getUTCHours())}:${pad(date.getUTCMinutes())}:${pad(date.getUTCSeconds())} UTC`\n}\n\n/** Deployment-tunable configuration. */\nexport interface Config {\n /** Sandbox session cache key shared with the other tool packages. */\n readonly scanId?: string\n /** Scan targets (threat-model identity snapping). */\n readonly scanTargets?: readonly string[]\n /** Run directory for the .state mirrors (default sharpkit_runs/<scanId>/.state). */\n readonly runDir?: string\n /** Whether this composition's agent may call finish_scan (root guard). */\n readonly allowFinish?: boolean\n}\n\nexport const name = 'pentest-tool-analysis'\n\nexport const inject = ['tools', 'pentestReporting']\n\n/** The per-scan analysis stores exposed to other packages. */\nexport interface AnalysisHandle {\n readonly coverage: JsonMirrorStore\n readonly threatModels: JsonMirrorStore\n readonly notes: JsonMirrorStore\n /** Coverage entries in list order with ids (reporting package consumes). */\n coverageEntries(): Array<Record<string, unknown> & { entry_id: string }>\n outcomeCounts(): Record<string, number>\n}\n\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n pentestAnalysis: AnalysisHandle\n }\n}\n\n/** ISO-8601 timestamp (threat model + notes stores). */\nfunction isoNow(): string {\n return new Date().toISOString().replace('Z', '+00:00')\n}\n\nexport function apply(ctx: Context, config: Config = {}): AnalysisHandle {\n const scanId = config.scanId ?? 'pentest'\n const runDir = config.runDir ?? join('sharpkit_runs', scanId)\n const stateDir = join(runDir, '.state')\n const coverage = new JsonMirrorStore(join(stateDir, 'coverage.json'))\n const threatModels = new JsonMirrorStore(join(stateDir, 'threat_models.json'))\n const notes = new JsonMirrorStore(join(stateDir, 'notes.json'))\n void coverage.hydrate()\n void threatModels.hydrate()\n void notes.hydrate()\n\n const coverageEntries = (): Array<Record<string, unknown> & { entry_id: string }> =>\n coverage.values().map(entry => ({ ...entry, entry_id: String(entry['id'] ?? '') })) as Array<Record<string, unknown> & { entry_id: string }>\n\n const outcomeCounts = (): Record<string, number> => {\n const counts: Record<string, number> = {}\n for (const outcome of VALID_OUTCOMES) {\n const count = coverage.values().filter(entry => entry['outcome'] === outcome).length\n if (count > 0) counts[outcome] = count\n }\n return counts\n }\n\n const handle: AnalysisHandle = { coverage, threatModels, notes, coverageEntries, outcomeCounts }\n ctx.provide('pentestAnalysis', handle)\n\n const resolveTarget = (target: string | undefined): { readonly identity: string } | { readonly error: string } => {\n const cleaned = target?.trim() ?? ''\n if (cleaned === '') {\n const targets = config.scanTargets ?? []\n if (targets.length === 1) return { identity: normalizeTargetIdentity(targets[0] ?? '') }\n return { error: 'target cannot be empty - name the target this threat model describes' }\n }\n return { identity: normalizeTargetIdentity(cleaned) }\n }\n\n ctx.tools.register(defineTool({\n name: 'get_threat_model',\n description: 'Fetch the threat model shared for a target in this scan (found=false when none exists yet). Read-only.',\n parameters: {\n target: { type: 'string', description: 'Target the model describes; omit when the scan has exactly one target.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n found: { type: 'boolean' },\n target: { type: 'string' },\n content: { type: 'string' },\n amendments: { type: 'array', items: { type: 'object', properties: {}, additionalProperties: true } },\n amendments_note: { 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; found?: boolean; error?: string }\n if (!result.success) return [{ type: 'text', text: `get_threat_model failed: ${result.error ?? 'unknown'}` }]\n return [{ type: 'text', text: result.found === true ? 'threat model found' : 'no threat model yet' }]\n },\n },\n execute: (async (rawArgs: never) => {\n const args = rawArgs as never as { target?: string }\n const resolved = resolveTarget(args.target)\n if ('error' in resolved) return { success: false, error: resolved.error }\n const model = threatModels.get(resolved.identity)\n if (model === undefined || String(model['content'] ?? '').trim() === '') {\n return { success: true, found: false, target: resolved.identity, message: 'No threat model exists for this target yet. Save one with save_threat_model.' }\n }\n const amendments = model['amendments'] as unknown[] | undefined\n return {\n success: true,\n found: true,\n target: resolved.identity,\n content: String(model['content']),\n ...(amendments !== undefined && amendments.length > 0 ? { amendments, amendments_note: 'Addenda recorded by agents after the model was saved.' } : {}),\n }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'save_threat_model',\n description: `Share your threat model for a target with the whole scan team (full replace; clears amendments). Must cover Overview, Trust Boundaries, Attack Surface, and Severity Calibration; minimum ${String(MIN_MODEL_CHARS)} characters, maximum 512KB.`,\n parameters: {\n target: { type: 'string', description: 'Target the model describes; omit when the scan has exactly one target.' },\n content: { type: 'string', required: true, description: 'The full markdown threat model.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n target: { type: 'string' },\n amendments_cleared: { type: 'integer' },\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 ? 'threat model saved' : `save_threat_model failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: (async (rawArgs: never) => {\n const args = rawArgs as never as { target?: string; content: string }\n const resolved = resolveTarget(args.target)\n if ('error' in resolved) return { success: false, error: resolved.error }\n const content = args.content.trim()\n if (content.length < MIN_MODEL_CHARS) {\n return { success: false, error: `Threat model is too thin (${String(content.length)} chars). It has to be usable by every agent in this scan.` }\n }\n if (Buffer.byteLength(content, 'utf8') > MAX_MODEL_BYTES) return { success: false, error: 'Threat model exceeds 512KB; tighten it.' }\n const lower = content.toLowerCase()\n const missing = REQUIRED_SECTIONS.filter(section => !lower.includes(section))\n if (missing.length > 0) {\n return { success: false, error: `Threat model is missing required section(s): ${missing.join(', ')}. Cover Overview, Trust Boundaries, Attack Surface, and Severity Calibration.` }\n }\n const previous = threatModels.get(resolved.identity)\n const previousAmendments = previous?.['amendments']\n const amendmentsCleared = Array.isArray(previousAmendments) ? previousAmendments.length : 0\n threatModels.set(resolved.identity, {\n target: resolved.identity,\n written_at: isoNow(),\n written_by: null,\n content,\n })\n await threatModels.persist()\n return {\n success: true,\n target: resolved.identity,\n amendments_cleared: amendmentsCleared,\n message: amendmentsCleared > 0\n ? `Threat model shared with this scan. It replaced a previous model, folding ${String(amendmentsCleared)} amendment(s) into the full rewrite.`\n : 'Threat model shared with this scan.',\n }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'amend_threat_model',\n description: `Append an addendum to the existing threat model without rewriting it (minimum ${String(MIN_AMENDMENT_CHARS)} characters). Use save_threat_model for a full rewrite that folds amendments.`,\n parameters: {\n target: { type: 'string', description: 'Target the model describes; omit when the scan has exactly one target.' },\n addendum: { type: 'string', required: true, description: 'The markdown addendum.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n target: { type: 'string' },\n amendment_count: { type: 'integer' },\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 ? 'amendment recorded' : `amend_threat_model failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: (async (rawArgs: never) => {\n const args = rawArgs as never as { target?: string; addendum: string }\n const resolved = resolveTarget(args.target)\n if ('error' in resolved) return { success: false, error: resolved.error }\n const addendum = args.addendum.trim()\n if (addendum.length < MIN_AMENDMENT_CHARS) {\n return { success: false, error: `Amendment is too thin (${String(addendum.length)} chars). Give the new knowledge in full.` }\n }\n const model = threatModels.get(resolved.identity)\n if (model === undefined) return { success: false, error: 'No threat model exists for this target yet. Save the full model with save_threat_model first.' }\n const amendments = (model['amendments'] as unknown[] | undefined) ?? []\n if (amendments.length >= MAX_AMENDMENTS) {\n return { success: false, error: `This threat model already carries ${String(amendments.length)} amendments. Fold them into a full save_threat_model rewrite.` }\n }\n if (Buffer.byteLength(String(model['content']) + addendum, 'utf8') > MAX_MODEL_BYTES) {\n return { success: false, error: 'Combined threat model exceeds 512KB; fold amendments with save_threat_model.' }\n }\n amendments.push({ at: isoNow(), by: null, content: addendum })\n model['amendments'] = amendments\n await threatModels.persist()\n return { success: true, target: resolved.identity, amendment_count: amendments.length, message: 'Amendment recorded. Every agent reading the threat model will see it.' }\n }) as never,\n }))\n\n // ---- coverage ----\n\n interface CoverageValidateInput {\n readonly surface: string\n readonly riskArea: string\n readonly outcome: string\n readonly evidence: string\n }\n\n /** strix coverage `_validate`: normalization + per-field errors. */\n function validateCoverage(input: CoverageValidateInput): { readonly outcome: string } | { readonly errors: string[] } {\n const errors: string[] = []\n if (input.surface === '') errors.push('surface cannot be empty - name the endpoint, route, file, or component')\n if (input.riskArea === '') errors.push('risk_area cannot be empty - name what you were testing for')\n const outcome = input.outcome.trim().toLowerCase().replaceAll('-', '_').replaceAll(' ', '_')\n if (!(VALID_OUTCOMES as readonly string[]).includes(outcome)) {\n errors.push(`Invalid outcome: '${input.outcome}'. Must be one of: [${VALID_OUTCOMES.join(', ')}]`)\n }\n if (OUTCOMES_REQUIRING_EVIDENCE.has(outcome) && input.evidence === '') {\n errors.push(`evidence is required for outcome '${outcome}' - name the specific control, response, or observation that justifies it`)\n }\n return errors.length > 0 ? { errors } : { outcome }\n }\n\n const findCoverageDuplicate = (surface: string, riskArea: string): Record<string, unknown> | undefined =>\n coverage.values().find(entry => String(entry['surface']).toLowerCase() === surface.toLowerCase() && String(entry['risk_area']).toLowerCase() === riskArea.toLowerCase())\n\n ctx.tools.register(defineTool({\n name: 'record_coverage',\n description: \"Record that you exercised an attack surface for a risk area, with an outcome. Outcomes: reported / no_issue_found / ruled_out / not_applicable / needs_follow_up. ruled_out, not_applicable, and needs_follow_up REQUIRE evidence. One entry per (surface, risk_area).\",\n parameters: {\n surface: { type: 'string', required: true, description: 'The endpoint, route, file, or component you exercised.' },\n risk_area: { type: 'string', required: true, description: 'What you were testing for (e.g. \"SQL injection in login\").' },\n outcome: { type: 'string', required: true, enum: [...VALID_OUTCOMES], description: 'The testing outcome.' },\n evidence: { type: 'string', description: 'Concrete observation justifying the outcome (required for some outcomes).' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n entry_id: { type: 'string' },\n outcome: { type: 'string' },\n message: { type: 'string' },\n error: { type: 'string' },\n errors: { type: 'array', items: { type: 'string' } },\n existing_entry_id: { type: 'string' },\n existing_outcome: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string; errors?: string[] }\n const reason = result.error ?? result.errors?.join('; ') ?? 'unknown'\n return [{ type: 'text', text: result.success ? 'coverage recorded' : `record_coverage failed: ${reason}` }]\n },\n },\n execute: (async (rawArgs: never) => {\n const args = rawArgs as never as { surface?: string; risk_area?: string; outcome?: string; evidence?: string }\n const surface = (args.surface ?? '').trim()\n const riskArea = (args.risk_area ?? '').trim()\n const evidence = (args.evidence ?? '').trim()\n const checked = validateCoverage({ surface, riskArea, outcome: args.outcome ?? '', evidence })\n if ('errors' in checked) return { success: false, error: 'Validation failed', errors: checked.errors }\n const duplicate = findCoverageDuplicate(surface, riskArea)\n if (duplicate !== undefined) {\n return {\n success: false,\n error: `'${surface}' (${riskArea}) already has coverage entry ${String(duplicate['id'])}, recorded by ${String(duplicate['agent_name'] ?? 'an agent')} as '${String(duplicate['outcome'])}'. Two rows for the same surface and risk area are never allowed; update_coverage to change the outcome.`,\n existing_entry_id: String(duplicate['id']),\n existing_outcome: String(duplicate['outcome']),\n }\n }\n const id = generateId(new Set(coverage.values().map(entry => String(entry['id'] ?? ''))))\n if (id === null) return { success: false, error: 'could not allocate a coverage entry id' }\n coverage.set(id, {\n id,\n surface,\n risk_area: riskArea,\n outcome: checked.outcome,\n created_at: displayTimestamp(new Date()),\n ...(evidence !== '' ? { evidence } : {}),\n })\n await coverage.persist()\n return { success: true, entry_id: id, outcome: checked.outcome, message: `Coverage recorded for '${surface}' (${checked.outcome})` }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'update_coverage',\n description: \"Move an existing coverage entry to a new outcome (surface and risk_area are never editable). The previous state is kept as history.\",\n parameters: {\n entry_id: { type: 'string', required: true, description: 'Id from record_coverage or list_coverage.' },\n outcome: { type: 'string', required: true, enum: [...VALID_OUTCOMES], description: 'The new outcome.' },\n evidence: { type: 'string', description: 'Evidence for the new outcome (required for some outcomes).' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n entry_id: { type: 'string' },\n previous_outcome: { type: 'string' },\n outcome: { type: 'string' },\n message: { type: 'string' },\n error: { type: 'string' },\n errors: { type: 'array', items: { type: 'string' } },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string; errors?: string[] }\n const reason = result.error ?? result.errors?.join('; ') ?? 'unknown'\n return [{ type: 'text', text: result.success ? 'coverage updated' : `update_coverage failed: ${reason}` }]\n },\n },\n execute: (async (rawArgs: never) => {\n const args = rawArgs as never as { entry_id?: string; outcome?: string; evidence?: string }\n const entryId = (args.entry_id ?? '').trim()\n const entry = coverage.get(entryId)\n if (entry === undefined) return { success: false, error: `No coverage entry '${entryId}'. Call list_coverage to see recorded entries.` }\n const surface = String(entry['surface'] ?? '')\n const riskArea = String(entry['risk_area'] ?? '')\n const evidence = (args.evidence ?? '').trim()\n const checked = validateCoverage({ surface, riskArea, outcome: args.outcome ?? '', evidence })\n if ('errors' in checked) return { success: false, error: 'Validation failed', errors: checked.errors }\n const previousOutcome = String(entry['outcome'])\n const history = (entry['history'] as Array<Record<string, unknown>> | undefined) ?? []\n const prior: Record<string, unknown> = { outcome: previousOutcome, recorded_at: String(entry['created_at']) }\n if (entry['evidence'] !== undefined) prior['evidence'] = entry['evidence']\n history.push(prior)\n entry['history'] = history\n entry['outcome'] = checked.outcome\n entry['updated_at'] = displayTimestamp(new Date())\n if (evidence !== '') entry['evidence'] = evidence\n await coverage.persist()\n return {\n success: true,\n entry_id: entryId,\n previous_outcome: previousOutcome,\n outcome: checked.outcome,\n message: `'${surface}' (${riskArea}) moved from ${previousOutcome} to ${checked.outcome}. The previous state is kept as history.`,\n }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'list_coverage',\n description: 'List coverage entries recorded in this scan (filters compose; outcome counts are over all entries).',\n parameters: {\n outcome: { type: 'string', description: 'Filter to one outcome.' },\n surface: { type: 'string', description: 'Case-insensitive substring match on the surface.' },\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 }, required: true },\n filtered_count: { type: 'integer', required: true },\n total_count: { type: 'integer', required: true },\n outcome_counts: { type: 'object', properties: {}, additionalProperties: true, required: true },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { entries?: unknown[]; error?: string }\n if (result.error !== undefined) return [{ type: 'text', text: `list_coverage failed: ${result.error}` }]\n return [{ type: 'text', text: `${String(result.entries?.length ?? 0)} coverage entry(ies)` }]\n },\n },\n execute: (async (rawArgs: never) => {\n const args = rawArgs as never as { outcome?: string; surface?: string }\n let outcomeFilter: string | undefined\n if (args.outcome !== undefined && args.outcome !== '') {\n outcomeFilter = args.outcome.trim().toLowerCase().replaceAll('-', '_').replaceAll(' ', '_')\n if (outcomeFilter !== undefined && !(VALID_OUTCOMES as readonly string[]).includes(outcomeFilter)) {\n return {\n success: false,\n error: `Invalid outcome: '${args.outcome}'. Must be one of: [${VALID_OUTCOMES.join(', ')}]`,\n entries: [],\n filtered_count: 0,\n total_count: coverage.size,\n outcome_counts: {},\n }\n }\n }\n const surfaceFilter = args.surface?.toLowerCase() ?? ''\n const entries = coverageEntries()\n .filter(entry => (outcomeFilter === undefined || entry['outcome'] === outcomeFilter))\n .filter(entry => surfaceFilter === '' || String(entry['surface']).toLowerCase().includes(surfaceFilter))\n .sort((a, b) => String(a['created_at']).localeCompare(String(b['created_at'])))\n .map(entry => {\n const listing: Record<string, unknown> = {\n entry_id: entry.entry_id,\n surface: entry['surface'],\n risk_area: entry['risk_area'],\n outcome: entry['outcome'],\n created_at: entry['created_at'],\n }\n const evidence = entry['evidence']\n if (typeof evidence === 'string' && evidence !== '') {\n listing['evidence'] = evidence.length > 240 ? `${evidence.slice(0, 240)}...` : evidence\n }\n const history = entry['history'] as Array<Record<string, unknown>> | undefined\n if (history !== undefined && history.length > 0) {\n listing['previous_outcomes'] = history.map(item => item['outcome'])\n }\n return listing\n })\n return {\n success: true,\n entries,\n filtered_count: entries.length,\n total_count: coverage.size,\n outcome_counts: outcomeCounts(),\n }\n }) as never,\n }))\n\n // ---- notes ----\n\n const noteList = (): Array<Record<string, unknown> & { id: string }> =>\n notes.values().map(entry => ({ ...entry, id: String(entry['id'] ?? '') })) as Array<Record<string, unknown> & { id: string }>\n\n ctx.tools.register(defineTool({\n name: 'create_note',\n description: 'Create a persistent note shared across the scan team (categories: general/findings/methodology/questions/plan/wiki).',\n parameters: {\n title: { type: 'string', required: true, description: 'Short note title.' },\n content: { type: 'string', required: true, description: 'The note body.' },\n category: { type: 'string', enum: [...VALID_NOTE_CATEGORIES], description: 'Note category (default general).' },\n tags: { type: 'array', items: { type: 'string' }, description: 'Optional tags.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n note_id: { type: 'string' },\n message: { type: 'string' },\n total_count: { type: 'integer' },\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 ? 'note created' : `create_note failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: (async (rawArgs: never) => {\n const args = rawArgs as never as { title?: string; content?: string; category?: string; tags?: string[] }\n const title = (args.title ?? '').trim()\n const content = (args.content ?? '').trim()\n const category = (args.category ?? 'general').trim()\n if (title === '') return { success: false, error: 'Title cannot be empty' }\n if (content === '') return { success: false, error: 'Content cannot be empty' }\n if (!(VALID_NOTE_CATEGORIES as readonly string[]).includes(category)) {\n return { success: false, error: `Invalid category. Must be one of: ${VALID_NOTE_CATEGORIES.join(', ')}` }\n }\n const id = generateId(new Set(noteList().map(note => note.id)))\n if (id === null) return { success: false, error: 'could not allocate a note id' }\n const now = isoNow()\n notes.set(id, {\n id,\n title,\n content,\n category,\n tags: args.tags ?? [],\n created_at: now,\n updated_at: now,\n })\n await notes.persist()\n return { success: true, note_id: id, message: `Note '${title}' created successfully`, total_count: notes.size }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'list_notes',\n description: 'List notes (filters compose; newest first).',\n parameters: {\n category: { type: 'string', description: 'Exact category filter.' },\n tags: { type: 'array', items: { type: 'string' }, description: 'ANY-match tag filter.' },\n search: { type: 'string', description: 'Substring match on title or content.' },\n include_content: { type: 'boolean', description: 'Full content instead of a 280-char preview.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n notes: { type: 'array', items: { type: 'object', properties: {}, additionalProperties: true }, required: true },\n filtered_count: { type: 'integer', required: true },\n total_count: { type: 'integer', required: true },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { notes?: unknown[] }\n return [{ type: 'text', text: `${String(result.notes?.length ?? 0)} note(s)` }]\n },\n },\n execute: (async (rawArgs: never) => {\n const args = rawArgs as never as { category?: string; tags?: string[]; search?: string; include_content?: boolean }\n const category = args.category?.trim() ?? ''\n const search = args.search?.toLowerCase() ?? ''\n const tags = args.tags ?? []\n const entries = noteList()\n .filter(note => category === '' || note['category'] === category)\n .filter(note => tags.length === 0 || tags.some(tag => (note['tags'] as string[] | undefined)?.includes(tag) === true))\n .filter(note => search === '' || String(note['title']).toLowerCase().includes(search) || String(note['content']).toLowerCase().includes(search))\n .sort((a, b) => String(b['created_at']).localeCompare(String(a['created_at'])))\n .map(note => {\n const listing: Record<string, unknown> = {\n note_id: note.id,\n title: note['title'],\n category: note['category'],\n tags: note['tags'],\n created_at: note['created_at'],\n updated_at: note['updated_at'],\n }\n const content = String(note['content'] ?? '')\n listing[args.include_content === true ? 'content' : 'content_preview'] = args.include_content === true ? content : content.length > 280 ? `${content.slice(0, 280)}...` : content\n return listing\n })\n return { success: true, notes: entries, filtered_count: entries.length, total_count: notes.size }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'get_note',\n description: 'Fetch one note by id.',\n parameters: {\n note_id: { type: 'string', required: true, description: 'Note id from list_notes or create_note.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n note: { 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 ? 'note returned' : `get_note failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: (async (rawArgs: never) => {\n const args = rawArgs as never as { note_id?: string }\n const noteId = (args.note_id ?? '').trim()\n if (noteId === '') return { success: false, error: 'Note ID cannot be empty' }\n const note = notes.get(noteId)\n if (note === undefined) return { success: false, error: `Note with ID '${noteId}' not found` }\n return { success: true, note: { ...note, note_id: noteId } }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'update_note',\n description: 'Revise a note (only the fields you pass change; updated_at always bumps).',\n parameters: {\n note_id: { type: 'string', required: true, description: 'Note id.' },\n title: { type: 'string', description: 'Replacement title.' },\n content: { type: 'string', description: 'Replacement content.' },\n tags: { type: 'array', items: { type: 'string' }, description: 'Replacement tags (full replace).' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n note_id: { type: 'string' },\n message: { type: 'string' },\n total_count: { type: 'integer' },\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 ? 'note updated' : `update_note failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: (async (rawArgs: never) => {\n const args = rawArgs as never as { note_id?: string; title?: string; content?: string; tags?: string[] }\n const noteId = (args.note_id ?? '').trim()\n const note = notes.get(noteId)\n if (note === undefined) return { success: false, error: `Note with ID '${noteId}' not found` }\n if (args.title !== undefined && args.title.trim() === '') return { success: false, error: 'Title cannot be empty' }\n if (args.content !== undefined && args.content.trim() === '') return { success: false, error: 'Content cannot be empty' }\n if (args.title !== undefined) note['title'] = args.title.trim()\n if (args.content !== undefined) note['content'] = args.content.trim()\n if (args.tags !== undefined) note['tags'] = args.tags\n note['updated_at'] = isoNow()\n await notes.persist()\n return { success: true, note_id: noteId, message: `Note '${String(note['title'])}' updated successfully`, total_count: notes.size }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'delete_note',\n description: 'Delete a note by id.',\n parameters: {\n note_id: { type: 'string', required: true, description: 'Note id.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n note_id: { type: 'string' },\n message: { type: 'string' },\n total_count: { type: 'integer' },\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 ? 'note deleted' : `delete_note failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: (async (rawArgs: never) => {\n const args = rawArgs as never as { note_id?: string }\n const noteId = (args.note_id ?? '').trim()\n const note = notes.get(noteId)\n if (note === undefined) return { success: false, error: `Note with ID '${noteId}' not found` }\n notes.delete(noteId)\n await notes.persist()\n return { success: true, note_id: noteId, message: `Note '${String(note['title'])}' deleted successfully`, total_count: notes.size }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'think',\n description: 'Record a private reasoning step (no storage; use notes for persistent knowledge).',\n parameters: {\n thought: { type: 'string', required: true, description: 'The reasoning step.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\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 ? 'recorded' : `think failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: (async (rawArgs: never) => {\n const args = rawArgs as never as { thought?: string }\n if ((args.thought ?? '').trim() === '') return { success: false, error: 'Thought cannot be empty' }\n return { success: true, message: 'Thought recorded' }\n }) as never,\n }))\n\n // ---- finish_scan ----\n\n ctx.tools.register(defineTool({\n name: 'finish_scan',\n description: 'Complete the scan: validates the four report sections, records the coverage summary, writes the final artifacts, and marks the scan completed. Root agent only.',\n parameters: {\n executive_summary: { type: 'string', required: true, description: 'Non-technical summary for stakeholders.' },\n methodology: { type: 'string', required: true, description: 'How the scan was conducted.' },\n technical_analysis: { type: 'string', required: true, description: 'Technical findings analysis.' },\n recommendations: { type: 'string', required: true, description: 'Prioritized remediation recommendations.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n scan_completed: { type: 'boolean' },\n message: { type: 'string' },\n vulnerabilities_found: { type: 'integer' },\n coverage_recorded: { type: 'integer' },\n coverage_outcomes: { type: 'object', properties: {}, additionalProperties: true },\n coverage_warning: { type: 'string' },\n unresolved_surfaces: { type: 'array', items: { type: 'object', properties: {}, additionalProperties: true } },\n warning: { type: 'string' },\n error: { type: 'string' },\n errors: { type: 'array', items: { type: 'string' } },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string; errors?: string[] }\n const reason = result.error ?? result.errors?.join('; ') ?? 'unknown'\n return [{ type: 'text', text: result.success ? 'scan completed' : `finish_scan failed: ${reason}` }]\n },\n },\n execute: (async (rawArgs: never) => {\n const args = rawArgs as never as { executive_summary?: string; methodology?: string; technical_analysis?: string; recommendations?: string }\n if (config.allowFinish === false) {\n return { success: false, scan_completed: false, error: 'This tool can only be used by the root/main agent. If you are a subagent, use agent_finish instead' }\n }\n const sections = {\n executiveSummary: (args.executive_summary ?? '').trim(),\n methodology: (args.methodology ?? '').trim(),\n technicalAnalysis: (args.technical_analysis ?? '').trim(),\n recommendations: (args.recommendations ?? '').trim(),\n }\n const errors: string[] = []\n if (sections.executiveSummary === '') errors.push('Executive summary cannot be empty')\n if (sections.methodology === '') errors.push('Methodology cannot be empty')\n if (sections.technicalAnalysis === '') errors.push('Technical analysis cannot be empty')\n if (sections.recommendations === '') errors.push('Recommendations cannot be empty')\n if (errors.length > 0) return { success: false, error: 'Validation failed', errors }\n const summary: Record<string, unknown> = {\n coverage_recorded: coverage.size,\n coverage_outcomes: outcomeCounts(),\n }\n if (coverage.size === 0) {\n summary['coverage_warning'] = 'No coverage was recorded for this scan. The report cannot state what was and was not tested; record coverage with record_coverage in future scans.'\n } else {\n const unresolved = coverageEntries()\n .filter(entry => entry['outcome'] === 'needs_follow_up')\n .map(entry => ({ surface: entry['surface'], risk_area: entry['risk_area'] }))\n if (unresolved.length > 0) {\n summary['coverage_warning'] = `${String(unresolved.length)} surface(s) still need follow-up; they are listed in unresolved_surfaces.`\n summary['unresolved_surfaces'] = unresolved\n }\n }\n const reporting = (ctx as unknown as { pentestReporting?: { finishScan(sections: { executiveSummary: string; methodology: string; technicalAnalysis: string; recommendations: string }, status?: string): Promise<void>; state: { vulnerabilityReports: unknown[] } } }).pentestReporting\n if (reporting === undefined) {\n return { success: true, scan_completed: true, message: 'Scan completed (not persisted)', warning: 'Results could not be persisted - report state unavailable', ...summary }\n }\n await reporting.finishScan(sections)\n return { success: true, scan_completed: true, message: 'Scan completed successfully', vulnerabilities_found: reporting.state.vulnerabilityReports.length, ...summary }\n }) as never,\n }))\n\n return handle\n}\n\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["args","result"],"sources":["../src/stores.ts","../src/index.ts"],"sourcesContent":["/**\n * Run-scoped JSON mirror stores (strix .state/ mirrors): atomic writes\n * (temp-in-dir + rename), hydrate-on-start, per-store in-memory maps.\n * One instance per scan, owned by the analysis service.\n * @module @gpzhang2001/sharpkit-analysis/stores\n */\n\nimport { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\n\n/** A JSON file mirror with atomic write and hydrate. The path may be lazy: a\n * provider is resolved (and re-read) on every persist/hydrate so a store can\n * follow the per-scan run directory resolved at tool-execute time. */\nexport class JsonMirrorStore {\n private readonly pathOrProvider: string | null | (() => string | null | Promise<string | null>)\n private readonly map = new Map<string, Record<string, unknown>>()\n /** Monotonic per-store counter: unique temp names within one process even\n * under concurrent persist() calls (same pid would otherwise collide and\n * one rename would fail with ENOENT — observed once on 2026-09-22). */\n private tempCounter = 0\n\n constructor(path: string | null | (() => string | null | Promise<string | null>)) {\n this.pathOrProvider = path\n }\n\n private async path(): Promise<string | null> {\n return typeof this.pathOrProvider === 'function' ? await Promise.resolve(this.pathOrProvider()) : this.pathOrProvider\n }\n\n /** Load the mirror file; corrupt JSON is tolerated as empty on hydrate (resume aid only). */\n async hydrate(): Promise<void> {\n const path = await this.path()\n if (path === null) return\n try {\n const raw = await readFile(path, 'utf8')\n const parsed = JSON.parse(raw) as unknown\n if (typeof parsed !== 'object' || parsed === null) return\n for (const [key, value] of Object.entries(parsed as Record<string, unknown>)) {\n if (typeof value === 'object' && value !== null) this.map.set(key, value as Record<string, unknown>)\n }\n } catch {\n // Missing or unreadable mirror: start empty.\n }\n }\n\n /** Persist the whole map atomically (strix `_persist_locked`). A rename\n * loss to a concurrent writer of the same store is tolerated: the in-memory\n * map stays authoritative and the mirror is a resume aid. */\n async persist(): Promise<void> {\n const path = await this.path()\n if (path === null) return\n const payload = JSON.stringify(Object.fromEntries(this.map), null, 2)\n await mkdir(dirname(path), { recursive: true })\n const temp = `${dirname(path)}/.${join('', path.split('/').pop() ?? 'store')}.${process.pid}.${this.tempCounter++}.tmp`\n await writeFile(temp, payload, 'utf8')\n try {\n await rename(temp, path)\n } catch (error) {\n await rm(temp, { force: true }).catch(() => undefined)\n if (this.map.size > 0) throw error\n }\n }\n\n get(key: string): Record<string, unknown> | undefined {\n return this.map.get(key)\n }\n\n set(key: string, value: Record<string, unknown>): void {\n this.map.set(key, value)\n }\n\n delete(key: string): boolean {\n return this.map.delete(key)\n }\n\n values(): Record<string, unknown>[] {\n return [...this.map.values()]\n }\n\n get size(): number {\n return this.map.size\n }\n}\n\n/** Generate a 6-hex id with bounded collision retries (strix parity). */\nexport function generateId(existing: ReadonlySet<string>): string | null {\n for (let attempt = 0; attempt < 1024; attempt++) {\n const id = Math.random().toString(16).slice(2, 8).padEnd(6, '0')\n if (!existing.has(id)) return id\n }\n return null\n}\n\n/**\n * Normalize a remote target to its identity (threat_model/tools.py\n * `_normalize_remote_target` + `_normalize_git_remote`): scp-style and\n * scheme'd git remotes map to https URLs, `.git` stripped, trailing slash\n * stripped, host lowercased with default ports collapsed.\n * @param target - the raw target string.\n */\nexport function normalizeTargetIdentity(target: string): string {\n const trimmed = target.trim()\n const scp = /^git@([^:]+):(.+?)(?:\\.git)?$/.exec(trimmed)\n if (scp !== null) return `https://${scp[1]?.toLowerCase()}/${scp[2]}`\n const withScheme = /^([a-z][a-z0-9+.-]*):\\/\\/([^/?#]+)([^#]*)/i.exec(trimmed)\n if (withScheme !== null) {\n const scheme = withScheme[1] ?? ''\n const authority = withScheme[2] ?? ''\n const path = withScheme[3] ?? ''\n const defaultPort = scheme.toLowerCase() === 'https' ? '443' : scheme.toLowerCase() === 'http' ? '80' : null\n let host = authority.toLowerCase()\n if (defaultPort !== null && host.endsWith(`:${defaultPort}`)) host = host.slice(0, -defaultPort.length - 1)\n return `https://${host}${path.replace(/\\/$/, '')}`.replace(/\\.git$/, '')\n }\n return trimmed.replace(/\\/$/, '').replace(/\\.git$/, '')\n}\n","/**\n * Analysis tools — port of strix tools/{threat_model,coverage,notes,\n * thinking}/ + finish_scan: get/save/amend_threat_model (validated\n * sections, size gates, append-only amendments), record/update/\n * list_coverage (duplicate guard, evidence requirements, history),\n * create/list/get/update/delete_note, think, and finish_scan (root-only,\n * four required sections, coverage summary, final artifacts). State lives\n * in per-scan mirror stores under the run dir (.state parity) and is\n * provided as `pentestAnalysis` for the reporting package's coverage\n * document and SARIF bridge.\n * @module @gpzhang2001/sharpkit-analysis\n */\n\nimport { join, resolve } from 'node:path'\nimport type { Context } from '@deepseek-ai/cordis'\nimport { defineTool } from '@deepseek-ai/dsh-tools'\nimport { JsonMirrorStore, generateId, normalizeTargetIdentity } from './stores.ts'\n\nexport { normalizeTargetIdentity } from './stores.ts'\n\n/** Coverage outcome values (strix VALID_OUTCOMES). */\nexport const VALID_OUTCOMES = ['reported', 'no_issue_found', 'ruled_out', 'not_applicable', 'needs_follow_up'] as const\n\n/** Outcomes that require evidence text (strix `_OUTCOMES_REQUIRING_EVIDENCE`). */\nconst OUTCOMES_REQUIRING_EVIDENCE = new Set(['ruled_out', 'not_applicable', 'needs_follow_up'])\n\n/** Note categories (strix `_VALID_NOTE_CATEGORIES`). */\nexport const VALID_NOTE_CATEGORIES = ['general', 'findings', 'methodology', 'questions', 'plan', 'wiki'] as const\n\n/** Required threat-model sections, matched as lowercase substrings (strix parity). */\nconst REQUIRED_SECTIONS = ['overview', 'trust boundaries', 'attack surface', 'severity calibration'] as const\n\n/** Threat model gates (strix constants). */\nconst MAX_MODEL_BYTES = 512 * 1024\nconst MIN_MODEL_CHARS = 400\nconst MIN_AMENDMENT_CHARS = 80\nconst MAX_AMENDMENTS = 40\n\n/** Display timestamp in the coverage format. */\nfunction displayTimestamp(date: Date): string {\n const pad = (value: number): string => String(value).padStart(2, '0')\n return `${date.getUTCFullYear()}-${pad(date.getUTCMonth() + 1)}-${pad(date.getUTCDate())} ${pad(date.getUTCHours())}:${pad(date.getUTCMinutes())}:${pad(date.getUTCSeconds())} UTC`\n}\n\n/** Deployment-tunable configuration. */\nexport interface Config {\n /** Sandbox session cache key shared with the other tool packages. */\n readonly scanId?: string\n /** Scan targets (threat-model identity snapping). */\n readonly scanTargets?: readonly string[]\n /** Run directory for the .state mirrors (default: the reporting package's\n * per-scan runDirFor() — session cwd + multi-scan isolation; the mirrors sit\n * in <that>/.state alongside the artifacts). */\n readonly runDir?: string\n /** Whether this composition's agent may call finish_scan (root guard). */\n readonly allowFinish?: boolean\n}\n\nexport const name = 'pentest-tool-analysis'\n\nexport const inject = ['tools', 'pentestReporting']\n\n/** The per-scan analysis stores exposed to other packages. */\nexport interface AnalysisHandle {\n readonly coverage: JsonMirrorStore\n readonly threatModels: JsonMirrorStore\n readonly notes: JsonMirrorStore\n /** Coverage entries in list order with ids (reporting package consumes). */\n coverageEntries(): Array<Record<string, unknown> & { entry_id: string }>\n outcomeCounts(): Record<string, number>\n}\n\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n pentestAnalysis: AnalysisHandle\n }\n}\n\n/** ISO-8601 timestamp (threat model + notes stores). */\nfunction isoNow(): string {\n return new Date().toISOString().replace('Z', '+00:00')\n}\n\nexport function apply(ctx: Context, config: Config = {}): AnalysisHandle {\n const scanId = config.scanId ?? 'pentest'\n\n /**\n * The .state mirrors live in the SAME run directory as the reporting\n * artifacts (they previously split off to a fixed 'sharpkit_runs/pentest'\n * while reporting used its configured runName — two directories for one\n * scan). The path resolves lazily per tool execution: an explicit\n * config.runDir wins (tests, E2E); otherwise the reporting package's\n * runDirFor() decides (session cwd + per-scan isolation).\n */\n interface ReportingSource { runDirFor?(session: unknown): Promise<string> }\n let activeSession: unknown\n let hydratedFor: unknown\n let hydratePromise: Promise<void> | undefined\n const noteExec = async (rawExec: never): Promise<void> => {\n const session = (rawExec as { agent?: { session?: unknown } } | undefined)?.agent?.session\n if (session === undefined) return\n activeSession = session\n if (hydratedFor === session || hydratePromise !== undefined) return\n hydratedFor = session\n hydratePromise = Promise.all([coverage.hydrate(), threatModels.hydrate(), notes.hydrate()]).then(() => undefined)\n await hydratePromise\n hydratePromise = undefined\n }\n const stateDirOf = async (): Promise<string | null> => {\n if (config.runDir !== undefined) return join(resolve(config.runDir), '.state')\n const reporting = (ctx as unknown as { pentestReporting?: ReportingSource }).pentestReporting\n const runDir = await reporting?.runDirFor?.(activeSession)\n return runDir === undefined ? null : join(runDir, '.state')\n }\n const mirrorPath = (name: string): (() => Promise<string | null>) => async () => {\n const dir = await stateDirOf()\n return dir === null ? null : join(dir, name)\n }\n const coverage = new JsonMirrorStore(mirrorPath('coverage.json'))\n const threatModels = new JsonMirrorStore(mirrorPath('threat_models.json'))\n const notes = new JsonMirrorStore(mirrorPath('notes.json'))\n\n const coverageEntries = (): Array<Record<string, unknown> & { entry_id: string }> =>\n coverage.values().map(entry => ({ ...entry, entry_id: String(entry['id'] ?? '') })) as Array<Record<string, unknown> & { entry_id: string }>\n\n const outcomeCounts = (): Record<string, number> => {\n const counts: Record<string, number> = {}\n for (const outcome of VALID_OUTCOMES) {\n const count = coverage.values().filter(entry => entry['outcome'] === outcome).length\n if (count > 0) counts[outcome] = count\n }\n return counts\n }\n\n const handle: AnalysisHandle = { coverage, threatModels, notes, coverageEntries, outcomeCounts }\n ctx.provide('pentestAnalysis', handle)\n\n const resolveTarget = (target: string | undefined): { readonly identity: string } | { readonly error: string } => {\n const cleaned = target?.trim() ?? ''\n if (cleaned === '') {\n const targets = config.scanTargets ?? []\n if (targets.length === 1) return { identity: normalizeTargetIdentity(targets[0] ?? '') }\n return { error: 'target cannot be empty - name the target this threat model describes' }\n }\n return { identity: normalizeTargetIdentity(cleaned) }\n }\n\n ctx.tools.register(defineTool({\n name: 'get_threat_model',\n description: 'Fetch the threat model shared for a target in this scan (found=false when none exists yet). Read-only.',\n parameters: {\n target: { type: 'string', description: 'Target the model describes; omit when the scan has exactly one target.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n found: { type: 'boolean' },\n target: { type: 'string' },\n content: { type: 'string' },\n amendments: { type: 'array', items: { type: 'object', properties: {}, additionalProperties: true } },\n amendments_note: { 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; found?: boolean; error?: string }\n if (!result.success) return [{ type: 'text', text: `get_threat_model failed: ${result.error ?? 'unknown'}` }]\n return [{ type: 'text', text: result.found === true ? 'threat model found' : 'no threat model yet' }]\n },\n },\n execute: (async (rawArgs: never, rawExec: never) => {\n await noteExec(rawExec)\n const args = rawArgs as never as { target?: string }\n const resolved = resolveTarget(args.target)\n if ('error' in resolved) return { success: false, error: resolved.error }\n const model = threatModels.get(resolved.identity)\n if (model === undefined || String(model['content'] ?? '').trim() === '') {\n return { success: true, found: false, target: resolved.identity, message: 'No threat model exists for this target yet. Save one with save_threat_model.' }\n }\n const amendments = model['amendments'] as unknown[] | undefined\n return {\n success: true,\n found: true,\n target: resolved.identity,\n content: String(model['content']),\n ...(amendments !== undefined && amendments.length > 0 ? { amendments, amendments_note: 'Addenda recorded by agents after the model was saved.' } : {}),\n }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'save_threat_model',\n description: `Share your threat model for a target with the whole scan team (full replace; clears amendments). Must cover Overview, Trust Boundaries, Attack Surface, and Severity Calibration; minimum ${String(MIN_MODEL_CHARS)} characters, maximum 512KB.`,\n parameters: {\n target: { type: 'string', description: 'Target the model describes; omit when the scan has exactly one target.' },\n content: { type: 'string', required: true, description: 'The full markdown threat model.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n target: { type: 'string' },\n amendments_cleared: { type: 'integer' },\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 ? 'threat model saved' : `save_threat_model failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: (async (rawArgs: never, rawExec: never) => {\n await noteExec(rawExec)\n const args = rawArgs as never as { target?: string; content: string }\n const resolved = resolveTarget(args.target)\n if ('error' in resolved) return { success: false, error: resolved.error }\n const content = args.content.trim()\n if (content.length < MIN_MODEL_CHARS) {\n return { success: false, error: `Threat model is too thin (${String(content.length)} chars). It has to be usable by every agent in this scan.` }\n }\n if (Buffer.byteLength(content, 'utf8') > MAX_MODEL_BYTES) return { success: false, error: 'Threat model exceeds 512KB; tighten it.' }\n const lower = content.toLowerCase()\n const missing = REQUIRED_SECTIONS.filter(section => !lower.includes(section))\n if (missing.length > 0) {\n return { success: false, error: `Threat model is missing required section(s): ${missing.join(', ')}. Cover Overview, Trust Boundaries, Attack Surface, and Severity Calibration.` }\n }\n const previous = threatModels.get(resolved.identity)\n const previousAmendments = previous?.['amendments']\n const amendmentsCleared = Array.isArray(previousAmendments) ? previousAmendments.length : 0\n threatModels.set(resolved.identity, {\n target: resolved.identity,\n written_at: isoNow(),\n written_by: null,\n content,\n })\n await threatModels.persist()\n return {\n success: true,\n target: resolved.identity,\n amendments_cleared: amendmentsCleared,\n message: amendmentsCleared > 0\n ? `Threat model shared with this scan. It replaced a previous model, folding ${String(amendmentsCleared)} amendment(s) into the full rewrite.`\n : 'Threat model shared with this scan.',\n }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'amend_threat_model',\n description: `Append an addendum to the existing threat model without rewriting it (minimum ${String(MIN_AMENDMENT_CHARS)} characters). Use save_threat_model for a full rewrite that folds amendments.`,\n parameters: {\n target: { type: 'string', description: 'Target the model describes; omit when the scan has exactly one target.' },\n addendum: { type: 'string', required: true, description: 'The markdown addendum.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n target: { type: 'string' },\n amendment_count: { type: 'integer' },\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 ? 'amendment recorded' : `amend_threat_model failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: (async (rawArgs: never, rawExec: never) => {\n await noteExec(rawExec)\n const args = rawArgs as never as { target?: string; addendum: string }\n const resolved = resolveTarget(args.target)\n if ('error' in resolved) return { success: false, error: resolved.error }\n const addendum = args.addendum.trim()\n if (addendum.length < MIN_AMENDMENT_CHARS) {\n return { success: false, error: `Amendment is too thin (${String(addendum.length)} chars). Give the new knowledge in full.` }\n }\n const model = threatModels.get(resolved.identity)\n if (model === undefined) return { success: false, error: 'No threat model exists for this target yet. Save the full model with save_threat_model first.' }\n const amendments = (model['amendments'] as unknown[] | undefined) ?? []\n if (amendments.length >= MAX_AMENDMENTS) {\n return { success: false, error: `This threat model already carries ${String(amendments.length)} amendments. Fold them into a full save_threat_model rewrite.` }\n }\n if (Buffer.byteLength(String(model['content']) + addendum, 'utf8') > MAX_MODEL_BYTES) {\n return { success: false, error: 'Combined threat model exceeds 512KB; fold amendments with save_threat_model.' }\n }\n amendments.push({ at: isoNow(), by: null, content: addendum })\n model['amendments'] = amendments\n await threatModels.persist()\n return { success: true, target: resolved.identity, amendment_count: amendments.length, message: 'Amendment recorded. Every agent reading the threat model will see it.' }\n }) as never,\n }))\n\n // ---- coverage ----\n\n interface CoverageValidateInput {\n readonly surface: string\n readonly riskArea: string\n readonly outcome: string\n readonly evidence: string\n }\n\n /** strix coverage `_validate`: normalization + per-field errors. */\n function validateCoverage(input: CoverageValidateInput): { readonly outcome: string } | { readonly errors: string[] } {\n const errors: string[] = []\n if (input.surface === '') errors.push('surface cannot be empty - name the endpoint, route, file, or component')\n if (input.riskArea === '') errors.push('risk_area cannot be empty - name what you were testing for')\n const outcome = input.outcome.trim().toLowerCase().replaceAll('-', '_').replaceAll(' ', '_')\n if (!(VALID_OUTCOMES as readonly string[]).includes(outcome)) {\n errors.push(`Invalid outcome: '${input.outcome}'. Must be one of: [${VALID_OUTCOMES.join(', ')}]`)\n }\n if (OUTCOMES_REQUIRING_EVIDENCE.has(outcome) && input.evidence === '') {\n errors.push(`evidence is required for outcome '${outcome}' - name the specific control, response, or observation that justifies it`)\n }\n return errors.length > 0 ? { errors } : { outcome }\n }\n\n const findCoverageDuplicate = (surface: string, riskArea: string): Record<string, unknown> | undefined =>\n coverage.values().find(entry => String(entry['surface']).toLowerCase() === surface.toLowerCase() && String(entry['risk_area']).toLowerCase() === riskArea.toLowerCase())\n\n ctx.tools.register(defineTool({\n name: 'record_coverage',\n description: \"Record that you exercised an attack surface for a risk area, with an outcome. Outcomes: reported / no_issue_found / ruled_out / not_applicable / needs_follow_up. ruled_out, not_applicable, and needs_follow_up REQUIRE evidence. One entry per (surface, risk_area).\",\n parameters: {\n surface: { type: 'string', required: true, description: 'The endpoint, route, file, or component you exercised.' },\n risk_area: { type: 'string', required: true, description: 'What you were testing for (e.g. \"SQL injection in login\").' },\n outcome: { type: 'string', required: true, enum: [...VALID_OUTCOMES], description: 'The testing outcome.' },\n evidence: { type: 'string', description: 'Concrete observation justifying the outcome (required for some outcomes).' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n entry_id: { type: 'string' },\n outcome: { type: 'string' },\n message: { type: 'string' },\n error: { type: 'string' },\n errors: { type: 'array', items: { type: 'string' } },\n existing_entry_id: { type: 'string' },\n existing_outcome: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string; errors?: string[] }\n const reason = result.error ?? result.errors?.join('; ') ?? 'unknown'\n return [{ type: 'text', text: result.success ? 'coverage recorded' : `record_coverage failed: ${reason}` }]\n },\n },\n execute: (async (rawArgs: never, rawExec: never) => {\n await noteExec(rawExec)\n const args = rawArgs as never as { surface?: string; risk_area?: string; outcome?: string; evidence?: string }\n const surface = (args.surface ?? '').trim()\n const riskArea = (args.risk_area ?? '').trim()\n const evidence = (args.evidence ?? '').trim()\n const checked = validateCoverage({ surface, riskArea, outcome: args.outcome ?? '', evidence })\n if ('errors' in checked) return { success: false, error: 'Validation failed', errors: checked.errors }\n const duplicate = findCoverageDuplicate(surface, riskArea)\n if (duplicate !== undefined) {\n return {\n success: false,\n error: `'${surface}' (${riskArea}) already has coverage entry ${String(duplicate['id'])}, recorded by ${String(duplicate['agent_name'] ?? 'an agent')} as '${String(duplicate['outcome'])}'. Two rows for the same surface and risk area are never allowed; update_coverage to change the outcome.`,\n existing_entry_id: String(duplicate['id']),\n existing_outcome: String(duplicate['outcome']),\n }\n }\n const id = generateId(new Set(coverage.values().map(entry => String(entry['id'] ?? ''))))\n if (id === null) return { success: false, error: 'could not allocate a coverage entry id' }\n coverage.set(id, {\n id,\n surface,\n risk_area: riskArea,\n outcome: checked.outcome,\n created_at: displayTimestamp(new Date()),\n ...(evidence !== '' ? { evidence } : {}),\n })\n await coverage.persist()\n return { success: true, entry_id: id, outcome: checked.outcome, message: `Coverage recorded for '${surface}' (${checked.outcome})` }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'update_coverage',\n description: \"Move an existing coverage entry to a new outcome (surface and risk_area are never editable). The previous state is kept as history.\",\n parameters: {\n entry_id: { type: 'string', required: true, description: 'Id from record_coverage or list_coverage.' },\n outcome: { type: 'string', required: true, enum: [...VALID_OUTCOMES], description: 'The new outcome.' },\n evidence: { type: 'string', description: 'Evidence for the new outcome (required for some outcomes).' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n entry_id: { type: 'string' },\n previous_outcome: { type: 'string' },\n outcome: { type: 'string' },\n message: { type: 'string' },\n error: { type: 'string' },\n errors: { type: 'array', items: { type: 'string' } },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string; errors?: string[] }\n const reason = result.error ?? result.errors?.join('; ') ?? 'unknown'\n return [{ type: 'text', text: result.success ? 'coverage updated' : `update_coverage failed: ${reason}` }]\n },\n },\n execute: (async (rawArgs: never, rawExec: never) => {\n await noteExec(rawExec)\n const args = rawArgs as never as { entry_id?: string; outcome?: string; evidence?: string }\n const entryId = (args.entry_id ?? '').trim()\n const entry = coverage.get(entryId)\n if (entry === undefined) return { success: false, error: `No coverage entry '${entryId}'. Call list_coverage to see recorded entries.` }\n const surface = String(entry['surface'] ?? '')\n const riskArea = String(entry['risk_area'] ?? '')\n const evidence = (args.evidence ?? '').trim()\n const checked = validateCoverage({ surface, riskArea, outcome: args.outcome ?? '', evidence })\n if ('errors' in checked) return { success: false, error: 'Validation failed', errors: checked.errors }\n const previousOutcome = String(entry['outcome'])\n const history = (entry['history'] as Array<Record<string, unknown>> | undefined) ?? []\n const prior: Record<string, unknown> = { outcome: previousOutcome, recorded_at: String(entry['created_at']) }\n if (entry['evidence'] !== undefined) prior['evidence'] = entry['evidence']\n history.push(prior)\n entry['history'] = history\n entry['outcome'] = checked.outcome\n entry['updated_at'] = displayTimestamp(new Date())\n if (evidence !== '') entry['evidence'] = evidence\n await coverage.persist()\n return {\n success: true,\n entry_id: entryId,\n previous_outcome: previousOutcome,\n outcome: checked.outcome,\n message: `'${surface}' (${riskArea}) moved from ${previousOutcome} to ${checked.outcome}. The previous state is kept as history.`,\n }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'list_coverage',\n description: 'List coverage entries recorded in this scan (filters compose; outcome counts are over all entries).',\n parameters: {\n outcome: { type: 'string', description: 'Filter to one outcome.' },\n surface: { type: 'string', description: 'Case-insensitive substring match on the surface.' },\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 }, required: true },\n filtered_count: { type: 'integer', required: true },\n total_count: { type: 'integer', required: true },\n outcome_counts: { type: 'object', properties: {}, additionalProperties: true, required: true },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { entries?: unknown[]; error?: string }\n if (result.error !== undefined) return [{ type: 'text', text: `list_coverage failed: ${result.error}` }]\n return [{ type: 'text', text: `${String(result.entries?.length ?? 0)} coverage entry(ies)` }]\n },\n },\n execute: (async (rawArgs: never, rawExec: never) => {\n await noteExec(rawExec)\n const args = rawArgs as never as { outcome?: string; surface?: string }\n let outcomeFilter: string | undefined\n if (args.outcome !== undefined && args.outcome !== '') {\n outcomeFilter = args.outcome.trim().toLowerCase().replaceAll('-', '_').replaceAll(' ', '_')\n if (outcomeFilter !== undefined && !(VALID_OUTCOMES as readonly string[]).includes(outcomeFilter)) {\n return {\n success: false,\n error: `Invalid outcome: '${args.outcome}'. Must be one of: [${VALID_OUTCOMES.join(', ')}]`,\n entries: [],\n filtered_count: 0,\n total_count: coverage.size,\n outcome_counts: {},\n }\n }\n }\n const surfaceFilter = args.surface?.toLowerCase() ?? ''\n const entries = coverageEntries()\n .filter(entry => (outcomeFilter === undefined || entry['outcome'] === outcomeFilter))\n .filter(entry => surfaceFilter === '' || String(entry['surface']).toLowerCase().includes(surfaceFilter))\n .sort((a, b) => String(a['created_at']).localeCompare(String(b['created_at'])))\n .map(entry => {\n const listing: Record<string, unknown> = {\n entry_id: entry.entry_id,\n surface: entry['surface'],\n risk_area: entry['risk_area'],\n outcome: entry['outcome'],\n created_at: entry['created_at'],\n }\n const evidence = entry['evidence']\n if (typeof evidence === 'string' && evidence !== '') {\n listing['evidence'] = evidence.length > 240 ? `${evidence.slice(0, 240)}...` : evidence\n }\n const history = entry['history'] as Array<Record<string, unknown>> | undefined\n if (history !== undefined && history.length > 0) {\n listing['previous_outcomes'] = history.map(item => item['outcome'])\n }\n return listing\n })\n return {\n success: true,\n entries,\n filtered_count: entries.length,\n total_count: coverage.size,\n outcome_counts: outcomeCounts(),\n }\n }) as never,\n }))\n\n // ---- notes ----\n\n const noteList = (): Array<Record<string, unknown> & { id: string }> =>\n notes.values().map(entry => ({ ...entry, id: String(entry['id'] ?? '') })) as Array<Record<string, unknown> & { id: string }>\n\n ctx.tools.register(defineTool({\n name: 'create_note',\n description: 'Create a persistent note shared across the scan team (categories: general/findings/methodology/questions/plan/wiki).',\n parameters: {\n title: { type: 'string', required: true, description: 'Short note title.' },\n content: { type: 'string', required: true, description: 'The note body.' },\n category: { type: 'string', enum: [...VALID_NOTE_CATEGORIES], description: 'Note category (default general).' },\n tags: { type: 'array', items: { type: 'string' }, description: 'Optional tags.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n note_id: { type: 'string' },\n message: { type: 'string' },\n total_count: { type: 'integer' },\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 ? 'note created' : `create_note failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: (async (rawArgs: never, rawExec: never) => {\n await noteExec(rawExec)\n const args = rawArgs as never as { title?: string; content?: string; category?: string; tags?: string[] }\n const title = (args.title ?? '').trim()\n const content = (args.content ?? '').trim()\n const category = (args.category ?? 'general').trim()\n if (title === '') return { success: false, error: 'Title cannot be empty' }\n if (content === '') return { success: false, error: 'Content cannot be empty' }\n if (!(VALID_NOTE_CATEGORIES as readonly string[]).includes(category)) {\n return { success: false, error: `Invalid category. Must be one of: ${VALID_NOTE_CATEGORIES.join(', ')}` }\n }\n const id = generateId(new Set(noteList().map(note => note.id)))\n if (id === null) return { success: false, error: 'could not allocate a note id' }\n const now = isoNow()\n notes.set(id, {\n id,\n title,\n content,\n category,\n tags: args.tags ?? [],\n created_at: now,\n updated_at: now,\n })\n await notes.persist()\n return { success: true, note_id: id, message: `Note '${title}' created successfully`, total_count: notes.size }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'list_notes',\n description: 'List notes (filters compose; newest first).',\n parameters: {\n category: { type: 'string', description: 'Exact category filter.' },\n tags: { type: 'array', items: { type: 'string' }, description: 'ANY-match tag filter.' },\n search: { type: 'string', description: 'Substring match on title or content.' },\n include_content: { type: 'boolean', description: 'Full content instead of a 280-char preview.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n notes: { type: 'array', items: { type: 'object', properties: {}, additionalProperties: true }, required: true },\n filtered_count: { type: 'integer', required: true },\n total_count: { type: 'integer', required: true },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { notes?: unknown[] }\n return [{ type: 'text', text: `${String(result.notes?.length ?? 0)} note(s)` }]\n },\n },\n execute: (async (rawArgs: never, rawExec: never) => {\n await noteExec(rawExec)\n const args = rawArgs as never as { category?: string; tags?: string[]; search?: string; include_content?: boolean }\n const category = args.category?.trim() ?? ''\n const search = args.search?.toLowerCase() ?? ''\n const tags = args.tags ?? []\n const entries = noteList()\n .filter(note => category === '' || note['category'] === category)\n .filter(note => tags.length === 0 || tags.some(tag => (note['tags'] as string[] | undefined)?.includes(tag) === true))\n .filter(note => search === '' || String(note['title']).toLowerCase().includes(search) || String(note['content']).toLowerCase().includes(search))\n .sort((a, b) => String(b['created_at']).localeCompare(String(a['created_at'])))\n .map(note => {\n const listing: Record<string, unknown> = {\n note_id: note.id,\n title: note['title'],\n category: note['category'],\n tags: note['tags'],\n created_at: note['created_at'],\n updated_at: note['updated_at'],\n }\n const content = String(note['content'] ?? '')\n listing[args.include_content === true ? 'content' : 'content_preview'] = args.include_content === true ? content : content.length > 280 ? `${content.slice(0, 280)}...` : content\n return listing\n })\n return { success: true, notes: entries, filtered_count: entries.length, total_count: notes.size }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'get_note',\n description: 'Fetch one note by id.',\n parameters: {\n note_id: { type: 'string', required: true, description: 'Note id from list_notes or create_note.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n note: { 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 ? 'note returned' : `get_note failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: (async (rawArgs: never, rawExec: never) => {\n await noteExec(rawExec)\n const args = rawArgs as never as { note_id?: string }\n const noteId = (args.note_id ?? '').trim()\n if (noteId === '') return { success: false, error: 'Note ID cannot be empty' }\n const note = notes.get(noteId)\n if (note === undefined) return { success: false, error: `Note with ID '${noteId}' not found` }\n return { success: true, note: { ...note, note_id: noteId } }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'update_note',\n description: 'Revise a note (only the fields you pass change; updated_at always bumps).',\n parameters: {\n note_id: { type: 'string', required: true, description: 'Note id.' },\n title: { type: 'string', description: 'Replacement title.' },\n content: { type: 'string', description: 'Replacement content.' },\n tags: { type: 'array', items: { type: 'string' }, description: 'Replacement tags (full replace).' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n note_id: { type: 'string' },\n message: { type: 'string' },\n total_count: { type: 'integer' },\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 ? 'note updated' : `update_note failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: (async (rawArgs: never, rawExec: never) => {\n await noteExec(rawExec)\n const args = rawArgs as never as { note_id?: string; title?: string; content?: string; tags?: string[] }\n const noteId = (args.note_id ?? '').trim()\n const note = notes.get(noteId)\n if (note === undefined) return { success: false, error: `Note with ID '${noteId}' not found` }\n if (args.title !== undefined && args.title.trim() === '') return { success: false, error: 'Title cannot be empty' }\n if (args.content !== undefined && args.content.trim() === '') return { success: false, error: 'Content cannot be empty' }\n if (args.title !== undefined) note['title'] = args.title.trim()\n if (args.content !== undefined) note['content'] = args.content.trim()\n if (args.tags !== undefined) note['tags'] = args.tags\n note['updated_at'] = isoNow()\n await notes.persist()\n return { success: true, note_id: noteId, message: `Note '${String(note['title'])}' updated successfully`, total_count: notes.size }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'delete_note',\n description: 'Delete a note by id.',\n parameters: {\n note_id: { type: 'string', required: true, description: 'Note id.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n note_id: { type: 'string' },\n message: { type: 'string' },\n total_count: { type: 'integer' },\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 ? 'note deleted' : `delete_note failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: (async (rawArgs: never, rawExec: never) => {\n await noteExec(rawExec)\n const args = rawArgs as never as { note_id?: string }\n const noteId = (args.note_id ?? '').trim()\n const note = notes.get(noteId)\n if (note === undefined) return { success: false, error: `Note with ID '${noteId}' not found` }\n notes.delete(noteId)\n await notes.persist()\n return { success: true, note_id: noteId, message: `Note '${String(note['title'])}' deleted successfully`, total_count: notes.size }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'think',\n description: 'Record a private reasoning step (no storage; use notes for persistent knowledge).',\n parameters: {\n thought: { type: 'string', required: true, description: 'The reasoning step.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\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 ? 'recorded' : `think failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: (async (rawArgs: never, rawExec: never) => {\n await noteExec(rawExec)\n const args = rawArgs as never as { thought?: string }\n if ((args.thought ?? '').trim() === '') return { success: false, error: 'Thought cannot be empty' }\n return { success: true, message: 'Thought recorded' }\n }) as never,\n }))\n\n // ---- finish_scan ----\n\n /**\n * Best-effort capture reconciliation at scan close: count the Caido proxy\n * history for this scan's sandbox session. Structural access (no hard\n * dependency on the proxy package): pentestSandbox → caidoEndpoint → one\n * GraphQL count query. Reconciliation must never block or fail scan\n * completion — but a failure is never silent either: the reason travels\n * back in `note` (into the tool result, the finish_scan render, and\n * run.json). The previous `catch → undefined` swallowed a live breakage\n * for a full day unnoticed (2026-09-22: run.json carried no capture\n * fields at all while capture_summary worked seconds earlier).\n */\n const proxyCapturedRequests = async (): Promise<{ captured?: number; note?: string }> => {\n const fail = (reason: string): { note: string } => {\n ctx.logger?.warn(`pentest-analysis: capture reconcile failed: ${reason}`)\n return { note: `Proxy capture count unknown: ${reason}` }\n }\n try {\n // ctx.get (not property access): the cordis property proxy throws\n // `cannot get property \"pentestSandbox\" without inject` in a real\n // runtime when the service is not in `inject` — this exact breakage\n // hid behind the old silent catch for a full day (2026-09-22).\n // ctx.get reads the service store without the inject requirement\n // (same seam as tool-reporting's ctx.get('pentestAnalysis')).\n const sandbox = ctx.get('pentestSandbox') as\n | { createSession(options: { scanId: string }): Promise<{ caidoEndpoint(): Promise<{ baseUrl: string; token: string }> }> }\n | undefined\n if (sandbox === undefined) return fail('sandbox service unavailable')\n const session = await sandbox.createSession({ scanId })\n const endpoint = await session.caidoEndpoint()\n const response = await fetch(`${endpoint.baseUrl.replace(/\\/$/, '')}/graphql`, {\n method: 'POST',\n headers: { 'content-type': 'application/json', authorization: `Bearer ${endpoint.token}` },\n body: JSON.stringify({\n query: 'query CaptureReconcile { requests(first: 1, order: {by: CREATED_AT, ordering: DESC}) { count { value } } }',\n }),\n signal: AbortSignal.timeout(5_000),\n })\n if (!response.ok) return fail(`Caido answered HTTP ${String(response.status)}`)\n const payload = await response.json() as { data?: { requests?: { count?: { value?: unknown } | null } | null }; errors?: Array<{ message?: unknown }> | null }\n if (payload.errors !== undefined && payload.errors !== null && payload.errors.length > 0) return fail(`Caido: ${String(payload.errors[0]?.message ?? 'GraphQL error')}`)\n const value = payload.data?.requests?.count?.value\n if (value === null || value === undefined) return fail('Caido returned no request count')\n return { captured: Number(value) }\n } catch (error) {\n return fail(error instanceof Error ? error.message : String(error))\n }\n }\n\n ctx.tools.register(defineTool({\n name: 'finish_scan',\n description: 'Complete the scan: validates the four report sections, records the coverage summary, writes the final artifacts, and marks the scan completed. Root agent only.',\n parameters: {\n executive_summary: { type: 'string', required: true, description: 'Non-technical summary for stakeholders.' },\n methodology: { type: 'string', required: true, description: 'How the scan was conducted.' },\n technical_analysis: { type: 'string', required: true, description: 'Technical findings analysis.' },\n recommendations: { type: 'string', required: true, description: 'Prioritized remediation recommendations.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n scan_completed: { type: 'boolean' },\n message: { type: 'string' },\n vulnerabilities_found: { type: 'integer' },\n coverage_recorded: { type: 'integer' },\n coverage_outcomes: { type: 'object', properties: {}, additionalProperties: true },\n coverage_warning: { type: 'string' },\n unresolved_surfaces: { type: 'array', items: { type: 'object', properties: {}, additionalProperties: true } },\n proxy_captured_requests: { type: 'integer', description: 'Requests in the Caido proxy history at close time (the queryable evidence trail).' },\n capture_warning: { type: 'string' },\n capture_note: { type: 'string' },\n warning: { type: 'string' },\n error: { type: 'string' },\n errors: { type: 'array', items: { type: 'string' } },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string; errors?: string[]; proxy_captured_requests?: number; capture_warning?: string; capture_note?: string }\n const reason = result.error ?? result.errors?.join('; ') ?? 'unknown'\n if (!result.success) return [{ type: 'text', text: `finish_scan failed: ${reason}` }]\n // The capture reconciliation travels with the completion line so the\n // agent closes its summary against the real evidence-trail size.\n const capture = result.capture_warning !== undefined\n ? result.capture_warning\n : result.proxy_captured_requests !== undefined\n ? `proxy capture history holds ${String(result.proxy_captured_requests)} requests`\n : (result.capture_note ?? 'proxy capture count unknown')\n return [{ type: 'text', text: `scan completed; ${capture}` }]\n },\n },\n execute: (async (rawArgs: never, rawExec: never) => {\n await noteExec(rawExec)\n const args = rawArgs as never as { executive_summary?: string; methodology?: string; technical_analysis?: string; recommendations?: string }\n if (config.allowFinish === false) {\n return { success: false, scan_completed: false, error: 'This tool can only be used by the root/main agent. If you are a subagent, use agent_finish instead' }\n }\n const sections = {\n executiveSummary: (args.executive_summary ?? '').trim(),\n methodology: (args.methodology ?? '').trim(),\n technicalAnalysis: (args.technical_analysis ?? '').trim(),\n recommendations: (args.recommendations ?? '').trim(),\n }\n const errors: string[] = []\n if (sections.executiveSummary === '') errors.push('Executive summary cannot be empty')\n if (sections.methodology === '') errors.push('Methodology cannot be empty')\n if (sections.technicalAnalysis === '') errors.push('Technical analysis cannot be empty')\n if (sections.recommendations === '') errors.push('Recommendations cannot be empty')\n if (errors.length > 0) return { success: false, error: 'Validation failed', errors }\n const summary: Record<string, unknown> = {\n coverage_recorded: coverage.size,\n coverage_outcomes: outcomeCounts(),\n }\n if (coverage.size === 0) {\n summary['coverage_warning'] = 'No coverage was recorded for this scan. The report cannot state what was and was not tested; record coverage with record_coverage in future scans.'\n } else {\n const unresolved = coverageEntries()\n .filter(entry => entry['outcome'] === 'needs_follow_up')\n .map(entry => ({ surface: entry['surface'], risk_area: entry['risk_area'] }))\n if (unresolved.length > 0) {\n summary['coverage_warning'] = `${String(unresolved.length)} surface(s) still need follow-up; they are listed in unresolved_surfaces.`\n summary['unresolved_surfaces'] = unresolved\n }\n }\n // Capture reconciliation: the proxy history is the hard evidence trail;\n // surface its size (or its absence) instead of letting it stay invisible.\n // Both the count and every failure reason reach run.json via extras.\n const reconcile = await proxyCapturedRequests()\n const captureExtras: Record<string, unknown> = {}\n if (reconcile.captured !== undefined) {\n summary['proxy_captured_requests'] = reconcile.captured\n captureExtras['proxy_captured_requests'] = reconcile.captured\n if (reconcile.captured === 0) {\n summary['capture_warning'] = 'The Caido proxy captured 0 requests for this scan: no queryable traffic history exists, so PoC requests cannot be replayed from history. Report this as a capture-line gap; plaintext HTTP should be captured transparently in sandbox images >= 1.1.0.'\n captureExtras['capture_warning'] = summary['capture_warning']\n }\n } else {\n summary['capture_note'] = reconcile.note ?? 'Proxy capture count unknown.'\n captureExtras['capture_note'] = summary['capture_note']\n }\n const reporting = (ctx as unknown as { pentestReporting?: { finishScan(sections: { executiveSummary: string; methodology: string; technicalAnalysis: string; recommendations: string }, status?: string, extras?: Readonly<Record<string, unknown>>): Promise<void>; state: { vulnerabilityReports: unknown[] } } }).pentestReporting\n if (reporting === undefined) {\n return { success: true, scan_completed: true, message: 'Scan completed (not persisted)', warning: 'Results could not be persisted - report state unavailable', ...summary }\n }\n await reporting.finishScan(sections, 'completed', captureExtras)\n return { success: true, scan_completed: true, message: 'Scan completed successfully', vulnerabilities_found: reporting.state.vulnerabilityReports.length, ...summary }\n }) as never,\n }))\n\n return handle\n}\n\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gpzhang2001/sharpkit-analysis",
|
|
3
3
|
"description": "Threat model / coverage / notes / think tools + finish_scan over per-scan mirror stores",
|
|
4
|
-
"version": "0.2.
|
|
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-reporting": "^0.2.
|
|
31
|
+
"@gpzhang2001/sharpkit-reporting": "^0.2.2"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@deepseek-ai/cordis": "4.0.2",
|
|
40
40
|
"@deepseek-ai/dsh-tools": "0.1.2-rc.1",
|
|
41
|
-
"@gpzhang2001/sharpkit-reporting": "^0.2.
|
|
41
|
+
"@gpzhang2001/sharpkit-reporting": "^0.2.2"
|
|
42
42
|
},
|
|
43
43
|
"main": "lib/index.js",
|
|
44
44
|
"types": "lib/index.d.ts",
|
package/src/index.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* @module @gpzhang2001/sharpkit-analysis
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { join } from 'node:path'
|
|
14
|
+
import { join, resolve } from 'node:path'
|
|
15
15
|
import type { Context } from '@deepseek-ai/cordis'
|
|
16
16
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
17
17
|
import { JsonMirrorStore, generateId, normalizeTargetIdentity } from './stores.ts'
|
|
@@ -48,7 +48,9 @@ export interface Config {
|
|
|
48
48
|
readonly scanId?: string
|
|
49
49
|
/** Scan targets (threat-model identity snapping). */
|
|
50
50
|
readonly scanTargets?: readonly string[]
|
|
51
|
-
/** Run directory for the .state mirrors (default
|
|
51
|
+
/** Run directory for the .state mirrors (default: the reporting package's
|
|
52
|
+
* per-scan runDirFor() — session cwd + multi-scan isolation; the mirrors sit
|
|
53
|
+
* in <that>/.state alongside the artifacts). */
|
|
52
54
|
readonly runDir?: string
|
|
53
55
|
/** Whether this composition's agent may call finish_scan (root guard). */
|
|
54
56
|
readonly allowFinish?: boolean
|
|
@@ -81,14 +83,42 @@ function isoNow(): string {
|
|
|
81
83
|
|
|
82
84
|
export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
|
|
83
85
|
const scanId = config.scanId ?? 'pentest'
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The .state mirrors live in the SAME run directory as the reporting
|
|
89
|
+
* artifacts (they previously split off to a fixed 'sharpkit_runs/pentest'
|
|
90
|
+
* while reporting used its configured runName — two directories for one
|
|
91
|
+
* scan). The path resolves lazily per tool execution: an explicit
|
|
92
|
+
* config.runDir wins (tests, E2E); otherwise the reporting package's
|
|
93
|
+
* runDirFor() decides (session cwd + per-scan isolation).
|
|
94
|
+
*/
|
|
95
|
+
interface ReportingSource { runDirFor?(session: unknown): Promise<string> }
|
|
96
|
+
let activeSession: unknown
|
|
97
|
+
let hydratedFor: unknown
|
|
98
|
+
let hydratePromise: Promise<void> | undefined
|
|
99
|
+
const noteExec = async (rawExec: never): Promise<void> => {
|
|
100
|
+
const session = (rawExec as { agent?: { session?: unknown } } | undefined)?.agent?.session
|
|
101
|
+
if (session === undefined) return
|
|
102
|
+
activeSession = session
|
|
103
|
+
if (hydratedFor === session || hydratePromise !== undefined) return
|
|
104
|
+
hydratedFor = session
|
|
105
|
+
hydratePromise = Promise.all([coverage.hydrate(), threatModels.hydrate(), notes.hydrate()]).then(() => undefined)
|
|
106
|
+
await hydratePromise
|
|
107
|
+
hydratePromise = undefined
|
|
108
|
+
}
|
|
109
|
+
const stateDirOf = async (): Promise<string | null> => {
|
|
110
|
+
if (config.runDir !== undefined) return join(resolve(config.runDir), '.state')
|
|
111
|
+
const reporting = (ctx as unknown as { pentestReporting?: ReportingSource }).pentestReporting
|
|
112
|
+
const runDir = await reporting?.runDirFor?.(activeSession)
|
|
113
|
+
return runDir === undefined ? null : join(runDir, '.state')
|
|
114
|
+
}
|
|
115
|
+
const mirrorPath = (name: string): (() => Promise<string | null>) => async () => {
|
|
116
|
+
const dir = await stateDirOf()
|
|
117
|
+
return dir === null ? null : join(dir, name)
|
|
118
|
+
}
|
|
119
|
+
const coverage = new JsonMirrorStore(mirrorPath('coverage.json'))
|
|
120
|
+
const threatModels = new JsonMirrorStore(mirrorPath('threat_models.json'))
|
|
121
|
+
const notes = new JsonMirrorStore(mirrorPath('notes.json'))
|
|
92
122
|
|
|
93
123
|
const coverageEntries = (): Array<Record<string, unknown> & { entry_id: string }> =>
|
|
94
124
|
coverage.values().map(entry => ({ ...entry, entry_id: String(entry['id'] ?? '') })) as Array<Record<string, unknown> & { entry_id: string }>
|
|
@@ -142,7 +172,8 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
|
|
|
142
172
|
return [{ type: 'text', text: result.found === true ? 'threat model found' : 'no threat model yet' }]
|
|
143
173
|
},
|
|
144
174
|
},
|
|
145
|
-
execute: (async (rawArgs: never) => {
|
|
175
|
+
execute: (async (rawArgs: never, rawExec: never) => {
|
|
176
|
+
await noteExec(rawExec)
|
|
146
177
|
const args = rawArgs as never as { target?: string }
|
|
147
178
|
const resolved = resolveTarget(args.target)
|
|
148
179
|
if ('error' in resolved) return { success: false, error: resolved.error }
|
|
@@ -185,7 +216,8 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
|
|
|
185
216
|
return [{ type: 'text', text: result.success ? 'threat model saved' : `save_threat_model failed: ${result.error ?? 'unknown'}` }]
|
|
186
217
|
},
|
|
187
218
|
},
|
|
188
|
-
execute: (async (rawArgs: never) => {
|
|
219
|
+
execute: (async (rawArgs: never, rawExec: never) => {
|
|
220
|
+
await noteExec(rawExec)
|
|
189
221
|
const args = rawArgs as never as { target?: string; content: string }
|
|
190
222
|
const resolved = resolveTarget(args.target)
|
|
191
223
|
if ('error' in resolved) return { success: false, error: resolved.error }
|
|
@@ -244,7 +276,8 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
|
|
|
244
276
|
return [{ type: 'text', text: result.success ? 'amendment recorded' : `amend_threat_model failed: ${result.error ?? 'unknown'}` }]
|
|
245
277
|
},
|
|
246
278
|
},
|
|
247
|
-
execute: (async (rawArgs: never) => {
|
|
279
|
+
execute: (async (rawArgs: never, rawExec: never) => {
|
|
280
|
+
await noteExec(rawExec)
|
|
248
281
|
const args = rawArgs as never as { target?: string; addendum: string }
|
|
249
282
|
const resolved = resolveTarget(args.target)
|
|
250
283
|
if ('error' in resolved) return { success: false, error: resolved.error }
|
|
@@ -325,7 +358,8 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
|
|
|
325
358
|
return [{ type: 'text', text: result.success ? 'coverage recorded' : `record_coverage failed: ${reason}` }]
|
|
326
359
|
},
|
|
327
360
|
},
|
|
328
|
-
execute: (async (rawArgs: never) => {
|
|
361
|
+
execute: (async (rawArgs: never, rawExec: never) => {
|
|
362
|
+
await noteExec(rawExec)
|
|
329
363
|
const args = rawArgs as never as { surface?: string; risk_area?: string; outcome?: string; evidence?: string }
|
|
330
364
|
const surface = (args.surface ?? '').trim()
|
|
331
365
|
const riskArea = (args.risk_area ?? '').trim()
|
|
@@ -384,7 +418,8 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
|
|
|
384
418
|
return [{ type: 'text', text: result.success ? 'coverage updated' : `update_coverage failed: ${reason}` }]
|
|
385
419
|
},
|
|
386
420
|
},
|
|
387
|
-
execute: (async (rawArgs: never) => {
|
|
421
|
+
execute: (async (rawArgs: never, rawExec: never) => {
|
|
422
|
+
await noteExec(rawExec)
|
|
388
423
|
const args = rawArgs as never as { entry_id?: string; outcome?: string; evidence?: string }
|
|
389
424
|
const entryId = (args.entry_id ?? '').trim()
|
|
390
425
|
const entry = coverage.get(entryId)
|
|
@@ -440,7 +475,8 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
|
|
|
440
475
|
return [{ type: 'text', text: `${String(result.entries?.length ?? 0)} coverage entry(ies)` }]
|
|
441
476
|
},
|
|
442
477
|
},
|
|
443
|
-
execute: (async (rawArgs: never) => {
|
|
478
|
+
execute: (async (rawArgs: never, rawExec: never) => {
|
|
479
|
+
await noteExec(rawExec)
|
|
444
480
|
const args = rawArgs as never as { outcome?: string; surface?: string }
|
|
445
481
|
let outcomeFilter: string | undefined
|
|
446
482
|
if (args.outcome !== undefined && args.outcome !== '') {
|
|
@@ -520,7 +556,8 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
|
|
|
520
556
|
return [{ type: 'text', text: result.success ? 'note created' : `create_note failed: ${result.error ?? 'unknown'}` }]
|
|
521
557
|
},
|
|
522
558
|
},
|
|
523
|
-
execute: (async (rawArgs: never) => {
|
|
559
|
+
execute: (async (rawArgs: never, rawExec: never) => {
|
|
560
|
+
await noteExec(rawExec)
|
|
524
561
|
const args = rawArgs as never as { title?: string; content?: string; category?: string; tags?: string[] }
|
|
525
562
|
const title = (args.title ?? '').trim()
|
|
526
563
|
const content = (args.content ?? '').trim()
|
|
@@ -572,7 +609,8 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
|
|
|
572
609
|
return [{ type: 'text', text: `${String(result.notes?.length ?? 0)} note(s)` }]
|
|
573
610
|
},
|
|
574
611
|
},
|
|
575
|
-
execute: (async (rawArgs: never) => {
|
|
612
|
+
execute: (async (rawArgs: never, rawExec: never) => {
|
|
613
|
+
await noteExec(rawExec)
|
|
576
614
|
const args = rawArgs as never as { category?: string; tags?: string[]; search?: string; include_content?: boolean }
|
|
577
615
|
const category = args.category?.trim() ?? ''
|
|
578
616
|
const search = args.search?.toLowerCase() ?? ''
|
|
@@ -620,7 +658,8 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
|
|
|
620
658
|
return [{ type: 'text', text: result.success ? 'note returned' : `get_note failed: ${result.error ?? 'unknown'}` }]
|
|
621
659
|
},
|
|
622
660
|
},
|
|
623
|
-
execute: (async (rawArgs: never) => {
|
|
661
|
+
execute: (async (rawArgs: never, rawExec: never) => {
|
|
662
|
+
await noteExec(rawExec)
|
|
624
663
|
const args = rawArgs as never as { note_id?: string }
|
|
625
664
|
const noteId = (args.note_id ?? '').trim()
|
|
626
665
|
if (noteId === '') return { success: false, error: 'Note ID cannot be empty' }
|
|
@@ -656,7 +695,8 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
|
|
|
656
695
|
return [{ type: 'text', text: result.success ? 'note updated' : `update_note failed: ${result.error ?? 'unknown'}` }]
|
|
657
696
|
},
|
|
658
697
|
},
|
|
659
|
-
execute: (async (rawArgs: never) => {
|
|
698
|
+
execute: (async (rawArgs: never, rawExec: never) => {
|
|
699
|
+
await noteExec(rawExec)
|
|
660
700
|
const args = rawArgs as never as { note_id?: string; title?: string; content?: string; tags?: string[] }
|
|
661
701
|
const noteId = (args.note_id ?? '').trim()
|
|
662
702
|
const note = notes.get(noteId)
|
|
@@ -695,7 +735,8 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
|
|
|
695
735
|
return [{ type: 'text', text: result.success ? 'note deleted' : `delete_note failed: ${result.error ?? 'unknown'}` }]
|
|
696
736
|
},
|
|
697
737
|
},
|
|
698
|
-
execute: (async (rawArgs: never) => {
|
|
738
|
+
execute: (async (rawArgs: never, rawExec: never) => {
|
|
739
|
+
await noteExec(rawExec)
|
|
699
740
|
const args = rawArgs as never as { note_id?: string }
|
|
700
741
|
const noteId = (args.note_id ?? '').trim()
|
|
701
742
|
const note = notes.get(noteId)
|
|
@@ -727,7 +768,8 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
|
|
|
727
768
|
return [{ type: 'text', text: result.success ? 'recorded' : `think failed: ${result.error ?? 'unknown'}` }]
|
|
728
769
|
},
|
|
729
770
|
},
|
|
730
|
-
execute: (async (rawArgs: never) => {
|
|
771
|
+
execute: (async (rawArgs: never, rawExec: never) => {
|
|
772
|
+
await noteExec(rawExec)
|
|
731
773
|
const args = rawArgs as never as { thought?: string }
|
|
732
774
|
if ((args.thought ?? '').trim() === '') return { success: false, error: 'Thought cannot be empty' }
|
|
733
775
|
return { success: true, message: 'Thought recorded' }
|
|
@@ -736,6 +778,54 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
|
|
|
736
778
|
|
|
737
779
|
// ---- finish_scan ----
|
|
738
780
|
|
|
781
|
+
/**
|
|
782
|
+
* Best-effort capture reconciliation at scan close: count the Caido proxy
|
|
783
|
+
* history for this scan's sandbox session. Structural access (no hard
|
|
784
|
+
* dependency on the proxy package): pentestSandbox → caidoEndpoint → one
|
|
785
|
+
* GraphQL count query. Reconciliation must never block or fail scan
|
|
786
|
+
* completion — but a failure is never silent either: the reason travels
|
|
787
|
+
* back in `note` (into the tool result, the finish_scan render, and
|
|
788
|
+
* run.json). The previous `catch → undefined` swallowed a live breakage
|
|
789
|
+
* for a full day unnoticed (2026-09-22: run.json carried no capture
|
|
790
|
+
* fields at all while capture_summary worked seconds earlier).
|
|
791
|
+
*/
|
|
792
|
+
const proxyCapturedRequests = async (): Promise<{ captured?: number; note?: string }> => {
|
|
793
|
+
const fail = (reason: string): { note: string } => {
|
|
794
|
+
ctx.logger?.warn(`pentest-analysis: capture reconcile failed: ${reason}`)
|
|
795
|
+
return { note: `Proxy capture count unknown: ${reason}` }
|
|
796
|
+
}
|
|
797
|
+
try {
|
|
798
|
+
// ctx.get (not property access): the cordis property proxy throws
|
|
799
|
+
// `cannot get property "pentestSandbox" without inject` in a real
|
|
800
|
+
// runtime when the service is not in `inject` — this exact breakage
|
|
801
|
+
// hid behind the old silent catch for a full day (2026-09-22).
|
|
802
|
+
// ctx.get reads the service store without the inject requirement
|
|
803
|
+
// (same seam as tool-reporting's ctx.get('pentestAnalysis')).
|
|
804
|
+
const sandbox = ctx.get('pentestSandbox') as
|
|
805
|
+
| { createSession(options: { scanId: string }): Promise<{ caidoEndpoint(): Promise<{ baseUrl: string; token: string }> }> }
|
|
806
|
+
| undefined
|
|
807
|
+
if (sandbox === undefined) return fail('sandbox service unavailable')
|
|
808
|
+
const session = await sandbox.createSession({ scanId })
|
|
809
|
+
const endpoint = await session.caidoEndpoint()
|
|
810
|
+
const response = await fetch(`${endpoint.baseUrl.replace(/\/$/, '')}/graphql`, {
|
|
811
|
+
method: 'POST',
|
|
812
|
+
headers: { 'content-type': 'application/json', authorization: `Bearer ${endpoint.token}` },
|
|
813
|
+
body: JSON.stringify({
|
|
814
|
+
query: 'query CaptureReconcile { requests(first: 1, order: {by: CREATED_AT, ordering: DESC}) { count { value } } }',
|
|
815
|
+
}),
|
|
816
|
+
signal: AbortSignal.timeout(5_000),
|
|
817
|
+
})
|
|
818
|
+
if (!response.ok) return fail(`Caido answered HTTP ${String(response.status)}`)
|
|
819
|
+
const payload = await response.json() as { data?: { requests?: { count?: { value?: unknown } | null } | null }; errors?: Array<{ message?: unknown }> | null }
|
|
820
|
+
if (payload.errors !== undefined && payload.errors !== null && payload.errors.length > 0) return fail(`Caido: ${String(payload.errors[0]?.message ?? 'GraphQL error')}`)
|
|
821
|
+
const value = payload.data?.requests?.count?.value
|
|
822
|
+
if (value === null || value === undefined) return fail('Caido returned no request count')
|
|
823
|
+
return { captured: Number(value) }
|
|
824
|
+
} catch (error) {
|
|
825
|
+
return fail(error instanceof Error ? error.message : String(error))
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
739
829
|
ctx.tools.register(defineTool({
|
|
740
830
|
name: 'finish_scan',
|
|
741
831
|
description: 'Complete the scan: validates the four report sections, records the coverage summary, writes the final artifacts, and marks the scan completed. Root agent only.',
|
|
@@ -757,6 +847,9 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
|
|
|
757
847
|
coverage_outcomes: { type: 'object', properties: {}, additionalProperties: true },
|
|
758
848
|
coverage_warning: { type: 'string' },
|
|
759
849
|
unresolved_surfaces: { type: 'array', items: { type: 'object', properties: {}, additionalProperties: true } },
|
|
850
|
+
proxy_captured_requests: { type: 'integer', description: 'Requests in the Caido proxy history at close time (the queryable evidence trail).' },
|
|
851
|
+
capture_warning: { type: 'string' },
|
|
852
|
+
capture_note: { type: 'string' },
|
|
760
853
|
warning: { type: 'string' },
|
|
761
854
|
error: { type: 'string' },
|
|
762
855
|
errors: { type: 'array', items: { type: 'string' } },
|
|
@@ -764,12 +857,21 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
|
|
|
764
857
|
additionalProperties: false,
|
|
765
858
|
},
|
|
766
859
|
render: (_args, value) => {
|
|
767
|
-
const result = value as { success: boolean; error?: string; errors?: string[] }
|
|
860
|
+
const result = value as { success: boolean; error?: string; errors?: string[]; proxy_captured_requests?: number; capture_warning?: string; capture_note?: string }
|
|
768
861
|
const reason = result.error ?? result.errors?.join('; ') ?? 'unknown'
|
|
769
|
-
return [{ type: 'text', text:
|
|
862
|
+
if (!result.success) return [{ type: 'text', text: `finish_scan failed: ${reason}` }]
|
|
863
|
+
// The capture reconciliation travels with the completion line so the
|
|
864
|
+
// agent closes its summary against the real evidence-trail size.
|
|
865
|
+
const capture = result.capture_warning !== undefined
|
|
866
|
+
? result.capture_warning
|
|
867
|
+
: result.proxy_captured_requests !== undefined
|
|
868
|
+
? `proxy capture history holds ${String(result.proxy_captured_requests)} requests`
|
|
869
|
+
: (result.capture_note ?? 'proxy capture count unknown')
|
|
870
|
+
return [{ type: 'text', text: `scan completed; ${capture}` }]
|
|
770
871
|
},
|
|
771
872
|
},
|
|
772
|
-
execute: (async (rawArgs: never) => {
|
|
873
|
+
execute: (async (rawArgs: never, rawExec: never) => {
|
|
874
|
+
await noteExec(rawExec)
|
|
773
875
|
const args = rawArgs as never as { executive_summary?: string; methodology?: string; technical_analysis?: string; recommendations?: string }
|
|
774
876
|
if (config.allowFinish === false) {
|
|
775
877
|
return { success: false, scan_completed: false, error: 'This tool can only be used by the root/main agent. If you are a subagent, use agent_finish instead' }
|
|
@@ -801,11 +903,27 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
|
|
|
801
903
|
summary['unresolved_surfaces'] = unresolved
|
|
802
904
|
}
|
|
803
905
|
}
|
|
804
|
-
|
|
906
|
+
// Capture reconciliation: the proxy history is the hard evidence trail;
|
|
907
|
+
// surface its size (or its absence) instead of letting it stay invisible.
|
|
908
|
+
// Both the count and every failure reason reach run.json via extras.
|
|
909
|
+
const reconcile = await proxyCapturedRequests()
|
|
910
|
+
const captureExtras: Record<string, unknown> = {}
|
|
911
|
+
if (reconcile.captured !== undefined) {
|
|
912
|
+
summary['proxy_captured_requests'] = reconcile.captured
|
|
913
|
+
captureExtras['proxy_captured_requests'] = reconcile.captured
|
|
914
|
+
if (reconcile.captured === 0) {
|
|
915
|
+
summary['capture_warning'] = 'The Caido proxy captured 0 requests for this scan: no queryable traffic history exists, so PoC requests cannot be replayed from history. Report this as a capture-line gap; plaintext HTTP should be captured transparently in sandbox images >= 1.1.0.'
|
|
916
|
+
captureExtras['capture_warning'] = summary['capture_warning']
|
|
917
|
+
}
|
|
918
|
+
} else {
|
|
919
|
+
summary['capture_note'] = reconcile.note ?? 'Proxy capture count unknown.'
|
|
920
|
+
captureExtras['capture_note'] = summary['capture_note']
|
|
921
|
+
}
|
|
922
|
+
const reporting = (ctx as unknown as { pentestReporting?: { finishScan(sections: { executiveSummary: string; methodology: string; technicalAnalysis: string; recommendations: string }, status?: string, extras?: Readonly<Record<string, unknown>>): Promise<void>; state: { vulnerabilityReports: unknown[] } } }).pentestReporting
|
|
805
923
|
if (reporting === undefined) {
|
|
806
924
|
return { success: true, scan_completed: true, message: 'Scan completed (not persisted)', warning: 'Results could not be persisted - report state unavailable', ...summary }
|
|
807
925
|
}
|
|
808
|
-
await reporting.finishScan(sections)
|
|
926
|
+
await reporting.finishScan(sections, 'completed', captureExtras)
|
|
809
927
|
return { success: true, scan_completed: true, message: 'Scan completed successfully', vulnerabilities_found: reporting.state.vulnerabilityReports.length, ...summary }
|
|
810
928
|
}) as never,
|
|
811
929
|
}))
|
package/src/stores.ts
CHANGED
|
@@ -5,23 +5,34 @@
|
|
|
5
5
|
* @module @gpzhang2001/sharpkit-analysis/stores
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises'
|
|
8
|
+
import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises'
|
|
9
9
|
import { dirname, join } from 'node:path'
|
|
10
10
|
|
|
11
|
-
/** A JSON file mirror with atomic write and hydrate.
|
|
11
|
+
/** A JSON file mirror with atomic write and hydrate. The path may be lazy: a
|
|
12
|
+
* provider is resolved (and re-read) on every persist/hydrate so a store can
|
|
13
|
+
* follow the per-scan run directory resolved at tool-execute time. */
|
|
12
14
|
export class JsonMirrorStore {
|
|
13
|
-
private readonly
|
|
15
|
+
private readonly pathOrProvider: string | null | (() => string | null | Promise<string | null>)
|
|
14
16
|
private readonly map = new Map<string, Record<string, unknown>>()
|
|
17
|
+
/** Monotonic per-store counter: unique temp names within one process even
|
|
18
|
+
* under concurrent persist() calls (same pid would otherwise collide and
|
|
19
|
+
* one rename would fail with ENOENT — observed once on 2026-09-22). */
|
|
20
|
+
private tempCounter = 0
|
|
15
21
|
|
|
16
|
-
constructor(path: string | null) {
|
|
17
|
-
this.
|
|
22
|
+
constructor(path: string | null | (() => string | null | Promise<string | null>)) {
|
|
23
|
+
this.pathOrProvider = path
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private async path(): Promise<string | null> {
|
|
27
|
+
return typeof this.pathOrProvider === 'function' ? await Promise.resolve(this.pathOrProvider()) : this.pathOrProvider
|
|
18
28
|
}
|
|
19
29
|
|
|
20
30
|
/** Load the mirror file; corrupt JSON is tolerated as empty on hydrate (resume aid only). */
|
|
21
31
|
async hydrate(): Promise<void> {
|
|
22
|
-
|
|
32
|
+
const path = await this.path()
|
|
33
|
+
if (path === null) return
|
|
23
34
|
try {
|
|
24
|
-
const raw = await readFile(
|
|
35
|
+
const raw = await readFile(path, 'utf8')
|
|
25
36
|
const parsed = JSON.parse(raw) as unknown
|
|
26
37
|
if (typeof parsed !== 'object' || parsed === null) return
|
|
27
38
|
for (const [key, value] of Object.entries(parsed as Record<string, unknown>)) {
|
|
@@ -32,14 +43,22 @@ export class JsonMirrorStore {
|
|
|
32
43
|
}
|
|
33
44
|
}
|
|
34
45
|
|
|
35
|
-
/** Persist the whole map atomically (strix `_persist_locked`).
|
|
46
|
+
/** Persist the whole map atomically (strix `_persist_locked`). A rename
|
|
47
|
+
* loss to a concurrent writer of the same store is tolerated: the in-memory
|
|
48
|
+
* map stays authoritative and the mirror is a resume aid. */
|
|
36
49
|
async persist(): Promise<void> {
|
|
37
|
-
|
|
50
|
+
const path = await this.path()
|
|
51
|
+
if (path === null) return
|
|
38
52
|
const payload = JSON.stringify(Object.fromEntries(this.map), null, 2)
|
|
39
|
-
await mkdir(dirname(
|
|
40
|
-
const temp = `${dirname(
|
|
53
|
+
await mkdir(dirname(path), { recursive: true })
|
|
54
|
+
const temp = `${dirname(path)}/.${join('', path.split('/').pop() ?? 'store')}.${process.pid}.${this.tempCounter++}.tmp`
|
|
41
55
|
await writeFile(temp, payload, 'utf8')
|
|
42
|
-
|
|
56
|
+
try {
|
|
57
|
+
await rename(temp, path)
|
|
58
|
+
} catch (error) {
|
|
59
|
+
await rm(temp, { force: true }).catch(() => undefined)
|
|
60
|
+
if (this.map.size > 0) throw error
|
|
61
|
+
}
|
|
43
62
|
}
|
|
44
63
|
|
|
45
64
|
get(key: string): Record<string, unknown> | undefined {
|