pentesting 0.5.1 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +356 -23
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1723,9 +1723,27 @@ var ApprovalManager = class extends EventEmitter3 {
1723
1723
  this.removeListener(APPROVAL_EVENT.RESPONSE, responseHandler);
1724
1724
  if (response.decision === "approve_always") {
1725
1725
  this.autoApprovedTools.add(toolName);
1726
+ const pendingForSameTool = Array.from(this.pendingRequests.entries()).filter(([_, req]) => req.toolName === toolName);
1727
+ for (const [pendingId, _] of pendingForSameTool) {
1728
+ this.pendingRequests.delete(pendingId);
1729
+ this.emit(APPROVAL_EVENT.RESPONSE, {
1730
+ requestId: pendingId,
1731
+ decision: "approve",
1732
+ respondedAt: (/* @__PURE__ */ new Date()).toISOString()
1733
+ });
1734
+ }
1726
1735
  resolve("approve");
1727
1736
  } else if (response.decision === "deny_always") {
1728
1737
  this.autoDeniedTools.add(toolName);
1738
+ const pendingForSameTool = Array.from(this.pendingRequests.entries()).filter(([_, req]) => req.toolName === toolName);
1739
+ for (const [pendingId, _] of pendingForSameTool) {
1740
+ this.pendingRequests.delete(pendingId);
1741
+ this.emit(APPROVAL_EVENT.RESPONSE, {
1742
+ requestId: pendingId,
1743
+ decision: "deny",
1744
+ respondedAt: (/* @__PURE__ */ new Date()).toISOString()
1745
+ });
1746
+ }
1729
1747
  resolve("deny");
1730
1748
  } else {
1731
1749
  resolve(response.decision);
@@ -3298,6 +3316,14 @@ ${prompt}`
3298
3316
  this.state.lastProgressTime = /* @__PURE__ */ new Date();
3299
3317
  this.state.repeatedActions.clear();
3300
3318
  }
3319
+ /**
3320
+ * Check if the main loop should stop (due to pause, completion, etc.)
3321
+ * Uses string comparison to avoid TypeScript narrowing issues with const enums
3322
+ */
3323
+ shouldStopLoop() {
3324
+ const status = this.state.status;
3325
+ return status === AGENT_STATUS.PAUSED || status === AGENT_STATUS.COMPLETED || status === AGENT_STATUS.IDLE;
3326
+ }
3301
3327
  trackAction(action) {
3302
3328
  const count = this.state.repeatedActions.get(action) || 0;
3303
3329
  this.state.repeatedActions.set(action, count + 1);
@@ -3399,7 +3425,7 @@ Goal: Deep penetration to obtain root/system privileges, extract internal data,
3399
3425
  });
3400
3426
  let iteration = 0;
3401
3427
  const maxIterations = this.config.maxIterations;
3402
- while (iteration < maxIterations && this.state.status === AGENT_STATUS.RUNNING) {
3428
+ while (iteration < maxIterations && !this.shouldStopLoop()) {
3403
3429
  iteration++;
3404
3430
  this.state.iteration = iteration;
3405
3431
  this.getCurrentPhase().attempts++;
@@ -3422,7 +3448,15 @@ Goal: Deep penetration to obtain root/system privileges, extract internal data,
3422
3448
  continue;
3423
3449
  }
3424
3450
  const response = await this.executeStep();
3451
+ if (this.shouldStopLoop()) {
3452
+ this.think(THOUGHT_TYPE.OBSERVATION, "Execution paused by user");
3453
+ break;
3454
+ }
3425
3455
  await this.analyzeResponse(response);
3456
+ if (this.shouldStopLoop()) {
3457
+ this.think(THOUGHT_TYPE.OBSERVATION, "Execution paused by user");
3458
+ break;
3459
+ }
3426
3460
  if (this.shouldAdvancePhase()) {
3427
3461
  if (!this.advanceToNextPhase()) {
3428
3462
  this.think(THOUGHT_TYPE.OBSERVATION, "[done] All phases completed!");
@@ -3436,9 +3470,14 @@ Goal: Deep penetration to obtain root/system privileges, extract internal data,
3436
3470
  await this.attemptRecovery(error);
3437
3471
  }
3438
3472
  }
3439
- this.state.status = AGENT_STATUS.COMPLETED;
3440
- await this.generateFinalReport();
3441
- this.emit(AGENT_EVENT.COMPLETE, this.getSummary());
3473
+ const finalStatus = this.state.status;
3474
+ if (finalStatus !== AGENT_STATUS.PAUSED && finalStatus !== AGENT_STATUS.COMPLETED) {
3475
+ this.state.status = AGENT_STATUS.COMPLETED;
3476
+ await this.generateFinalReport();
3477
+ this.emit(AGENT_EVENT.COMPLETE, this.getSummary());
3478
+ } else if (finalStatus === AGENT_STATUS.PAUSED) {
3479
+ this.emit(AGENT_EVENT.PAUSED);
3480
+ }
3442
3481
  }
3443
3482
  // ===== Step Execution =====
3444
3483
  async executeStep() {
@@ -4936,6 +4975,235 @@ var FindingDisplay = ({ block }) => {
4936
4975
  // src/cli/app.tsx
4937
4976
  import { homedir } from "os";
4938
4977
  import { join as join6 } from "path";
4978
+
4979
+ // src/cli/utils/keyboard-listener.ts
4980
+ import { EventEmitter as EventEmitter7 } from "events";
4981
+ import * as readline2 from "readline";
4982
+ var KeyboardListener = class extends EventEmitter7 {
4983
+ isListening = false;
4984
+ isPaused = false;
4985
+ stdin = process.stdin;
4986
+ originalRawMode;
4987
+ preInputBuffer = "";
4988
+ constructor() {
4989
+ super();
4990
+ this.handleKeypress = this.handleKeypress.bind(this);
4991
+ this.handleData = this.handleData.bind(this);
4992
+ }
4993
+ /**
4994
+ * Start listening for keyboard input in raw mode
4995
+ */
4996
+ start() {
4997
+ if (this.isListening) return;
4998
+ try {
4999
+ if (this.stdin.isTTY) {
5000
+ this.originalRawMode = this.stdin.isRaw;
5001
+ this.stdin.setRawMode(true);
5002
+ readline2.emitKeypressEvents(this.stdin);
5003
+ this.stdin.on("keypress", this.handleKeypress);
5004
+ this.stdin.resume();
5005
+ this.isListening = true;
5006
+ }
5007
+ } catch {
5008
+ this.stdin.on("data", this.handleData);
5009
+ this.isListening = true;
5010
+ }
5011
+ }
5012
+ /**
5013
+ * Stop listening and restore terminal state
5014
+ */
5015
+ stop() {
5016
+ if (!this.isListening) return;
5017
+ try {
5018
+ this.stdin.off("keypress", this.handleKeypress);
5019
+ this.stdin.off("data", this.handleData);
5020
+ if (this.stdin.isTTY && this.originalRawMode !== void 0) {
5021
+ this.stdin.setRawMode(this.originalRawMode);
5022
+ }
5023
+ } catch {
5024
+ }
5025
+ this.isListening = false;
5026
+ }
5027
+ /**
5028
+ * Pause listening (for when Ink needs control)
5029
+ */
5030
+ pause() {
5031
+ if (!this.isListening || this.isPaused) return;
5032
+ try {
5033
+ if (this.stdin.isTTY && this.originalRawMode !== void 0) {
5034
+ this.stdin.setRawMode(this.originalRawMode);
5035
+ }
5036
+ this.isPaused = true;
5037
+ } catch {
5038
+ }
5039
+ }
5040
+ /**
5041
+ * Resume listening (after Ink releases control)
5042
+ */
5043
+ resume() {
5044
+ if (!this.isListening || !this.isPaused) return;
5045
+ try {
5046
+ if (this.stdin.isTTY) {
5047
+ this.stdin.setRawMode(true);
5048
+ }
5049
+ this.isPaused = false;
5050
+ } catch {
5051
+ }
5052
+ }
5053
+ /**
5054
+ * Get the current pre-input buffer
5055
+ */
5056
+ getPreInputBuffer() {
5057
+ return this.preInputBuffer;
5058
+ }
5059
+ /**
5060
+ * Clear the pre-input buffer
5061
+ */
5062
+ clearPreInputBuffer() {
5063
+ this.preInputBuffer = "";
5064
+ }
5065
+ /**
5066
+ * Get and clear the pre-input buffer
5067
+ */
5068
+ consumePreInputBuffer() {
5069
+ const buffer = this.preInputBuffer;
5070
+ this.preInputBuffer = "";
5071
+ return buffer;
5072
+ }
5073
+ /**
5074
+ * Handle keypress events (readline mode)
5075
+ */
5076
+ handleKeypress(_str, key) {
5077
+ if (this.isPaused) return;
5078
+ if (key.name === "escape") {
5079
+ this.emit("escape" /* ESCAPE */);
5080
+ return;
5081
+ }
5082
+ if (key.ctrl && key.name === "c") {
5083
+ this.emit("ctrl_c" /* CTRL_C */);
5084
+ return;
5085
+ }
5086
+ if (key.name === "return" || key.name === "enter") {
5087
+ if (this.preInputBuffer) {
5088
+ this.emit("line", this.preInputBuffer);
5089
+ }
5090
+ this.emit("enter" /* ENTER */);
5091
+ return;
5092
+ }
5093
+ if (key.name === "backspace") {
5094
+ if (this.preInputBuffer.length > 0) {
5095
+ this.preInputBuffer = this.preInputBuffer.slice(0, -1);
5096
+ }
5097
+ this.emit("backspace" /* BACKSPACE */);
5098
+ return;
5099
+ }
5100
+ if (key.name === "up") {
5101
+ this.emit("up" /* UP */);
5102
+ return;
5103
+ }
5104
+ if (key.name === "down") {
5105
+ this.emit("down" /* DOWN */);
5106
+ return;
5107
+ }
5108
+ if (key.name === "left") {
5109
+ this.emit("left" /* LEFT */);
5110
+ return;
5111
+ }
5112
+ if (key.name === "right") {
5113
+ this.emit("right" /* RIGHT */);
5114
+ return;
5115
+ }
5116
+ if (key.name === "tab") {
5117
+ this.emit("tab" /* TAB */);
5118
+ return;
5119
+ }
5120
+ if (_str && !key.ctrl && !key.meta && _str.length === 1) {
5121
+ const charCode = _str.charCodeAt(0);
5122
+ if (charCode >= 32 && charCode <= 126) {
5123
+ this.preInputBuffer += _str;
5124
+ this.emit("char", _str);
5125
+ }
5126
+ }
5127
+ }
5128
+ /**
5129
+ * Handle raw data events (fallback mode)
5130
+ */
5131
+ handleData(data) {
5132
+ if (this.isPaused) return;
5133
+ const str = data.toString();
5134
+ for (let i = 0; i < str.length; i++) {
5135
+ const char = str[i];
5136
+ const code = char.charCodeAt(0);
5137
+ if (code === 27) {
5138
+ if (i + 1 < str.length && str[i + 1] === "[") {
5139
+ if (i + 2 < str.length) {
5140
+ const arrow = str[i + 2];
5141
+ switch (arrow) {
5142
+ case "A":
5143
+ this.emit("up" /* UP */);
5144
+ break;
5145
+ case "B":
5146
+ this.emit("down" /* DOWN */);
5147
+ break;
5148
+ case "C":
5149
+ this.emit("right" /* RIGHT */);
5150
+ break;
5151
+ case "D":
5152
+ this.emit("left" /* LEFT */);
5153
+ break;
5154
+ }
5155
+ i += 2;
5156
+ continue;
5157
+ }
5158
+ } else {
5159
+ this.emit("escape" /* ESCAPE */);
5160
+ continue;
5161
+ }
5162
+ }
5163
+ if (code === 3) {
5164
+ this.emit("ctrl_c" /* CTRL_C */);
5165
+ continue;
5166
+ }
5167
+ if (code === 13 || code === 10) {
5168
+ if (this.preInputBuffer) {
5169
+ this.emit("line", this.preInputBuffer);
5170
+ }
5171
+ this.emit("enter" /* ENTER */);
5172
+ continue;
5173
+ }
5174
+ if (code === 127 || code === 8) {
5175
+ if (this.preInputBuffer.length > 0) {
5176
+ this.preInputBuffer = this.preInputBuffer.slice(0, -1);
5177
+ }
5178
+ this.emit("backspace" /* BACKSPACE */);
5179
+ continue;
5180
+ }
5181
+ if (code === 9) {
5182
+ this.emit("tab" /* TAB */);
5183
+ continue;
5184
+ }
5185
+ if (code >= 32 && code <= 126) {
5186
+ this.preInputBuffer += char;
5187
+ this.emit("char", char);
5188
+ }
5189
+ }
5190
+ }
5191
+ /**
5192
+ * Check if currently listening
5193
+ */
5194
+ isActive() {
5195
+ return this.isListening && !this.isPaused;
5196
+ }
5197
+ };
5198
+ var keyboardListenerInstance = null;
5199
+ function getKeyboardListener() {
5200
+ if (!keyboardListenerInstance) {
5201
+ keyboardListenerInstance = new KeyboardListener();
5202
+ }
5203
+ return keyboardListenerInstance;
5204
+ }
5205
+
5206
+ // src/cli/app.tsx
4939
5207
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
4940
5208
  var App = ({ autoApprove = false, target }) => {
4941
5209
  const { exit } = useApp();
@@ -4950,12 +5218,19 @@ var App = ({ autoApprove = false, target }) => {
4950
5218
  const [showCommandHints, setShowCommandHints] = useState(false);
4951
5219
  const [mode, setMode] = useState("agent");
4952
5220
  const [checkpointCount, setCheckpointCount] = useState(0);
5221
+ const [preInputBuffer, setPreInputBuffer] = useState("");
5222
+ const [wasInterrupted, setWasInterrupted] = useState(false);
4953
5223
  const [agent] = useState(() => new AutonomousHackingAgent(void 0, { autoApprove }));
4954
5224
  const sessionManager2 = getSessionManager();
4955
5225
  const approvalManager2 = getApprovalManager({ yoloMode: autoApprove });
4956
5226
  const sessionDirRef = useRef(join6(homedir(), ".pentest", "sessions", `session-${Date.now()}`));
4957
5227
  const contextManagerRef = useRef(null);
4958
5228
  const wireLoggerRef = useRef(null);
5229
+ const keyboardListenerRef = useRef(getKeyboardListener());
5230
+ const isProcessingRef = useRef(false);
5231
+ useEffect(() => {
5232
+ isProcessingRef.current = isProcessing;
5233
+ }, [isProcessing]);
4959
5234
  useEffect(() => {
4960
5235
  const sessionId = `session-${Date.now()}`;
4961
5236
  contextManagerRef.current = new ContextManager2(sessionDirRef.current);
@@ -5008,14 +5283,60 @@ var App = ({ autoApprove = false, target }) => {
5008
5283
  return duration;
5009
5284
  }, []);
5010
5285
  useEffect(() => {
5011
- const banner = `
5012
- \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
5013
- \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D
5014
- \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551
5015
- \u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551
5016
- \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551
5017
- \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D `;
5018
- addMessage(MESSAGE_TYPE.SYSTEM, banner);
5286
+ const listener = keyboardListenerRef.current;
5287
+ const handleEscape = () => {
5288
+ if (isProcessingRef.current && !pendingApproval) {
5289
+ agent.pause();
5290
+ stopTimer();
5291
+ setIsProcessing(false);
5292
+ setCurrentStatus("");
5293
+ setWasInterrupted(true);
5294
+ addMessage(MESSAGE_TYPE.SYSTEM, "\u23F8 Interrupted by ESC - Ready for input");
5295
+ const buffered = listener.consumePreInputBuffer();
5296
+ if (buffered) {
5297
+ setInput(buffered);
5298
+ }
5299
+ listener.pause();
5300
+ }
5301
+ };
5302
+ const handleChar = () => {
5303
+ setPreInputBuffer(listener.getPreInputBuffer());
5304
+ };
5305
+ const handleBackspace = () => {
5306
+ setPreInputBuffer(listener.getPreInputBuffer());
5307
+ };
5308
+ listener.on("escape" /* ESCAPE */, handleEscape);
5309
+ listener.on("char", handleChar);
5310
+ listener.on("backspace" /* BACKSPACE */, handleBackspace);
5311
+ return () => {
5312
+ listener.off("escape" /* ESCAPE */, handleEscape);
5313
+ listener.off("char", handleChar);
5314
+ listener.off("backspace" /* BACKSPACE */, handleBackspace);
5315
+ };
5316
+ }, [agent, addMessage, stopTimer, pendingApproval]);
5317
+ useEffect(() => {
5318
+ const listener = keyboardListenerRef.current;
5319
+ if (isProcessing && !pendingApproval) {
5320
+ listener.clearPreInputBuffer();
5321
+ setPreInputBuffer("");
5322
+ listener.start();
5323
+ listener.resume();
5324
+ } else {
5325
+ const buffered = listener.consumePreInputBuffer();
5326
+ if (buffered && !wasInterrupted) {
5327
+ setInput((prev) => prev + buffered);
5328
+ }
5329
+ setPreInputBuffer("");
5330
+ setWasInterrupted(false);
5331
+ listener.pause();
5332
+ }
5333
+ }, [isProcessing, pendingApproval, wasInterrupted]);
5334
+ useEffect(() => {
5335
+ return () => {
5336
+ keyboardListenerRef.current.stop();
5337
+ };
5338
+ }, []);
5339
+ useEffect(() => {
5019
5340
  addMessage(MESSAGE_TYPE.SYSTEM, `
5020
5341
  \u{1F680} /target <ip> \u2192 /start [goal] | /help for commands
5021
5342
  \u26A0\uFE0F For authorized penetration testing only. Unauthorized use is illegal.`);
@@ -5612,17 +5933,27 @@ pentesting v${APP_VERSION}`
5612
5933
  ] }, opt.decision)) }),
5613
5934
  /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\u2191\u2193 to select, Enter to confirm, or type /y /n /ya" }) })
5614
5935
  ] }),
5615
- isProcessing ? /* @__PURE__ */ jsxs2(Box2, { children: [
5616
- /* @__PURE__ */ jsx2(Text2, { color: THEME.status.running, children: /* @__PURE__ */ jsx2(Spinner, { type: "dots" }) }),
5617
- /* @__PURE__ */ jsxs2(Text2, { color: THEME.text.muted, children: [
5618
- " ",
5619
- currentStatus,
5620
- elapsedTime > 0 && /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
5621
- " (",
5622
- elapsedTime,
5623
- "s)"
5936
+ isProcessing ? /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
5937
+ /* @__PURE__ */ jsxs2(Box2, { children: [
5938
+ /* @__PURE__ */ jsx2(Text2, { color: THEME.status.running, children: /* @__PURE__ */ jsx2(Spinner, { type: "dots" }) }),
5939
+ /* @__PURE__ */ jsxs2(Text2, { color: THEME.text.muted, children: [
5940
+ " ",
5941
+ currentStatus,
5942
+ elapsedTime > 0 && /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
5943
+ " (",
5944
+ elapsedTime,
5945
+ "s)"
5946
+ ] })
5624
5947
  ] })
5625
- ] })
5948
+ ] }),
5949
+ preInputBuffer && /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, children: [
5950
+ /* @__PURE__ */ jsxs2(Text2, { color: THEME.text.accent, children: [
5951
+ "\u2728 ",
5952
+ preInputBuffer
5953
+ ] }),
5954
+ /* @__PURE__ */ jsx2(Text2, { color: THEME.text.muted, children: "\u258C" })
5955
+ ] }),
5956
+ /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "ESC to interrupt \u2502 Type ahead to queue input" }) })
5626
5957
  ] }) : /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
5627
5958
  showCommandHints && input.startsWith("/") && /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: [
5628
5959
  "/target <ip>",
@@ -5667,7 +5998,9 @@ pentesting v${APP_VERSION}`
5667
5998
  /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
5668
5999
  "v",
5669
6000
  APP_VERSION,
5670
- " \u2502 Ctrl+X mode \u2502 /help \u2502 Ctrl+C ",
6001
+ " \u2502 Ctrl+X mode \u2502 /help \u2502 ",
6002
+ isProcessing ? "ESC interrupt \u2502 " : "",
6003
+ "Ctrl+C ",
5671
6004
  isProcessing ? "stop" : "exit"
5672
6005
  ] })
5673
6006
  ] })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pentesting",
3
- "version": "0.5.1",
3
+ "version": "0.5.4",
4
4
  "description": "Autonomous Penetration Testing AI Agent",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -78,4 +78,4 @@
78
78
  "tsx": "^4.19.2",
79
79
  "typescript": "^5.7.3"
80
80
  }
81
- }
81
+ }