diff --git a/plugins/burp-suite/cyberstrikeai-burp-extension/README.md b/plugins/burp-suite/cyberstrikeai-burp-extension/README.md index 321fd637..73b7349c 100644 --- a/plugins/burp-suite/cyberstrikeai-burp-extension/README.md +++ b/plugins/burp-suite/cyberstrikeai-burp-extension/README.md @@ -4,7 +4,7 @@ ### What it does -- Configure **Host / Port / Password** and choose **Single-Agent** or **Multi-Agent** +- Configure **Host / Port / HTTPS / Password** and choose an agent mode - Click **Validate** to login (`POST /api/auth/login`) and verify token (`GET /api/auth/validate`) - Right-click any HTTP message in Burp and send it to CyberStrikeAI for **streaming web pentest** - Keep a **test history sidebar** (searchable) so you can revisit previous runs @@ -63,6 +63,7 @@ If you already have Gradle available, you can still use `build.gradle` to build. ### Notes -- This extension connects to your CyberStrikeAI server (default is `http://127.0.0.1:8080`). +- Default connection is `https://127.0.0.1:8080` (**HTTPS** checked). Self-signed / local certs are trusted automatically (no import). +- Uncheck **HTTPS** only if your server runs plain HTTP. - It uses **Bearer Token** authentication obtained from the configured password. diff --git a/plugins/burp-suite/cyberstrikeai-burp-extension/README.zh-CN.md b/plugins/burp-suite/cyberstrikeai-burp-extension/README.zh-CN.md index d50a96fe..e175aec7 100644 --- a/plugins/burp-suite/cyberstrikeai-burp-extension/README.zh-CN.md +++ b/plugins/burp-suite/cyberstrikeai-burp-extension/README.zh-CN.md @@ -81,7 +81,8 @@ cd plugins/burp-suite/cyberstrikeai-burp-extension 2) 填写: - **Host**:例如 `127.0.0.1` - **Port**:例如 `8080` - - **Password**:你的 CyberStrikeAI 登录密码(对应服务端 `config.yaml` 的 `auth.password`) + - **HTTPS**:默认勾选(对接 `config.yaml` 中 `tls_enabled` / 自签证书);插件会自动信任本地自签证书,无需导入 + - **Password**:你的 CyberStrikeAI 登录密码(对应服务端 `auth.password`) - **Agent mode**:选择 `Single Agent` 或 `Multi Agent` 3) 点击 **Validate** - 成功:状态显示 `OK (token saved)` @@ -94,8 +95,9 @@ cd plugins/burp-suite/cyberstrikeai-burp-extension - **Validate 失败 / 401** - 确认密码是否正确(服务端 `auth.password`) - - 确认 IP/端口是否能访问(例如浏览器能打开 `http://IP:PORT/`) - - 若服务器启用了反向代理/HTTPS,需要把插件里 baseUrl 改成对应协议与端口(当前插件默认使用 `http://`) + - 确认 IP/端口是否能访问(例如浏览器能打开 `https://IP:PORT/`) + - 服务端启用 TLS 时勾选 **HTTPS**(默认已勾选);自签证书无需手动导入 + - 若仍为纯 HTTP 部署,取消勾选 **HTTPS** - **选择 Multi Agent 后提示“多代理未启用”** - 服务端需要开启:`config.yaml` 中 `multi_agent.enabled: true` diff --git a/plugins/burp-suite/cyberstrikeai-burp-extension/dist/cyberstrikeai-burp-extension.jar b/plugins/burp-suite/cyberstrikeai-burp-extension/dist/cyberstrikeai-burp-extension.jar index 789f49da..91323400 100644 Binary files a/plugins/burp-suite/cyberstrikeai-burp-extension/dist/cyberstrikeai-burp-extension.jar and b/plugins/burp-suite/cyberstrikeai-burp-extension/dist/cyberstrikeai-burp-extension.jar differ diff --git a/plugins/burp-suite/cyberstrikeai-burp-extension/src/main/java/burp/BurpExtender.java b/plugins/burp-suite/cyberstrikeai-burp-extension/src/main/java/burp/BurpExtender.java index 8bd50b29..f554bc27 100644 --- a/plugins/burp-suite/cyberstrikeai-burp-extension/src/main/java/burp/BurpExtender.java +++ b/plugins/burp-suite/cyberstrikeai-burp-extension/src/main/java/burp/BurpExtender.java @@ -73,15 +73,34 @@ public class BurpExtender implements IBurpExtender, IContextMenuFactory { public void onEvent(String type, String message, String rawJson) { if (type == null) type = ""; switch (type) { + case "response_start": + tab.appendProgressToRun(runId, "\n\n[主回复]\n"); + break; case "response_delta": - case "eino_agent_reply_stream_delta": - tab.appendFinalToRun(runId, message); + if (message != null && !message.isEmpty()) { + tab.appendFinalToRun(runId, message); + } break; case "response": - tab.appendFinalToRun(runId, "\n\n--- Final Response ---\n"); tab.appendFinalToRun(runId, message); tab.setFinalResponse(runId, message); break; + case "eino_agent_reply_stream_start": + tab.appendProgressToRun(runId, "\n\n[子代理回复]\n"); + break; + case "eino_agent_reply_stream_delta": + if (message != null && !message.isEmpty()) { + tab.appendProgressToRun(runId, message); + } + break; + case "eino_agent_reply_stream_end": + tab.appendProgressToRun(runId, "\n"); + break; + case "eino_agent_reply": + if (message != null && !message.isEmpty()) { + tab.appendProgressToRun(runId, "\n\n[子代理回复]\n" + message + "\n"); + } + break; case "progress": tab.appendProgressToRun(runId, "\n[progress] " + message + "\n"); tab.setRunStatus(runId, "running"); @@ -94,21 +113,40 @@ public class BurpExtender implements IBurpExtender, IContextMenuFactory { tab.appendProgressToRun(runId, "\n[error] " + message + "\n"); tab.setRunStatus(runId, "error"); break; + case "reasoning_chain_stream_start": + tab.appendProgressToRun(runId, "\n\n[推理过程]\n"); + break; + case "reasoning_chain_stream_delta": + if (message != null && !message.isEmpty()) { + tab.appendProgressToRun(runId, message); + } + break; + case "reasoning_chain_stream_end": + tab.appendProgressToRun(runId, "\n"); + break; + case "reasoning_chain": + if (message != null && !message.isEmpty()) { + String streamId = rawJson != null ? SimpleJson.extractStringField(rawJson, "streamId") : ""; + if (streamId == null || streamId.isEmpty()) { + tab.appendProgressToRun(runId, "\n\n[推理过程]\n" + message + "\n"); + } + } + break; case "thinking_stream_start": if (tab.isShowDebugEvents()) { tab.resetThinkingStream(runId); } break; case "thinking_stream_delta": + if (tab.isShowDebugEvents() && message != null && !message.isEmpty()) { + tab.appendProgressToRun(runId, message); + } + break; case "tool_call": case "tool_result": case "tool_result_delta": if (tab.isShowDebugEvents() && message != null && !message.isEmpty()) { - if ("thinking_stream_delta".equals(type)) { - tab.appendThinkingDelta(runId, message); - } else { - tab.appendProgressToRun(runId, "\n[" + type + "] " + message + "\n"); - } + tab.appendProgressToRun(runId, "\n[" + type + "] " + message + "\n"); } break; case "conversation": @@ -125,7 +163,9 @@ public class BurpExtender implements IBurpExtender, IContextMenuFactory { case "done": break; default: - if (tab.isShowDebugEvents() && message != null && !message.isEmpty()) { + if (tab.isShowDebugEvents() && message != null && !message.isEmpty() + && !type.endsWith("_stream_delta") && !type.endsWith("_stream_start") + && !type.endsWith("_stream_end")) { tab.appendProgressToRun(runId, "\n[" + type + "] " + message + "\n"); } break; @@ -134,8 +174,9 @@ public class BurpExtender implements IBurpExtender, IContextMenuFactory { @Override public void onError(String message, Exception e) { - tab.appendProgressToRun(runId, "\n[error] " + message + "\n"); - tab.setRunStatus(runId, "error"); + boolean cancelled = message != null && message.toLowerCase().contains("cancel"); + tab.appendProgressToRun(runId, cancelled ? "\n[info] " + message + "\n" : "\n[error] " + message + "\n"); + tab.setRunStatus(runId, cancelled ? "cancelled" : "error"); callbacks.printError("CyberStrikeAI stream error: " + message); if (e != null) { callbacks.printError(e.toString()); diff --git a/plugins/burp-suite/cyberstrikeai-burp-extension/src/main/java/burp/CyberStrikeAIClient.java b/plugins/burp-suite/cyberstrikeai-burp-extension/src/main/java/burp/CyberStrikeAIClient.java index 2511ef13..dd79d397 100644 --- a/plugins/burp-suite/cyberstrikeai-burp-extension/src/main/java/burp/CyberStrikeAIClient.java +++ b/plugins/burp-suite/cyberstrikeai-burp-extension/src/main/java/burp/CyberStrikeAIClient.java @@ -2,17 +2,29 @@ package burp; import java.io.BufferedReader; import java.io.IOException; +import java.io.InterruptedIOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; +import java.net.SocketTimeoutException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; final class CyberStrikeAIClient { + private static final int AUTH_CONNECT_TIMEOUT_MS = 4_000; + private static final int AUTH_READ_TIMEOUT_MS = 5_000; + /** login + validate 整段上限,避免两次读超时叠加拖到半分钟 */ + private static final int AUTH_OVERALL_TIMEOUT_MS = 10_000; + private static final int DEFAULT_READ_TIMEOUT_MS = 15_000; + + private final AtomicReference activeConnection = new AtomicReference<>(); + private final AtomicReference activeThread = new AtomicReference<>(); + static final class Config { final String baseUrl; // e.g. http://127.0.0.1:8080 final String password; @@ -49,15 +61,97 @@ final class CyberStrikeAIClient { void onDone(); } + boolean hasActiveRequest() { + return activeConnection.get() != null; + } + + void cancelActiveRequest() { + HttpURLConnection conn = activeConnection.getAndSet(null); + if (conn != null) { + try { + conn.disconnect(); + } catch (Exception ignored) { + } + } + Thread t = activeThread.getAndSet(null); + if (t != null) { + t.interrupt(); + } + } + String loginAndValidate(Config cfg) throws IOException { - String token = login(cfg.baseUrl, cfg.password); - validate(cfg.baseUrl, token); - return token; + Thread worker = Thread.currentThread(); + java.util.Timer deadline = new java.util.Timer("CyberStrikeAI-AuthDeadline", true); + deadline.schedule(new java.util.TimerTask() { + @Override + public void run() { + worker.interrupt(); + cancelActiveRequest(); + } + }, AUTH_OVERALL_TIMEOUT_MS); + try { + String token = login(cfg.baseUrl, cfg.password); + if (Thread.interrupted()) { + throw timeoutIOException(); + } + validate(cfg.baseUrl, token); + if (Thread.interrupted()) { + throw timeoutIOException(); + } + return token; + } catch (SocketTimeoutException e) { + throw timeoutIOException(); + } finally { + deadline.cancel(); + } + } + + private static IOException timeoutIOException() { + return new IOException("Connection timed out (~" + (AUTH_OVERALL_TIMEOUT_MS / 1000) + + "s). Check host/port and HTTPS checkbox."); + } + + private void trackConnection(HttpURLConnection conn) { + activeThread.set(Thread.currentThread()); + activeConnection.set(conn); + } + + private void releaseConnection(HttpURLConnection conn) { + if (activeConnection.compareAndSet(conn, null)) { + activeThread.set(null); + } + } + + private static boolean isCancelled(Throwable e) { + if (e == null) { + return Thread.currentThread().isInterrupted(); + } + if (Thread.currentThread().isInterrupted()) { + return true; + } + if (e instanceof InterruptedIOException) { + return true; + } + if (e instanceof SocketTimeoutException) { + return false; + } + Throwable cause = e.getCause(); + if (cause != null && cause != e) { + return isCancelled(cause); + } + String msg = e.getMessage(); + return msg != null && ( + msg.toLowerCase().contains("cancel") + || msg.toLowerCase().contains("abort") + || msg.toLowerCase().contains("closed") + ); } private String login(String baseUrl, String password) throws IOException { URL url = new URL(baseUrl + "/api/auth/login"); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + HttpURLConnection conn = SslTrustAll.open(url, AUTH_CONNECT_TIMEOUT_MS, AUTH_READ_TIMEOUT_MS); + trackConnection(conn); + try { conn.setRequestMethod("POST"); conn.setDoOutput(true); conn.setRequestProperty("Content-Type", "application/json"); @@ -92,11 +186,16 @@ final class CyberStrikeAIClient { throw new IOException("Login response missing token. Check backend address and credentials."); } return token; + } finally { + releaseConnection(conn); + } } private void validate(String baseUrl, String token) throws IOException { URL url = new URL(baseUrl + "/api/auth/validate"); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + HttpURLConnection conn = SslTrustAll.open(url, AUTH_CONNECT_TIMEOUT_MS, AUTH_READ_TIMEOUT_MS); + trackConnection(conn); + try { conn.setRequestMethod("GET"); conn.setRequestProperty("Authorization", "Bearer " + token); int code = conn.getResponseCode(); @@ -104,6 +203,9 @@ final class CyberStrikeAIClient { if (code < 200 || code >= 300) { throw new IOException("Validate failed (" + code + "): " + resp); } + } finally { + releaseConnection(conn); + } } void streamTest(Config cfg, String token, String message, StreamListener listener) { @@ -117,11 +219,12 @@ final class CyberStrikeAIClient { payload.put("orchestration", cfg.agentMode.orchestration); } - new Thread(() -> { + Thread worker = new Thread(() -> { HttpURLConnection conn = null; try { URL url = new URL(urlStr); - conn = (HttpURLConnection) url.openConnection(); + conn = SslTrustAll.open(url, AUTH_CONNECT_TIMEOUT_MS, 0); + trackConnection(conn); conn.setRequestMethod("POST"); conn.setDoOutput(true); conn.setRequestProperty("Content-Type", "application/json"); @@ -142,6 +245,9 @@ final class CyberStrikeAIClient { try (BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { String line; while ((line = br.readLine()) != null) { + if (Thread.currentThread().isInterrupted()) { + break; + } // SSE format: "data: {json}" if (line.startsWith("data:")) { String json = line.substring("data:".length()).trim(); @@ -156,15 +262,25 @@ final class CyberStrikeAIClient { } } } - listener.onDone(); + if (Thread.currentThread().isInterrupted()) { + listener.onError("Cancelled.", null); + } else { + listener.onDone(); + } } catch (Exception e) { - listener.onError(e.getMessage(), e); + if (isCancelled(e)) { + listener.onError("Cancelled.", e); + } else { + listener.onError(e.getMessage(), e); + } } finally { if (conn != null) { + releaseConnection(conn); conn.disconnect(); } } - }, "CyberStrikeAI-Stream").start(); + }, "CyberStrikeAI-Stream"); + worker.start(); } void cancelByConversationId(String baseUrl, String token, String conversationId) throws IOException { @@ -172,7 +288,7 @@ final class CyberStrikeAIClient { throw new IOException("Missing conversationId."); } URL url = new URL(baseUrl + "/api/agent-loop/cancel"); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + HttpURLConnection conn = SslTrustAll.open(url, AUTH_CONNECT_TIMEOUT_MS, AUTH_READ_TIMEOUT_MS); conn.setRequestMethod("POST"); conn.setDoOutput(true); conn.setRequestProperty("Content-Type", "application/json"); diff --git a/plugins/burp-suite/cyberstrikeai-burp-extension/src/main/java/burp/CyberStrikeAITab.java b/plugins/burp-suite/cyberstrikeai-burp-extension/src/main/java/burp/CyberStrikeAITab.java index e3df85be..db1f5b86 100644 --- a/plugins/burp-suite/cyberstrikeai-burp-extension/src/main/java/burp/CyberStrikeAITab.java +++ b/plugins/burp-suite/cyberstrikeai-burp-extension/src/main/java/burp/CyberStrikeAITab.java @@ -14,6 +14,7 @@ final class CyberStrikeAITab implements ITab { private final JTextField hostField = new JTextField("127.0.0.1"); private final JTextField portField = new JTextField("8080"); + private final JCheckBox useHttpsBox = new JCheckBox("HTTPS", true); private final JPasswordField passwordField = new JPasswordField(); private final JComboBox agentModeBox = new JComboBox<>(new String[]{ "Native ReAct", "Eino Single (ADK)", "Deep (DeepAgent)", "Plan-Execute", "Supervisor" @@ -29,6 +30,10 @@ final class CyberStrikeAITab implements ITab { private final JTextArea progressArea = new JTextArea(); private final JTextArea finalRawArea = new JTextArea(); // raw final stream / final response + private JScrollPane progressScrollPane; + private JScrollPane finalRawScrollPane; + /** 距底部在此像素内视为「跟随滚动」,否则用户上拉阅读时不抢滚动条 */ + private static final int SCROLL_FOLLOW_THRESHOLD_PX = 48; private final JEditorPane markdownPane = new JEditorPane("text/html", ""); private final CardLayout outputCardsLayout = new CardLayout(); private final JPanel outputCards = new JPanel(outputCardsLayout); @@ -41,6 +46,7 @@ final class CyberStrikeAITab implements ITab { private final CyberStrikeAIClient client = new CyberStrikeAIClient(); private final AtomicReference tokenRef = new AtomicReference<>(""); + private final AtomicReference validateThreadRef = new AtomicReference<>(); private final DefaultListModel testListModel = new DefaultListModel<>(); private final JList testList = new JList<>(testListModel); @@ -107,6 +113,8 @@ final class CyberStrikeAITab implements ITab { row1.add(hostField); row1.add(new JLabel("Port")); row1.add(portField); + useHttpsBox.setToolTipText("Use https:// for CyberStrikeAI (self-signed certs are trusted automatically)"); + row1.add(useHttpsBox); row1.add(new JLabel("Password")); row1.add(passwordField); row1.add(validateButton); @@ -186,15 +194,22 @@ final class CyberStrikeAITab implements ITab { configureTextArea(requestArea, false); configureTextArea(responseArea, false); - outputCards.add(new JScrollPane(finalRawArea), "raw"); + finalRawScrollPane = new JScrollPane(finalRawArea); + finalRawScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + finalRawScrollPane.getVerticalScrollBar().setUnitIncrement(16); + outputCards.add(finalRawScrollPane, "raw"); outputCards.add(new JScrollPane(markdownPane), "md"); outputRoot.add(buildOutputHeader(), BorderLayout.NORTH); outputRoot.add(buildOutputBody(), BorderLayout.CENTER); rightTabs.addTab("Output", outputRoot); - rightTabs.addTab("Request", new JScrollPane(requestArea)); - rightTabs.addTab("Response", new JScrollPane(responseArea)); + JScrollPane requestScroll = new JScrollPane(requestArea); + requestScroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + rightTabs.addTab("Request", requestScroll); + JScrollPane responseScroll = new JScrollPane(responseArea); + responseScroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + rightTabs.addTab("Response", responseScroll); return rightTabs; } @@ -210,12 +225,13 @@ final class CyberStrikeAITab implements ITab { } private JComponent buildOutputBody() { - JScrollPane progressScroll = new JScrollPane(progressArea); - progressScroll.setBorder(BorderFactory.createTitledBorder("Progress")); - progressScroll.getVerticalScrollBar().setUnitIncrement(16); + progressScrollPane = new JScrollPane(progressArea); + progressScrollPane.setBorder(BorderFactory.createTitledBorder("Progress")); + progressScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + progressScrollPane.getVerticalScrollBar().setUnitIncrement(16); JPanel empty = new JPanel(); - progressContainer.add(progressScroll, "show"); + progressContainer.add(progressScrollPane, "show"); progressContainer.add(empty, "hide"); ((CardLayout) progressContainer.getLayout()).show(progressContainer, "show"); @@ -259,10 +275,27 @@ final class CyberStrikeAITab implements ITab { return split; } + private static boolean isScrollNearBottom(JScrollPane scrollPane) { + if (scrollPane == null) { + return true; + } + JScrollBar bar = scrollPane.getVerticalScrollBar(); + int max = Math.max(0, bar.getMaximum() - bar.getVisibleAmount()); + return bar.getValue() >= max - SCROLL_FOLLOW_THRESHOLD_PX; + } + + private static void scrollPaneToBottom(JScrollPane scrollPane) { + if (scrollPane == null) { + return; + } + JScrollBar bar = scrollPane.getVerticalScrollBar(); + bar.setValue(bar.getMaximum()); + } + private static void configureTextArea(JTextArea area, boolean monospaced) { area.setEditable(false); - area.setLineWrap(false); - area.setWrapStyleWord(false); + area.setLineWrap(true); + area.setWrapStyleWord(true); if (monospaced) { area.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12)); } else { @@ -381,24 +414,44 @@ final class CyberStrikeAITab implements ITab { private void wireActions() { validateButton.addActionListener(e -> { - validateButton.setEnabled(false); + if ("Cancel".equals(validateButton.getText())) { + cancelValidateInProgress(); + return; + } + validateButton.setText("Cancel"); + validateButton.setEnabled(true); + stopButton.setEnabled(true); statusLabel.setText("Validating..."); - log("Validating connection..."); - new Thread(() -> { + log("Validating connection... (max ~10s; click Cancel or Stop to abort)"); + Thread worker = new Thread(() -> { try { CyberStrikeAIClient.Config cfg = currentConfig(); String token = client.loginAndValidate(cfg); + if (Thread.currentThread().isInterrupted()) { + return; + } tokenRef.set(token); SwingUtilities.invokeLater(() -> statusLabel.setText("OK (token saved)")); log("Validation OK."); } catch (Exception ex) { tokenRef.set(""); - SwingUtilities.invokeLater(() -> statusLabel.setText("Failed: " + ex.getMessage())); - log("Validation failed: " + ex.getMessage()); + if (Thread.currentThread().isInterrupted()) { + SwingUtilities.invokeLater(() -> statusLabel.setText("Cancelled")); + log("Validation cancelled."); + } else { + SwingUtilities.invokeLater(() -> statusLabel.setText("Failed: " + ex.getMessage())); + log("Validation failed: " + ex.getMessage()); + } } finally { - SwingUtilities.invokeLater(() -> validateButton.setEnabled(true)); + validateThreadRef.set(null); + SwingUtilities.invokeLater(() -> { + validateButton.setText("Validate"); + validateButton.setEnabled(true); + }); } - }, "CyberStrikeAI-Validate").start(); + }, "CyberStrikeAI-Validate"); + validateThreadRef.set(worker); + worker.start(); }); clearButton.addActionListener(e -> { @@ -435,10 +488,23 @@ final class CyberStrikeAITab implements ITab { }); stopButton.addActionListener(e -> { + if ("Cancel".equals(validateButton.getText())) { + cancelValidateInProgress(); + return; + } + String runId = selectedRunId; + if (runId != null && client.hasActiveRequest()) { + client.cancelActiveRequest(); + appendProgressToRun(runId, "\n[info] Stream stopped.\n"); + setRunStatus(runId, "cancelled"); + return; + } + if (runId == null) return; TestRun run = runs.get(runId); if (run == null) return; + String token = getToken(); if (token == null || token.trim().isEmpty()) { appendProgressToRun(runId, "\n[error] Not validated.\n"); @@ -483,7 +549,8 @@ final class CyberStrikeAITab implements ITab { String host = hostField.getText().trim(); String port = portField.getText().trim(); String password = new String(passwordField.getPassword()); - String baseUrl = "http://" + host + ":" + port; + String scheme = useHttpsBox.isSelected() ? "https" : "http"; + String baseUrl = scheme + "://" + host + ":" + port; int idx = agentModeBox.getSelectedIndex(); CyberStrikeAIClient.AgentMode mode = (idx >= 0 && idx < AGENT_MODES.length) ? AGENT_MODES[idx] @@ -567,10 +634,31 @@ final class CyberStrikeAITab implements ITab { run.progressBuffer.append(s); } if (runId.equals(selectedRunId)) { - SwingUtilities.invokeLater(() -> { - progressArea.append(s); - progressArea.setCaretPosition(progressArea.getDocument().getLength()); - }); + SwingUtilities.invokeLater(() -> appendProgressUi(s, false)); + } + } + + private void appendProgressUi(String s, boolean forceFollow) { + JScrollBar bar = progressScrollPane != null ? progressScrollPane.getVerticalScrollBar() : null; + int scrollBefore = bar != null ? bar.getValue() : 0; + boolean follow = forceFollow || isScrollNearBottom(progressScrollPane); + progressArea.append(s); + if (follow) { + scrollPaneToBottom(progressScrollPane); + } else if (bar != null) { + bar.setValue(scrollBefore); + } + } + + private void appendFinalUi(String s, boolean forceFollow) { + JScrollBar bar = finalRawScrollPane != null ? finalRawScrollPane.getVerticalScrollBar() : null; + int scrollBefore = bar != null ? bar.getValue() : 0; + boolean follow = forceFollow || isScrollNearBottom(finalRawScrollPane); + finalRawArea.append(s); + if (follow) { + scrollPaneToBottom(finalRawScrollPane); + } else if (bar != null) { + bar.setValue(scrollBefore); } } @@ -620,10 +708,7 @@ final class CyberStrikeAITab implements ITab { run.finalBuffer.append(s); } if (runId.equals(selectedRunId)) { - SwingUtilities.invokeLater(() -> { - finalRawArea.append(s); - finalRawArea.setCaretPosition(finalRawArea.getDocument().getLength()); - }); + SwingUtilities.invokeLater(() -> appendFinalUi(s, false)); } } @@ -656,9 +741,9 @@ final class CyberStrikeAITab implements ITab { } SwingUtilities.invokeLater(() -> { progressArea.setText(progress); - progressArea.setCaretPosition(progressArea.getDocument().getLength()); + scrollPaneToBottom(progressScrollPane); finalRawArea.setText(fin); - finalRawArea.setCaretPosition(finalRawArea.getDocument().getLength()); + scrollPaneToBottom(finalRawScrollPane); requestArea.setText(run.requestRaw == null ? "" : run.requestRaw); responseArea.setText(run.responseRaw == null ? "" : run.responseRaw); refreshOutputView(); @@ -682,25 +767,36 @@ final class CyberStrikeAITab implements ITab { void clearAndShowStreamHeader(String title) { SwingUtilities.invokeLater(() -> { - progressArea.setText(""); - finalRawArea.setText(title + "\n\n"); + progressArea.setText("[*] " + title + "\n\n"); + finalRawArea.setText(""); + markdownPane.setText(""); }); } // Legacy helpers kept for Validate logging void appendStreamLine(String s) { if (s == null) return; - SwingUtilities.invokeLater(() -> { - progressArea.append(s); - progressArea.append("\n"); - progressArea.setCaretPosition(progressArea.getDocument().getLength()); - }); + SwingUtilities.invokeLater(() -> appendProgressUi(s + "\n", false)); } private void log(String s) { appendStreamLine("[*] " + s); } + private void cancelValidateInProgress() { + client.cancelActiveRequest(); + Thread t = validateThreadRef.getAndSet(null); + if (t != null) { + t.interrupt(); + } + SwingUtilities.invokeLater(() -> { + statusLabel.setText("Cancelled"); + validateButton.setText("Validate"); + validateButton.setEnabled(true); + }); + log("Validation cancelled."); + } + private void applyFilter() { String q = searchField.getText(); if (q == null) q = ""; diff --git a/plugins/burp-suite/cyberstrikeai-burp-extension/src/main/java/burp/SslTrustAll.java b/plugins/burp-suite/cyberstrikeai-burp-extension/src/main/java/burp/SslTrustAll.java new file mode 100644 index 00000000..aa8aca85 --- /dev/null +++ b/plugins/burp-suite/cyberstrikeai-burp-extension/src/main/java/burp/SslTrustAll.java @@ -0,0 +1,149 @@ +package burp; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.URL; +import java.security.cert.X509Certificate; + +/** + * Opens HTTPS connections without validating server certificates (self-signed / local dev). + * Applied per-connection only; does not change JVM-wide defaults for other Burp components. + */ +final class SslTrustAll { + + private static volatile SSLSocketFactory socketFactory; + private static final HostnameVerifier TRUST_ALL_HOSTS = (hostname, session) -> true; + + private SslTrustAll() { + } + + static HttpURLConnection open(URL url) throws IOException { + return open(url, 5_000, 30_000); + } + + static HttpURLConnection open(URL url, int connectTimeoutMs, int readTimeoutMs) throws IOException { + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setConnectTimeout(connectTimeoutMs); + conn.setReadTimeout(readTimeoutMs); + if (conn instanceof HttpsURLConnection) { + HttpsURLConnection https = (HttpsURLConnection) conn; + https.setSSLSocketFactory(new TimeoutSslSocketFactory(socketFactory(), connectTimeoutMs, readTimeoutMs)); + https.setHostnameVerifier(TRUST_ALL_HOSTS); + } + return conn; + } + + private static SSLSocketFactory socketFactory() { + SSLSocketFactory sf = socketFactory; + if (sf != null) { + return sf; + } + synchronized (SslTrustAll.class) { + sf = socketFactory; + if (sf != null) { + return sf; + } + try { + TrustManager[] trustAll = new TrustManager[]{ + new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) { + } + } + }; + SSLContext ctx = SSLContext.getInstance("TLS"); + ctx.init(null, trustAll, new java.security.SecureRandom()); + sf = ctx.getSocketFactory(); + socketFactory = sf; + return sf; + } catch (Exception e) { + throw new RuntimeException("Failed to initialize trust-all TLS", e); + } + } + } + + /** Ensures TCP connect + socket read respect timeouts (plain HttpURLConnection SSL can hang longer). */ + private static final class TimeoutSslSocketFactory extends SSLSocketFactory { + private final SSLSocketFactory delegate; + private final int connectTimeoutMs; + private final int readTimeoutMs; + + TimeoutSslSocketFactory(SSLSocketFactory delegate, int connectTimeoutMs, int readTimeoutMs) { + this.delegate = delegate; + this.connectTimeoutMs = connectTimeoutMs; + this.readTimeoutMs = readTimeoutMs; + } + + @Override + public String[] getDefaultCipherSuites() { + return delegate.getDefaultCipherSuites(); + } + + @Override + public String[] getSupportedCipherSuites() { + return delegate.getSupportedCipherSuites(); + } + + @Override + public Socket createSocket() throws IOException { + return tune(delegate.createSocket()); + } + + @Override + public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { + return tune(delegate.createSocket(s, host, port, autoClose)); + } + + @Override + public Socket createSocket(String host, int port) throws IOException { + Socket plain = new Socket(); + plain.connect(new InetSocketAddress(host, port), connectTimeoutMs); + return tune(delegate.createSocket(plain, host, port, true)); + } + + @Override + public Socket createSocket(String host, int port, java.net.InetAddress localHost, int localPort) throws IOException { + Socket plain = new Socket(); + plain.bind(new InetSocketAddress(localHost, localPort)); + plain.connect(new InetSocketAddress(host, port), connectTimeoutMs); + return tune(delegate.createSocket(plain, host, port, true)); + } + + @Override + public Socket createSocket(java.net.InetAddress host, int port) throws IOException { + Socket plain = new Socket(); + plain.connect(new InetSocketAddress(host, port), connectTimeoutMs); + return tune(delegate.createSocket(plain, host.getHostName(), port, true)); + } + + @Override + public Socket createSocket(java.net.InetAddress address, int port, java.net.InetAddress localAddress, int localPort) throws IOException { + Socket plain = new Socket(); + plain.bind(new InetSocketAddress(localAddress, localPort)); + plain.connect(new InetSocketAddress(address, port), connectTimeoutMs); + return tune(delegate.createSocket(plain, address.getHostName(), port, true)); + } + + private Socket tune(Socket socket) throws IOException { + socket.setSoTimeout(readTimeoutMs); + return socket; + } + } +} diff --git a/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/BurpExtender$1.class b/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/BurpExtender$1.class index 4921cbb9..bf57e524 100644 Binary files a/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/BurpExtender$1.class and b/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/BurpExtender$1.class differ diff --git a/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/BurpExtender.class b/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/BurpExtender.class index a271921f..086353b5 100644 Binary files a/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/BurpExtender.class and b/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/BurpExtender.class differ diff --git a/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAIClient$1.class b/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAIClient$1.class new file mode 100644 index 00000000..ccf18337 Binary files /dev/null and b/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAIClient$1.class differ diff --git a/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAIClient$AgentMode.class b/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAIClient$AgentMode.class index 26566ef6..fa12cc28 100644 Binary files a/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAIClient$AgentMode.class and b/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAIClient$AgentMode.class differ diff --git a/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAIClient$Config.class b/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAIClient$Config.class index f2a79c3c..1fb54851 100644 Binary files a/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAIClient$Config.class and b/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAIClient$Config.class differ diff --git a/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAIClient.class b/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAIClient.class index d85bd5ff..20fed65b 100644 Binary files a/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAIClient.class and b/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAIClient.class differ diff --git a/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAITab$1.class b/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAITab$1.class index 9fd5ec35..72341617 100644 Binary files a/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAITab$1.class and b/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAITab$1.class differ diff --git a/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAITab$DotIcon.class b/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAITab$DotIcon.class index a42c4763..5871502d 100644 Binary files a/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAITab$DotIcon.class and b/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAITab$DotIcon.class differ diff --git a/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAITab$TestRun.class b/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAITab$TestRun.class index 5d902bcd..9af5bf90 100644 Binary files a/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAITab$TestRun.class and b/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAITab$TestRun.class differ diff --git a/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAITab$TestRunCellRenderer.class b/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAITab$TestRunCellRenderer.class index 2f69f3ca..14160571 100644 Binary files a/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAITab$TestRunCellRenderer.class and b/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAITab$TestRunCellRenderer.class differ diff --git a/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAITab.class b/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAITab.class index aab4f092..7faa98c8 100644 Binary files a/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAITab.class and b/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/CyberStrikeAITab.class differ diff --git a/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/SslTrustAll$1.class b/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/SslTrustAll$1.class new file mode 100644 index 00000000..5f8c2dd9 Binary files /dev/null and b/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/SslTrustAll$1.class differ diff --git a/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/SslTrustAll$TimeoutSslSocketFactory.class b/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/SslTrustAll$TimeoutSslSocketFactory.class new file mode 100644 index 00000000..233a0def Binary files /dev/null and b/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/SslTrustAll$TimeoutSslSocketFactory.class differ diff --git a/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/SslTrustAll.class b/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/SslTrustAll.class new file mode 100644 index 00000000..568264d8 Binary files /dev/null and b/plugins/burp-suite/cyberstrikeai-burp-extension/target/classes/burp/SslTrustAll.class differ diff --git a/plugins/burp-suite/cyberstrikeai-burp-extension/target/cyberstrikeai-burp-extension-1.0.0.jar b/plugins/burp-suite/cyberstrikeai-burp-extension/target/cyberstrikeai-burp-extension-1.0.0.jar index 789f49da..91323400 100644 Binary files a/plugins/burp-suite/cyberstrikeai-burp-extension/target/cyberstrikeai-burp-extension-1.0.0.jar and b/plugins/burp-suite/cyberstrikeai-burp-extension/target/cyberstrikeai-burp-extension-1.0.0.jar differ diff --git a/plugins/burp-suite/cyberstrikeai-burp-extension/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/plugins/burp-suite/cyberstrikeai-burp-extension/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst index fd49ad75..e2b64ee9 100644 --- a/plugins/burp-suite/cyberstrikeai-burp-extension/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +++ b/plugins/burp-suite/cyberstrikeai-burp-extension/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -1,12 +1,16 @@ +burp/SslTrustAll.class +burp/SslTrustAll$TimeoutSslSocketFactory.class burp/CyberStrikeAIClient$StreamListener.class burp/CyberStrikeAIClient$Config.class burp/CyberStrikeAIClient$AgentMode.class burp/MarkdownRenderer.class burp/SimpleJson.class burp/CyberStrikeAIClient.class +burp/CyberStrikeAIClient$1.class burp/CyberStrikeAITab$DotIcon.class burp/CyberStrikeAITab.class burp/CyberStrikeAITab$1.class +burp/SslTrustAll$1.class burp/BurpExtender$1.class burp/BurpExtender.class burp/CyberStrikeAITab$TestRun.class diff --git a/plugins/burp-suite/cyberstrikeai-burp-extension/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/plugins/burp-suite/cyberstrikeai-burp-extension/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst index 1f4d83a9..00fabca4 100644 --- a/plugins/burp-suite/cyberstrikeai-burp-extension/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +++ b/plugins/burp-suite/cyberstrikeai-burp-extension/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -4,3 +4,4 @@ /Users/temp/Downloads/CyberStrikeAI-main/plugins/burp-suite/cyberstrikeai-burp-extension/src/main/java/burp/HttpMessageFormatter.java /Users/temp/Downloads/CyberStrikeAI-main/plugins/burp-suite/cyberstrikeai-burp-extension/src/main/java/burp/MarkdownRenderer.java /Users/temp/Downloads/CyberStrikeAI-main/plugins/burp-suite/cyberstrikeai-burp-extension/src/main/java/burp/SimpleJson.java +/Users/temp/Downloads/CyberStrikeAI-main/plugins/burp-suite/cyberstrikeai-burp-extension/src/main/java/burp/SslTrustAll.java