mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-03-31 08:19:54 +02:00
Add files via upload
This commit is contained in:
12
plugins/README.md
Normal file
12
plugins/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
## Plugins
|
||||
|
||||
This directory contains optional plugins/extensions that integrate CyberStrikeAI with other tools.
|
||||
|
||||
- `burp-suite/`: Burp Suite extensions
|
||||
|
||||
### Burp Suite Extension
|
||||
|
||||
- **Path**: `plugins/burp-suite/cyberstrikeai-burp-extension/`
|
||||
- **Build output**: `plugins/burp-suite/cyberstrikeai-burp-extension/dist/cyberstrikeai-burp-extension.jar`
|
||||
- **Docs**: see the plugin folder `README.md` / `README.zh-CN.md`
|
||||
|
||||
68
plugins/burp-suite/cyberstrikeai-burp-extension/README.md
Normal file
68
plugins/burp-suite/cyberstrikeai-burp-extension/README.md
Normal file
@@ -0,0 +1,68 @@
|
||||
## CyberStrikeAI Burp Suite Extension
|
||||
|
||||
中文说明见:`README.zh-CN.md`
|
||||
|
||||
### What it does
|
||||
|
||||
- Configure **Host / Port / Password** and choose **Single-Agent** or **Multi-Agent**
|
||||
- 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
|
||||
- Output is split into **collapsible Progress** + **Final Response** (Markdown rendering supported)
|
||||
- View captured **Request / Response** for each run
|
||||
- **Stop** a running task (calls `/api/agent-loop/cancel` once `conversationId` is available)
|
||||
|
||||
### Build
|
||||
|
||||
Requirements:
|
||||
|
||||
- JDK 11+
|
||||
- Maven (recommended) OR Burp Extender API jar (offline mode)
|
||||
|
||||
#### Option A (recommended): Maven build (no need to locate Burp)
|
||||
|
||||
```bash
|
||||
cd plugins/burp-suite/cyberstrikeai-burp-extension
|
||||
./build-mvn.sh
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
- `dist/cyberstrikeai-burp-extension.jar`
|
||||
|
||||
#### Option B: Offline build with `build.sh` (needs Burp API jar)
|
||||
|
||||
1) Create `lib/` and copy Burp's API jar into it:
|
||||
|
||||
```bash
|
||||
mkdir -p lib
|
||||
# copy from your Burp installation, for example:
|
||||
# cp "/path/to/burp-extender-api.jar" lib/
|
||||
```
|
||||
|
||||
2) Build:
|
||||
|
||||
```bash
|
||||
cd plugins/burp-suite/cyberstrikeai-burp-extension
|
||||
./build.sh
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
- `dist/cyberstrikeai-burp-extension.jar`
|
||||
|
||||
#### Option C: Gradle (optional)
|
||||
|
||||
If you already have Gradle available, you can still use `build.gradle` to build.
|
||||
|
||||
### Load in Burp Suite
|
||||
|
||||
- Burp Suite → **Extensions** → **Installed** → **Add**
|
||||
- Extension type: **Java**
|
||||
- Select the jar above
|
||||
|
||||
### Notes
|
||||
|
||||
- This extension connects to your CyberStrikeAI server (default is `http://127.0.0.1:8080`).
|
||||
- It uses **Bearer Token** authentication obtained from the configured password.
|
||||
|
||||
108
plugins/burp-suite/cyberstrikeai-burp-extension/README.zh-CN.md
Normal file
108
plugins/burp-suite/cyberstrikeai-burp-extension/README.zh-CN.md
Normal file
@@ -0,0 +1,108 @@
|
||||
## CyberStrikeAI Burp Suite 插件(中文说明)
|
||||
|
||||
### 功能概述
|
||||
|
||||
- 在 Burp 的 `CyberStrikeAI` 标签页中配置 **Host、端口、密码、单/多 Agent**
|
||||
- 点击 **Validate(验证)**:
|
||||
- 调用 `POST /api/auth/login` 用密码换取 Token
|
||||
- 调用 `GET /api/auth/validate` 校验 Token
|
||||
- 验证通过后 Token 会保存在插件内存中(本次 Burp 会话有效)
|
||||
- 右键任意 HTTP 请求包 → **Send to CyberStrikeAI (stream test)**:
|
||||
- 将该 HTTP 请求(含 headers/body;若存在响应则附带截断片段)发送到 CyberStrikeAI
|
||||
- 以 **SSE 流式**接收返回内容,并在标签页中实时展示
|
||||
- 单 Agent:`POST /api/agent-loop/stream`
|
||||
- 多 Agent:`POST /api/multi-agent/stream`(需要服务端启用 `multi_agent.enabled: true`)
|
||||
- **测试历史侧边栏(可搜索)**:每次发送都会新增一条记录,方便回看与对比
|
||||
- **Output 分区**:`Progress`(可折叠)+ `Final Response`(主区域)
|
||||
- **Markdown 渲染**:最终输出可在 Output 主区域渲染为富文本(可开关)
|
||||
- **Request / Response 回看**:右侧 Tab 可直接查看该次捕获到的原始请求/响应
|
||||
- **Stop 取消**:任务创建会话后可调用 `/api/agent-loop/cancel` 停止当前会话任务
|
||||
|
||||
### 编译(不依赖 Gradle/Maven,推荐)
|
||||
|
||||
> 给普通用户:你们应当直接发 **编译好的 jar**,用户在 Burp 里加载即可,**不需要编译**。
|
||||
|
||||
#### 方式 A(推荐,通用):用 Maven 编译(不需要知道 Burp 在哪)
|
||||
|
||||
适合:开发者/CI 打包一次,发布给所有用户使用。
|
||||
|
||||
环境要求:
|
||||
|
||||
- JDK 11+
|
||||
- Maven(会从 Maven Central 下载 `burp-extender-api` 依赖)
|
||||
|
||||
编译打包:
|
||||
|
||||
```bash
|
||||
cd plugins/burp-suite/cyberstrikeai-burp-extension
|
||||
./build-mvn.sh
|
||||
```
|
||||
|
||||
产物:
|
||||
|
||||
- `dist/cyberstrikeai-burp-extension.jar`
|
||||
|
||||
#### 方式 B(离线):纯 JDK 编译(需要 Burp 的 API jar)
|
||||
|
||||
- JDK 11+
|
||||
- Burp Extender API 的 jar(来自你的 Burp 安装目录)
|
||||
|
||||
#### 步骤
|
||||
|
||||
1) 在插件目录创建 `lib/`,并把 `burp-extender-api.jar` 复制进去:
|
||||
|
||||
```bash
|
||||
cd plugins/burp-suite/cyberstrikeai-burp-extension
|
||||
mkdir -p lib
|
||||
# 复制 Burp 自带的 API jar 到这里,例如:
|
||||
# cp "/path/to/burp-extender-api.jar" lib/
|
||||
```
|
||||
|
||||
2) 一键编译打包:
|
||||
|
||||
```bash
|
||||
cd plugins/burp-suite/cyberstrikeai-burp-extension
|
||||
./build.sh
|
||||
```
|
||||
|
||||
产物:
|
||||
|
||||
- `dist/cyberstrikeai-burp-extension.jar`
|
||||
|
||||
### 在 Burp Suite 中加载
|
||||
|
||||
- Burp Suite → **Extensions** → **Installed** → **Add**
|
||||
- Extension type:**Java**
|
||||
- 选择 `dist/cyberstrikeai-burp-extension.jar`
|
||||
|
||||
### 使用方法
|
||||
|
||||
1) 打开 Burp 顶部标签页 `CyberStrikeAI`
|
||||
2) 填写:
|
||||
- **Host**:例如 `127.0.0.1`
|
||||
- **Port**:例如 `8080`
|
||||
- **Password**:你的 CyberStrikeAI 登录密码(对应服务端 `config.yaml` 的 `auth.password`)
|
||||
- **Agent mode**:选择 `Single Agent` 或 `Multi Agent`
|
||||
3) 点击 **Validate**
|
||||
- 成功:状态显示 `OK (token saved)`
|
||||
- 失败:状态会显示错误原因(例如密码错误、服务不可达、401/403 等)
|
||||
4) 在 Burp 的 Proxy/HTTP history/Repeater 等列表中选中一条 HTTP 包
|
||||
5) 右键 → **Send to CyberStrikeAI (stream test)**
|
||||
6) 每次发送后会在 `CyberStrikeAI` 标签页左侧显示一个“测试记录”(请求标题 + 单/多 Agent + 状态);点击对应记录即可在右侧查看该次的流式输出结果
|
||||
|
||||
### 常见问题(排错)
|
||||
|
||||
- **Validate 失败 / 401**
|
||||
- 确认密码是否正确(服务端 `auth.password`)
|
||||
- 确认 IP/端口是否能访问(例如浏览器能打开 `http://IP:PORT/`)
|
||||
- 若服务器启用了反向代理/HTTPS,需要把插件里 baseUrl 改成对应协议与端口(当前插件默认使用 `http://`)
|
||||
|
||||
- **选择 Multi Agent 后提示“多代理未启用”**
|
||||
- 服务端需要开启:`config.yaml` 中 `multi_agent.enabled: true`
|
||||
- 并重启服务(或按你们项目的动态 apply 配置流程启用)
|
||||
|
||||
- **右键发送后无流式输出**
|
||||
- 先确认已 Validate(拿到 Token)
|
||||
- 确认 Burp 能访问到 CyberStrikeAI(网络/代理/防火墙)
|
||||
- 服务端的流式端点为 SSE,插件会解析 `data: {json}` 行;如果中间件缓冲可能影响实时性
|
||||
|
||||
45
plugins/burp-suite/cyberstrikeai-burp-extension/build-mvn.sh
Normal file
45
plugins/burp-suite/cyberstrikeai-burp-extension/build-mvn.sh
Normal file
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
DIST_DIR="$ROOT_DIR/dist"
|
||||
|
||||
MVN_BIN=""
|
||||
if command -v mvn >/dev/null 2>&1; then
|
||||
MVN_BIN="mvn"
|
||||
else
|
||||
# Auto-provision Maven for developer convenience.
|
||||
# This is only used to build the jar once in CI/dev; Burp users don't need to run this.
|
||||
MAVEN_VERSION="3.9.6"
|
||||
BASE_DIR="${HOME}/.cache/cyberstrikeai-burp-extension"
|
||||
MAVEN_DIR="$BASE_DIR/apache-maven-$MAVEN_VERSION"
|
||||
MAVEN_TGZ="$BASE_DIR/apache-maven-$MAVEN_VERSION-bin.tar.gz"
|
||||
MAVEN_URL="https://archive.apache.org/dist/maven/maven-3/$MAVEN_VERSION/binaries/apache-maven-$MAVEN_VERSION-bin.tar.gz"
|
||||
|
||||
if [[ -x "$MAVEN_DIR/bin/mvn" ]]; then
|
||||
MVN_BIN="$MAVEN_DIR/bin/mvn"
|
||||
else
|
||||
echo "[*] Maven not found. Downloading Maven $MAVEN_VERSION ..."
|
||||
mkdir -p "$BASE_DIR"
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -fsSL "$MAVEN_URL" -o "$MAVEN_TGZ"
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
wget -q "$MAVEN_URL" -O "$MAVEN_TGZ"
|
||||
else
|
||||
echo "Missing: curl/wget (needed to download Maven)."
|
||||
exit 1
|
||||
fi
|
||||
tar -xzf "$MAVEN_TGZ" -C "$BASE_DIR"
|
||||
MVN_BIN="$MAVEN_DIR/bin/mvn"
|
||||
fi
|
||||
fi
|
||||
|
||||
rm -rf "$DIST_DIR"
|
||||
mkdir -p "$DIST_DIR"
|
||||
|
||||
echo "[*] Building with Maven (downloads Burp API from Maven Central)..."
|
||||
(cd "$ROOT_DIR" && "$MVN_BIN" -q -DskipTests package)
|
||||
|
||||
cp "$ROOT_DIR/target/cyberstrikeai-burp-extension-1.0.0.jar" "$DIST_DIR/cyberstrikeai-burp-extension.jar"
|
||||
echo "[+] Done: $DIST_DIR/cyberstrikeai-burp-extension.jar"
|
||||
|
||||
45
plugins/burp-suite/cyberstrikeai-burp-extension/build.gradle
Normal file
45
plugins/burp-suite/cyberstrikeai-burp-extension/build.gradle
Normal file
@@ -0,0 +1,45 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'com.github.johnrengelman.shadow' version '8.1.1'
|
||||
}
|
||||
|
||||
group = 'ai.cyberstrike'
|
||||
version = '1.0.0'
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(11)
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Burp Extender API (legacy). Burp will provide the interfaces at runtime, but we compile against it.
|
||||
implementation 'net.portswigger.burp.extender:burp-extender-api:2.3'
|
||||
|
||||
// JSON parsing for SSE payloads.
|
||||
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.2'
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
options.encoding = 'UTF-8'
|
||||
options.release = 11
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes(
|
||||
'Main-Class': 'burp.BurpExtender'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
archiveBaseName.set('cyberstrikeai-burp-extension')
|
||||
archiveClassifier.set('all')
|
||||
archiveVersion.set('')
|
||||
}
|
||||
|
||||
35
plugins/burp-suite/cyberstrikeai-burp-extension/build.sh
Normal file
35
plugins/burp-suite/cyberstrikeai-burp-extension/build.sh
Normal file
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
LIB_DIR="$ROOT_DIR/lib"
|
||||
DIST_DIR="$ROOT_DIR/dist"
|
||||
BUILD_DIR="$ROOT_DIR/.build"
|
||||
|
||||
API_JAR="$LIB_DIR/burp-extender-api.jar"
|
||||
|
||||
if [[ ! -f "$API_JAR" ]]; then
|
||||
echo "Missing: $API_JAR"
|
||||
echo "Please copy Burp's burp-extender-api.jar into plugins/burp-suite/cyberstrikeai-burp-extension/lib/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -rf "$BUILD_DIR" "$DIST_DIR"
|
||||
mkdir -p "$BUILD_DIR" "$DIST_DIR"
|
||||
|
||||
SRC_FILES=$(find "$ROOT_DIR/src/main/java" -name "*.java")
|
||||
|
||||
echo "[*] Compiling..."
|
||||
javac \
|
||||
-encoding UTF-8 \
|
||||
--release 11 \
|
||||
-cp "$API_JAR" \
|
||||
-d "$BUILD_DIR" \
|
||||
$SRC_FILES
|
||||
|
||||
echo "[*] Packaging..."
|
||||
JAR_OUT="$DIST_DIR/cyberstrikeai-burp-extension.jar"
|
||||
jar --create --file "$JAR_OUT" --main-class burp.BurpExtender -C "$BUILD_DIR" .
|
||||
|
||||
echo "[+] Done: $JAR_OUT"
|
||||
|
||||
BIN
plugins/burp-suite/cyberstrikeai-burp-extension/dist/cyberstrikeai-burp-extension.jar
vendored
Normal file
BIN
plugins/burp-suite/cyberstrikeai-burp-extension/dist/cyberstrikeai-burp-extension.jar
vendored
Normal file
Binary file not shown.
44
plugins/burp-suite/cyberstrikeai-burp-extension/pom.xml
Normal file
44
plugins/burp-suite/cyberstrikeai-burp-extension/pom.xml
Normal file
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>ai.cyberstrike</groupId>
|
||||
<artifactId>cyberstrikeai-burp-extension</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<name>CyberStrikeAI Burp Suite Extension</name>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.release>11</maven.compiler.release>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- Compile-only: Burp provides these classes at runtime -->
|
||||
<dependency>
|
||||
<groupId>net.portswigger.burp.extender</groupId>
|
||||
<artifactId>burp-extender-api</artifactId>
|
||||
<version>2.3</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.4.2</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>burp.BurpExtender</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
rootProject.name = "cyberstrikeai-burp-extension"
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
package burp;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class BurpExtender implements IBurpExtender, IContextMenuFactory {
|
||||
private IBurpExtenderCallbacks callbacks;
|
||||
private IExtensionHelpers helpers;
|
||||
|
||||
private CyberStrikeAITab tab;
|
||||
private final CyberStrikeAIClient client = new CyberStrikeAIClient();
|
||||
|
||||
@Override
|
||||
public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) {
|
||||
this.callbacks = callbacks;
|
||||
this.helpers = callbacks.getHelpers();
|
||||
|
||||
callbacks.setExtensionName("CyberStrikeAI Extension");
|
||||
|
||||
this.tab = new CyberStrikeAITab();
|
||||
callbacks.addSuiteTab(tab);
|
||||
|
||||
callbacks.registerContextMenuFactory(this);
|
||||
|
||||
callbacks.printOutput("CyberStrikeAI extension loaded.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JMenuItem> createMenuItems(IContextMenuInvocation invocation) {
|
||||
List<JMenuItem> items = new ArrayList<>();
|
||||
|
||||
JMenuItem sendItem = new JMenuItem("Send to CyberStrikeAI (stream test)");
|
||||
sendItem.addActionListener(e -> {
|
||||
IHttpRequestResponse[] selected = invocation.getSelectedMessages();
|
||||
if (selected == null || selected.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
CyberStrikeAIClient.Config cfg = tab.currentConfig();
|
||||
String token = tab.getToken();
|
||||
if (token == null || token.trim().isEmpty()) {
|
||||
JOptionPane.showMessageDialog(tab.getUiComponent(),
|
||||
"Please click Validate first to obtain a token.",
|
||||
"CyberStrikeAI", JOptionPane.WARNING_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
String prompt = HttpMessageFormatter.toPrompt(helpers, selected[0]);
|
||||
String title = HttpMessageFormatter.getRequestTitle(helpers, selected[0]);
|
||||
String agentModeStr = (cfg.agentMode == CyberStrikeAIClient.AgentMode.MULTI) ? "Multi Agent" : "Single Agent";
|
||||
String runId = tab.startNewRun(title, agentModeStr, selected[0]);
|
||||
tab.appendProgressToRun(runId, "\n[server] " + cfg.baseUrl + "\n\n");
|
||||
|
||||
client.streamTest(cfg, token, prompt, new CyberStrikeAIClient.StreamListener() {
|
||||
@Override
|
||||
public void onEvent(String type, String message, String rawJson) {
|
||||
if (type == null) type = "";
|
||||
switch (type) {
|
||||
case "response_delta":
|
||||
case "eino_agent_reply_stream_delta":
|
||||
// delta chunk (content only)
|
||||
tab.appendFinalToRun(runId, message);
|
||||
break;
|
||||
case "response":
|
||||
// final response (full)
|
||||
tab.appendFinalToRun(runId, "\n\n--- Final Response ---\n");
|
||||
tab.appendFinalToRun(runId, message);
|
||||
tab.setFinalResponse(runId, message);
|
||||
break;
|
||||
case "progress":
|
||||
tab.appendProgressToRun(runId, "\n[progress] " + message + "\n");
|
||||
tab.setRunStatus(runId, "running");
|
||||
break;
|
||||
case "cancelled":
|
||||
tab.appendProgressToRun(runId, "\n[cancelled] " + message + "\n");
|
||||
tab.setRunStatus(runId, "cancelled");
|
||||
break;
|
||||
case "error":
|
||||
tab.appendProgressToRun(runId, "\n[error] " + message + "\n");
|
||||
tab.setRunStatus(runId, "error");
|
||||
break;
|
||||
case "thinking_stream_start":
|
||||
case "thinking_stream_delta":
|
||||
case "tool_call":
|
||||
case "tool_result":
|
||||
case "tool_result_delta":
|
||||
// debug; hide by default
|
||||
if (tab.isShowDebugEvents() && message != null && !message.isEmpty()) {
|
||||
tab.appendProgressToRun(runId, "\n[" + type + "] " + message + "\n");
|
||||
}
|
||||
break;
|
||||
case "conversation":
|
||||
// Capture conversationId for stop/cancel.
|
||||
if (rawJson != null) {
|
||||
String convId = SimpleJson.extractStringField(rawJson, "conversationId");
|
||||
if (convId != null && !convId.trim().isEmpty()) {
|
||||
tab.setRunConversationId(runId, convId);
|
||||
}
|
||||
}
|
||||
if (tab.isShowDebugEvents() && message != null && !message.isEmpty()) {
|
||||
tab.appendProgressToRun(runId, "\n[" + type + "] " + message + "\n");
|
||||
}
|
||||
break;
|
||||
case "done":
|
||||
// handled in onDone too
|
||||
break;
|
||||
default:
|
||||
if (tab.isShowDebugEvents() && message != null && !message.isEmpty()) {
|
||||
tab.appendProgressToRun(runId, "\n[" + type + "] " + message + "\n");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String message, Exception e) {
|
||||
tab.appendProgressToRun(runId, "\n[error] " + message + "\n");
|
||||
tab.setRunStatus(runId, "error");
|
||||
callbacks.printError("CyberStrikeAI stream error: " + message);
|
||||
if (e != null) {
|
||||
callbacks.printError(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDone() {
|
||||
tab.appendProgressToRun(runId, "\n\n[done]\n");
|
||||
tab.setRunStatus(runId, "done");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
items.add(sendItem);
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
package burp;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
final class CyberStrikeAIClient {
|
||||
|
||||
static final class Config {
|
||||
final String baseUrl; // e.g. http://127.0.0.1:8080
|
||||
final String password;
|
||||
final AgentMode agentMode;
|
||||
|
||||
Config(String baseUrl, String password, AgentMode agentMode) {
|
||||
this.baseUrl = baseUrl;
|
||||
this.password = password;
|
||||
this.agentMode = agentMode;
|
||||
}
|
||||
}
|
||||
|
||||
enum AgentMode {
|
||||
SINGLE,
|
||||
MULTI
|
||||
}
|
||||
|
||||
interface StreamListener {
|
||||
void onEvent(String type, String message, String rawJson);
|
||||
void onError(String message, Exception e);
|
||||
void onDone();
|
||||
}
|
||||
|
||||
String loginAndValidate(Config cfg) throws IOException {
|
||||
String token = login(cfg.baseUrl, cfg.password);
|
||||
validate(cfg.baseUrl, token);
|
||||
return token;
|
||||
}
|
||||
|
||||
private String login(String baseUrl, String password) throws IOException {
|
||||
URL url = new URL(baseUrl + "/api/auth/login");
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setDoOutput(true);
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
conn.setRequestProperty("Accept", "application/json");
|
||||
String body = "{\"password\":\"" + escapeJson(password) + "\"}";
|
||||
try (OutputStream os = conn.getOutputStream()) {
|
||||
os.write(body.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
int code = conn.getResponseCode();
|
||||
String contentType = conn.getHeaderField("Content-Type");
|
||||
String resp = readAll(code >= 200 && code < 300 ? conn.getInputStream() : conn.getErrorStream());
|
||||
|
||||
// Friendly diagnosis: HTML usually means wrong host/port (e.g., hit Burp UI/proxy page).
|
||||
if (looksLikeHtml(resp) || (contentType != null && contentType.toLowerCase().contains("text/html"))) {
|
||||
throw new IOException("Login failed: server returned HTML, not API JSON. Check IP/Port and ensure you point to CyberStrikeAI backend.");
|
||||
}
|
||||
|
||||
String serverError = SimpleJson.extractStringField(resp, "error");
|
||||
if (code < 200 || code >= 300) {
|
||||
if (!serverError.isEmpty()) {
|
||||
throw new IOException("Login failed (" + code + "): " + serverError);
|
||||
}
|
||||
throw new IOException("Login failed (" + code + ").");
|
||||
}
|
||||
|
||||
if (!serverError.isEmpty()) {
|
||||
throw new IOException("Login failed: " + serverError);
|
||||
}
|
||||
|
||||
String token = SimpleJson.extractStringField(resp, "token");
|
||||
if (token.isEmpty()) {
|
||||
throw new IOException("Login response missing token. Check backend address and credentials.");
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
private void validate(String baseUrl, String token) throws IOException {
|
||||
URL url = new URL(baseUrl + "/api/auth/validate");
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("GET");
|
||||
conn.setRequestProperty("Authorization", "Bearer " + token);
|
||||
int code = conn.getResponseCode();
|
||||
String resp = readAll(code >= 200 && code < 300 ? conn.getInputStream() : conn.getErrorStream());
|
||||
if (code < 200 || code >= 300) {
|
||||
throw new IOException("Validate failed (" + code + "): " + resp);
|
||||
}
|
||||
}
|
||||
|
||||
void streamTest(Config cfg, String token, String message, StreamListener listener) {
|
||||
String path = (cfg.agentMode == AgentMode.MULTI) ? "/api/multi-agent/stream" : "/api/agent-loop/stream";
|
||||
String urlStr = cfg.baseUrl + path;
|
||||
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
payload.put("message", message);
|
||||
payload.put("conversationId", "");
|
||||
payload.put("role", "");
|
||||
|
||||
new Thread(() -> {
|
||||
HttpURLConnection conn = null;
|
||||
try {
|
||||
URL url = new URL(urlStr);
|
||||
conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setDoOutput(true);
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
conn.setRequestProperty("Accept", "text/event-stream");
|
||||
conn.setRequestProperty("Authorization", "Bearer " + token);
|
||||
|
||||
String body = toJson(payload);
|
||||
try (OutputStream os = conn.getOutputStream()) {
|
||||
os.write(body.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
int code = conn.getResponseCode();
|
||||
InputStream is = (code >= 200 && code < 300) ? conn.getInputStream() : conn.getErrorStream();
|
||||
if (is == null) {
|
||||
throw new IOException("No response body (HTTP " + code + ")");
|
||||
}
|
||||
|
||||
try (BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
// SSE format: "data: {json}"
|
||||
if (line.startsWith("data:")) {
|
||||
String json = line.substring("data:".length()).trim();
|
||||
if (!json.isEmpty()) {
|
||||
String type = SimpleJson.extractStringField(json, "type");
|
||||
String msg = SimpleJson.extractStringField(json, "message");
|
||||
listener.onEvent(type, msg, json);
|
||||
if ("done".equals(type)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
listener.onDone();
|
||||
} catch (Exception e) {
|
||||
listener.onError(e.getMessage(), e);
|
||||
} finally {
|
||||
if (conn != null) {
|
||||
conn.disconnect();
|
||||
}
|
||||
}
|
||||
}, "CyberStrikeAI-Stream").start();
|
||||
}
|
||||
|
||||
void cancelByConversationId(String baseUrl, String token, String conversationId) throws IOException {
|
||||
if (conversationId == null || conversationId.trim().isEmpty()) {
|
||||
throw new IOException("Missing conversationId.");
|
||||
}
|
||||
URL url = new URL(baseUrl + "/api/agent-loop/cancel");
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setDoOutput(true);
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
conn.setRequestProperty("Accept", "application/json");
|
||||
conn.setRequestProperty("Authorization", "Bearer " + token);
|
||||
|
||||
String body = "{\"conversationId\":\"" + escapeJson(conversationId.trim()) + "\"}";
|
||||
try (OutputStream os = conn.getOutputStream()) {
|
||||
os.write(body.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
int code = conn.getResponseCode();
|
||||
String resp = readAll(code >= 200 && code < 300 ? conn.getInputStream() : conn.getErrorStream());
|
||||
if (code < 200 || code >= 300) {
|
||||
String serverError = SimpleJson.extractStringField(resp, "error");
|
||||
if (!serverError.isEmpty()) {
|
||||
throw new IOException("Cancel failed (" + code + "): " + serverError);
|
||||
}
|
||||
throw new IOException("Cancel failed (" + code + ").");
|
||||
}
|
||||
}
|
||||
|
||||
private static String toJson(Map<String, Object> payload) {
|
||||
String message = payload.get("message") != null ? String.valueOf(payload.get("message")) : "";
|
||||
String conversationId = payload.get("conversationId") != null ? String.valueOf(payload.get("conversationId")) : "";
|
||||
String role = payload.get("role") != null ? String.valueOf(payload.get("role")) : "";
|
||||
return "{"
|
||||
+ "\"message\":\"" + escapeJson(message) + "\","
|
||||
+ "\"conversationId\":\"" + escapeJson(conversationId) + "\","
|
||||
+ "\"role\":\"" + escapeJson(role) + "\""
|
||||
+ "}";
|
||||
}
|
||||
|
||||
private static String escapeJson(String s) {
|
||||
if (s == null) return "";
|
||||
StringBuilder sb = new StringBuilder(s.length() + 16);
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
switch (c) {
|
||||
case '\\': sb.append("\\\\"); break;
|
||||
case '"': sb.append("\\\""); break;
|
||||
case '\n': sb.append("\\n"); break;
|
||||
case '\r': sb.append("\\r"); break;
|
||||
case '\t': sb.append("\\t"); break;
|
||||
default:
|
||||
if (c < 0x20) {
|
||||
sb.append(String.format("\\u%04x", (int) c));
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String readAll(InputStream is) throws IOException {
|
||||
if (is == null) return "";
|
||||
try (BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
sb.append(line).append('\n');
|
||||
}
|
||||
return sb.toString().trim();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean looksLikeHtml(String s) {
|
||||
if (s == null) return false;
|
||||
String t = s.trim().toLowerCase();
|
||||
return t.startsWith("<!doctype html") || t.startsWith("<html") || t.contains("<head>") || t.contains("<body");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,724 @@
|
||||
package burp;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
final class CyberStrikeAITab implements ITab {
|
||||
private final JPanel root = new JPanel(new BorderLayout());
|
||||
|
||||
private final JTextField hostField = new JTextField("127.0.0.1");
|
||||
private final JTextField portField = new JTextField("8080");
|
||||
private final JPasswordField passwordField = new JPasswordField();
|
||||
private final JComboBox<String> agentModeBox = new JComboBox<>(new String[]{"Single Agent", "Multi Agent"});
|
||||
private final JButton validateButton = new JButton("Validate");
|
||||
private final JButton clearButton = new JButton("Clear Output");
|
||||
private final JButton stopButton = new JButton("Stop");
|
||||
private final JButton copyButton = new JButton("Copy");
|
||||
private final JButton clearAllButton = new JButton("Clear All");
|
||||
private final JLabel statusLabel = new JLabel("Not validated");
|
||||
private final JCheckBox showDebugEventsBox = new JCheckBox("Show debug events", false);
|
||||
private final JCheckBox renderMarkdownBox = new JCheckBox("Render Markdown", true);
|
||||
|
||||
private final JTextArea progressArea = new JTextArea();
|
||||
private final JTextArea finalRawArea = new JTextArea(); // raw final stream / final response
|
||||
private final JEditorPane markdownPane = new JEditorPane("text/html", "");
|
||||
private final CardLayout outputCardsLayout = new CardLayout();
|
||||
private final JPanel outputCards = new JPanel(outputCardsLayout);
|
||||
private final JPanel outputRoot = new JPanel(new BorderLayout());
|
||||
private final JPanel progressContainer = new JPanel(new CardLayout());
|
||||
private final JToggleButton progressToggle = new JToggleButton("Progress ▾", true);
|
||||
private final JTextArea requestArea = new JTextArea();
|
||||
private final JTextArea responseArea = new JTextArea();
|
||||
private final JTabbedPane rightTabs = new JTabbedPane();
|
||||
|
||||
private final CyberStrikeAIClient client = new CyberStrikeAIClient();
|
||||
private final AtomicReference<String> tokenRef = new AtomicReference<>("");
|
||||
|
||||
private final DefaultListModel<TestRun> testListModel = new DefaultListModel<>();
|
||||
private final JList<TestRun> testList = new JList<>(testListModel);
|
||||
private final DefaultListModel<TestRun> filteredListModel = new DefaultListModel<>();
|
||||
private final JList<TestRun> filteredList = new JList<>(filteredListModel);
|
||||
private final JTextField searchField = new JTextField();
|
||||
private final Map<String, TestRun> runs = new HashMap<>();
|
||||
private final Map<String, Integer> runIdToIndex = new HashMap<>();
|
||||
private final AtomicInteger runSeq = new AtomicInteger(1);
|
||||
private String selectedRunId = null;
|
||||
|
||||
private static final class TestRun {
|
||||
final String id;
|
||||
final String title;
|
||||
final String agentMode;
|
||||
final StringBuilder buffer = new StringBuilder();
|
||||
final StringBuilder progressBuffer = new StringBuilder();
|
||||
final StringBuilder finalBuffer = new StringBuilder();
|
||||
String status;
|
||||
String conversationId;
|
||||
String requestRaw;
|
||||
String responseRaw;
|
||||
String finalResponse;
|
||||
|
||||
TestRun(String id, String title, String agentMode) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.agentMode = agentMode;
|
||||
this.status = "running";
|
||||
this.conversationId = "";
|
||||
this.requestRaw = "";
|
||||
this.responseRaw = "";
|
||||
this.finalResponse = "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
CyberStrikeAITab() {
|
||||
root.add(buildConfigPanel(), BorderLayout.NORTH);
|
||||
root.add(buildMainPane(), BorderLayout.CENTER);
|
||||
wireActions();
|
||||
}
|
||||
|
||||
private JComponent buildConfigPanel() {
|
||||
// Best-practice toolbar layout:
|
||||
// Row 1 = connection settings
|
||||
// Row 2 = run controls + view options
|
||||
JPanel rootPanel = new JPanel();
|
||||
rootPanel.setLayout(new BoxLayout(rootPanel, BoxLayout.Y_AXIS));
|
||||
rootPanel.setBorder(BorderFactory.createEmptyBorder(4, 6, 4, 6));
|
||||
|
||||
hostField.setColumns(14);
|
||||
portField.setColumns(6);
|
||||
passwordField.setColumns(12);
|
||||
agentModeBox.setPreferredSize(new Dimension(160, agentModeBox.getPreferredSize().height));
|
||||
|
||||
JPanel row1 = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 2));
|
||||
row1.add(new JLabel("Host"));
|
||||
row1.add(hostField);
|
||||
row1.add(new JLabel("Port"));
|
||||
row1.add(portField);
|
||||
row1.add(new JLabel("Password"));
|
||||
row1.add(passwordField);
|
||||
row1.add(validateButton);
|
||||
row1.add(statusLabel);
|
||||
|
||||
JPanel row2 = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 2));
|
||||
row2.add(new JLabel("Agent"));
|
||||
row2.add(agentModeBox);
|
||||
row2.add(stopButton);
|
||||
row2.add(copyButton);
|
||||
row2.add(clearButton);
|
||||
row2.add(showDebugEventsBox);
|
||||
row2.add(renderMarkdownBox);
|
||||
|
||||
rootPanel.add(row1);
|
||||
rootPanel.add(row2);
|
||||
return rootPanel;
|
||||
}
|
||||
|
||||
private JComponent buildMainPane() {
|
||||
JPanel sidebarPanel = buildSidebarPanel();
|
||||
JComponent right = buildRightPanel();
|
||||
|
||||
JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, sidebarPanel, right);
|
||||
split.setResizeWeight(0.25);
|
||||
split.setBorder(null);
|
||||
return split;
|
||||
}
|
||||
|
||||
private JPanel buildSidebarPanel() {
|
||||
JPanel p = new JPanel(new BorderLayout());
|
||||
filteredList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
|
||||
filteredList.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 12));
|
||||
filteredList.setCellRenderer(new TestRunCellRenderer());
|
||||
filteredList.addListSelectionListener(e -> {
|
||||
if (!e.getValueIsAdjusting()) {
|
||||
String id = getSelectedRunIdFromList();
|
||||
if (id != null) {
|
||||
setLogAreaToRun(id);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
JLabel title = new JLabel("Test History");
|
||||
title.setBorder(BorderFactory.createEmptyBorder(6, 8, 6, 8));
|
||||
|
||||
JPanel top = new JPanel(new BorderLayout(8, 6));
|
||||
top.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 8));
|
||||
top.add(title, BorderLayout.NORTH);
|
||||
searchField.setToolTipText("Search runs (title)");
|
||||
top.add(searchField, BorderLayout.SOUTH);
|
||||
|
||||
JScrollPane sp = new JScrollPane(filteredList);
|
||||
sp.setBorder(BorderFactory.createTitledBorder("Runs"));
|
||||
|
||||
clearAllButton.addActionListener(e -> clearAllRuns());
|
||||
JPanel bottom = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 6));
|
||||
bottom.add(clearAllButton);
|
||||
|
||||
p.add(top, BorderLayout.NORTH);
|
||||
p.add(sp, BorderLayout.CENTER);
|
||||
p.add(bottom, BorderLayout.SOUTH);
|
||||
p.setPreferredSize(new Dimension(320, 200));
|
||||
return p;
|
||||
}
|
||||
|
||||
private JComponent buildRightPanel() {
|
||||
configureTextArea(progressArea, true);
|
||||
configureTextArea(finalRawArea, true);
|
||||
markdownPane.setEditable(false);
|
||||
markdownPane.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE);
|
||||
markdownPane.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 12));
|
||||
markdownPane.setOpaque(true);
|
||||
markdownPane.setBackground(Color.WHITE);
|
||||
|
||||
configureTextArea(requestArea, false);
|
||||
configureTextArea(responseArea, false);
|
||||
|
||||
outputCards.add(new JScrollPane(finalRawArea), "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));
|
||||
return rightTabs;
|
||||
}
|
||||
|
||||
private JComponent buildOutputHeader() {
|
||||
JPanel header = new JPanel(new BorderLayout(8, 0));
|
||||
header.setBorder(BorderFactory.createEmptyBorder(6, 8, 6, 8));
|
||||
|
||||
JPanel left = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 0));
|
||||
left.add(progressToggle);
|
||||
header.add(left, BorderLayout.WEST);
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
private JComponent buildOutputBody() {
|
||||
JScrollPane progressScroll = new JScrollPane(progressArea);
|
||||
progressScroll.setBorder(BorderFactory.createTitledBorder("Progress"));
|
||||
progressScroll.getVerticalScrollBar().setUnitIncrement(16);
|
||||
|
||||
JPanel empty = new JPanel();
|
||||
progressContainer.add(progressScroll, "show");
|
||||
progressContainer.add(empty, "hide");
|
||||
((CardLayout) progressContainer.getLayout()).show(progressContainer, "show");
|
||||
|
||||
JPanel finalPanel = new JPanel(new BorderLayout());
|
||||
finalPanel.add(outputCards, BorderLayout.CENTER);
|
||||
finalPanel.setBorder(BorderFactory.createTitledBorder("Final Response"));
|
||||
|
||||
JSplitPane split = new JSplitPane(JSplitPane.VERTICAL_SPLIT, progressContainer, finalPanel);
|
||||
split.setResizeWeight(0.15);
|
||||
split.setBorder(null);
|
||||
split.setDividerSize(6);
|
||||
|
||||
final int[] lastDividerLocation = new int[]{140}; // sensible default
|
||||
|
||||
progressToggle.addActionListener(e -> {
|
||||
boolean show = progressToggle.isSelected();
|
||||
progressToggle.setText(show ? "Progress ▾" : "Progress ▸");
|
||||
CardLayout cl = (CardLayout) progressContainer.getLayout();
|
||||
cl.show(progressContainer, show ? "show" : "hide");
|
||||
if (!show) {
|
||||
int current = split.getDividerLocation();
|
||||
if (current > 0) {
|
||||
lastDividerLocation[0] = current;
|
||||
}
|
||||
split.setDividerLocation(0);
|
||||
split.setDividerSize(0);
|
||||
} else {
|
||||
split.setDividerSize(6);
|
||||
// Restore previous divider location (or fallback to 20% of height)
|
||||
int restore = lastDividerLocation[0];
|
||||
if (restore <= 0) {
|
||||
int h = split.getHeight();
|
||||
restore = (h > 0) ? Math.max(80, (int) (h * 0.2)) : 140;
|
||||
}
|
||||
split.setDividerLocation(restore);
|
||||
}
|
||||
split.revalidate();
|
||||
split.repaint();
|
||||
});
|
||||
|
||||
return split;
|
||||
}
|
||||
|
||||
private static void configureTextArea(JTextArea area, boolean monospaced) {
|
||||
area.setEditable(false);
|
||||
area.setLineWrap(false);
|
||||
area.setWrapStyleWord(false);
|
||||
if (monospaced) {
|
||||
area.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
|
||||
} else {
|
||||
area.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
|
||||
}
|
||||
}
|
||||
|
||||
private static Color colorForStatus(String status) {
|
||||
if (status == null) return new Color(120, 120, 120);
|
||||
switch (status) {
|
||||
case "running":
|
||||
return new Color(33, 150, 243);
|
||||
case "done":
|
||||
return new Color(76, 175, 80);
|
||||
case "error":
|
||||
return new Color(244, 67, 54);
|
||||
case "cancelled":
|
||||
case "cancelling":
|
||||
return new Color(255, 152, 0);
|
||||
default:
|
||||
return new Color(120, 120, 120);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class DotIcon implements Icon {
|
||||
private final int size;
|
||||
private Color color;
|
||||
|
||||
DotIcon(int size, Color color) {
|
||||
this.size = size;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
void setColor(Color color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIconWidth() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIconHeight() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintIcon(Component c, Graphics g, int x, int y) {
|
||||
Graphics2D g2 = (Graphics2D) g.create();
|
||||
try {
|
||||
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
g2.setColor(color != null ? color : Color.GRAY);
|
||||
g2.fillOval(x, y, size, size);
|
||||
} finally {
|
||||
g2.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TestRunCellRenderer implements ListCellRenderer<TestRun> {
|
||||
private final JPanel panel = new JPanel(new BorderLayout(8, 0));
|
||||
private final JLabel dotLabel = new JLabel();
|
||||
private final JLabel titleLabel = new JLabel();
|
||||
private final JLabel metaLabel = new JLabel();
|
||||
private final JPanel textPanel = new JPanel();
|
||||
private final DotIcon dotIcon = new DotIcon(10, new Color(120, 120, 120));
|
||||
|
||||
TestRunCellRenderer() {
|
||||
panel.setBorder(BorderFactory.createEmptyBorder(6, 8, 6, 8));
|
||||
dotLabel.setIcon(dotIcon);
|
||||
|
||||
textPanel.setLayout(new BoxLayout(textPanel, BoxLayout.Y_AXIS));
|
||||
titleLabel.setFont(titleLabel.getFont().deriveFont(Font.BOLD));
|
||||
metaLabel.setFont(metaLabel.getFont().deriveFont(Font.PLAIN, 11f));
|
||||
metaLabel.setForeground(new Color(102, 102, 102));
|
||||
textPanel.add(titleLabel);
|
||||
textPanel.add(metaLabel);
|
||||
|
||||
panel.add(dotLabel, BorderLayout.WEST);
|
||||
panel.add(textPanel, BorderLayout.CENTER);
|
||||
panel.setOpaque(true);
|
||||
textPanel.setOpaque(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList<? extends TestRun> list, TestRun value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||
String titleText = value != null ? value.title : "";
|
||||
String modeText = value != null ? value.agentMode : "";
|
||||
String statusText = value != null ? value.status : "";
|
||||
|
||||
String shownTitle = titleText;
|
||||
if (shownTitle.length() > 80) {
|
||||
shownTitle = shownTitle.substring(0, 77) + "...";
|
||||
}
|
||||
titleLabel.setText(shownTitle);
|
||||
metaLabel.setText(modeText + " · " + statusText);
|
||||
|
||||
dotIcon.setColor(colorForStatus(statusText));
|
||||
|
||||
if (isSelected) {
|
||||
panel.setBackground(list.getSelectionBackground());
|
||||
titleLabel.setForeground(list.getSelectionForeground());
|
||||
metaLabel.setForeground(list.getSelectionForeground());
|
||||
} else {
|
||||
panel.setBackground(list.getBackground());
|
||||
titleLabel.setForeground(list.getForeground());
|
||||
metaLabel.setForeground(new Color(102, 102, 102));
|
||||
}
|
||||
|
||||
return panel;
|
||||
}
|
||||
}
|
||||
|
||||
// right panel builds scroll panes for each tab
|
||||
|
||||
private void wireActions() {
|
||||
validateButton.addActionListener(e -> {
|
||||
validateButton.setEnabled(false);
|
||||
statusLabel.setText("Validating...");
|
||||
log("Validating connection...");
|
||||
new Thread(() -> {
|
||||
try {
|
||||
CyberStrikeAIClient.Config cfg = currentConfig();
|
||||
String token = client.loginAndValidate(cfg);
|
||||
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());
|
||||
} finally {
|
||||
SwingUtilities.invokeLater(() -> validateButton.setEnabled(true));
|
||||
}
|
||||
}, "CyberStrikeAI-Validate").start();
|
||||
});
|
||||
|
||||
clearButton.addActionListener(e -> {
|
||||
if (selectedRunId == null) {
|
||||
progressArea.setText("");
|
||||
finalRawArea.setText("");
|
||||
markdownPane.setText("");
|
||||
return;
|
||||
}
|
||||
TestRun run = runs.get(selectedRunId);
|
||||
if (run == null) return;
|
||||
synchronized (run) {
|
||||
run.buffer.setLength(0);
|
||||
run.progressBuffer.setLength(0);
|
||||
run.finalBuffer.setLength(0);
|
||||
}
|
||||
progressArea.setText("");
|
||||
finalRawArea.setText("");
|
||||
markdownPane.setText("");
|
||||
});
|
||||
|
||||
copyButton.addActionListener(e -> {
|
||||
String text;
|
||||
int idx = rightTabs.getSelectedIndex();
|
||||
String tabName = idx >= 0 ? rightTabs.getTitleAt(idx) : "";
|
||||
if ("Request".equals(tabName)) {
|
||||
text = requestArea.getText();
|
||||
} else if ("Response".equals(tabName)) {
|
||||
text = responseArea.getText();
|
||||
} else {
|
||||
text = finalRawArea.getText();
|
||||
}
|
||||
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(text == null ? "" : text), null);
|
||||
});
|
||||
|
||||
stopButton.addActionListener(e -> {
|
||||
String runId = selectedRunId;
|
||||
if (runId == null) return;
|
||||
TestRun run = runs.get(runId);
|
||||
if (run == null) return;
|
||||
String token = getToken();
|
||||
if (token == null || token.trim().isEmpty()) {
|
||||
appendToRun(runId, "\n[error] Not validated.\n");
|
||||
return;
|
||||
}
|
||||
String convId;
|
||||
synchronized (run) {
|
||||
convId = run.conversationId;
|
||||
}
|
||||
if (convId == null || convId.trim().isEmpty()) {
|
||||
appendToRun(runId, "\n[info] conversationId not available yet (wait for server to create session).\n");
|
||||
return;
|
||||
}
|
||||
|
||||
stopButton.setEnabled(false);
|
||||
new Thread(() -> {
|
||||
try {
|
||||
CyberStrikeAIClient.Config cfg = currentConfig();
|
||||
client.cancelByConversationId(cfg.baseUrl, token, convId);
|
||||
appendToRun(runId, "\n[info] Cancel requested.\n");
|
||||
setRunStatus(runId, "cancelling");
|
||||
} catch (Exception ex) {
|
||||
appendToRun(runId, "\n[error] Cancel failed: " + ex.getMessage() + "\n");
|
||||
} finally {
|
||||
SwingUtilities.invokeLater(() -> stopButton.setEnabled(true));
|
||||
}
|
||||
}, "CyberStrikeAI-Cancel").start();
|
||||
});
|
||||
|
||||
searchField.getDocument().addDocumentListener(new javax.swing.event.DocumentListener() {
|
||||
@Override public void insertUpdate(javax.swing.event.DocumentEvent e) { applyFilter(); }
|
||||
@Override public void removeUpdate(javax.swing.event.DocumentEvent e) { applyFilter(); }
|
||||
@Override public void changedUpdate(javax.swing.event.DocumentEvent e) { applyFilter(); }
|
||||
});
|
||||
|
||||
renderMarkdownBox.addActionListener(e -> refreshOutputView());
|
||||
}
|
||||
|
||||
CyberStrikeAIClient.Config currentConfig() {
|
||||
String host = hostField.getText().trim();
|
||||
String port = portField.getText().trim();
|
||||
String password = new String(passwordField.getPassword());
|
||||
String baseUrl = "http://" + host + ":" + port;
|
||||
CyberStrikeAIClient.AgentMode mode = agentModeBox.getSelectedIndex() == 1
|
||||
? CyberStrikeAIClient.AgentMode.MULTI
|
||||
: CyberStrikeAIClient.AgentMode.SINGLE;
|
||||
return new CyberStrikeAIClient.Config(baseUrl, password, mode);
|
||||
}
|
||||
|
||||
String getToken() {
|
||||
return tokenRef.get();
|
||||
}
|
||||
|
||||
boolean isShowDebugEvents() {
|
||||
return showDebugEventsBox.isSelected();
|
||||
}
|
||||
|
||||
private String nextRunId() {
|
||||
return "run_" + runSeq.getAndIncrement();
|
||||
}
|
||||
|
||||
private String formatRunDisplay(String title, String agentMode, String status) {
|
||||
return title + " [" + agentMode + "] - " + status;
|
||||
}
|
||||
|
||||
String startNewRun(String title, String agentMode, IHttpRequestResponse msg) {
|
||||
String id = nextRunId();
|
||||
TestRun run = new TestRun(id, title, agentMode);
|
||||
if (msg != null) {
|
||||
run.requestRaw = bytesToString(msg.getRequest());
|
||||
run.responseRaw = bytesToString(msg.getResponse());
|
||||
}
|
||||
runs.put(id, run);
|
||||
|
||||
int index = testListModel.getSize();
|
||||
runIdToIndex.put(id, index);
|
||||
testListModel.addElement(run);
|
||||
filteredListModel.addElement(run);
|
||||
|
||||
selectedRunId = id;
|
||||
filteredList.setSelectedIndex(filteredListModel.getSize() - 1);
|
||||
progressArea.setText("");
|
||||
finalRawArea.setText("");
|
||||
markdownPane.setText("");
|
||||
requestArea.setText(run.requestRaw);
|
||||
responseArea.setText(run.responseRaw);
|
||||
refreshOutputView();
|
||||
return id;
|
||||
}
|
||||
|
||||
void setRunStatus(String runId, String status) {
|
||||
TestRun run = runs.get(runId);
|
||||
if (run == null) return;
|
||||
synchronized (run) {
|
||||
run.status = status;
|
||||
}
|
||||
Integer index = runIdToIndex.get(runId);
|
||||
if (index != null) {
|
||||
SwingUtilities.invokeLater(() -> filteredList.repaint());
|
||||
}
|
||||
}
|
||||
|
||||
void setRunConversationId(String runId, String conversationId) {
|
||||
if (runId == null) return;
|
||||
TestRun run = runs.get(runId);
|
||||
if (run == null) return;
|
||||
synchronized (run) {
|
||||
run.conversationId = conversationId == null ? "" : conversationId;
|
||||
}
|
||||
}
|
||||
|
||||
void appendToRun(String runId, String s) {
|
||||
// Backward compatibility: default to progress bucket
|
||||
appendProgressToRun(runId, s);
|
||||
}
|
||||
|
||||
void appendProgressToRun(String runId, String s) {
|
||||
if (runId == null || s == null) return;
|
||||
TestRun run = runs.get(runId);
|
||||
if (run == null) return;
|
||||
synchronized (run) {
|
||||
run.buffer.append(s);
|
||||
run.progressBuffer.append(s);
|
||||
}
|
||||
if (runId.equals(selectedRunId)) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
progressArea.append(s);
|
||||
progressArea.setCaretPosition(progressArea.getDocument().getLength());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void appendFinalToRun(String runId, String s) {
|
||||
if (runId == null || s == null) return;
|
||||
TestRun run = runs.get(runId);
|
||||
if (run == null) return;
|
||||
synchronized (run) {
|
||||
run.buffer.append(s);
|
||||
run.finalBuffer.append(s);
|
||||
}
|
||||
if (runId.equals(selectedRunId)) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
finalRawArea.append(s);
|
||||
finalRawArea.setCaretPosition(finalRawArea.getDocument().getLength());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void setFinalResponse(String runId, String finalResponse) {
|
||||
if (runId == null) return;
|
||||
TestRun run = runs.get(runId);
|
||||
if (run == null) return;
|
||||
synchronized (run) {
|
||||
run.finalResponse = finalResponse == null ? "" : finalResponse;
|
||||
}
|
||||
if (runId.equals(selectedRunId)) {
|
||||
SwingUtilities.invokeLater(this::refreshOutputView);
|
||||
}
|
||||
}
|
||||
|
||||
private String getSelectedRunIdFromList() {
|
||||
TestRun run = filteredList.getSelectedValue();
|
||||
return run == null ? null : run.id;
|
||||
}
|
||||
|
||||
private void setLogAreaToRun(String runId) {
|
||||
TestRun run = runs.get(runId);
|
||||
if (run == null) return;
|
||||
selectedRunId = runId;
|
||||
String progress;
|
||||
String fin;
|
||||
synchronized (run) {
|
||||
progress = run.progressBuffer.toString();
|
||||
fin = run.finalBuffer.toString();
|
||||
}
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
progressArea.setText(progress);
|
||||
progressArea.setCaretPosition(progressArea.getDocument().getLength());
|
||||
finalRawArea.setText(fin);
|
||||
finalRawArea.setCaretPosition(finalRawArea.getDocument().getLength());
|
||||
requestArea.setText(run.requestRaw == null ? "" : run.requestRaw);
|
||||
responseArea.setText(run.responseRaw == null ? "" : run.responseRaw);
|
||||
refreshOutputView();
|
||||
});
|
||||
}
|
||||
|
||||
private void clearAllRuns() {
|
||||
runs.clear();
|
||||
runIdToIndex.clear();
|
||||
testListModel.clear();
|
||||
filteredListModel.clear();
|
||||
selectedRunId = null;
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
progressArea.setText("");
|
||||
finalRawArea.setText("");
|
||||
markdownPane.setText("");
|
||||
requestArea.setText("");
|
||||
responseArea.setText("");
|
||||
});
|
||||
}
|
||||
|
||||
void clearAndShowStreamHeader(String title) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
progressArea.setText("");
|
||||
finalRawArea.setText(title + "\n\n");
|
||||
});
|
||||
}
|
||||
|
||||
// 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());
|
||||
});
|
||||
}
|
||||
|
||||
private void log(String s) {
|
||||
appendStreamLine("[*] " + s);
|
||||
}
|
||||
|
||||
private void applyFilter() {
|
||||
String q = searchField.getText();
|
||||
if (q == null) q = "";
|
||||
String query = q.trim().toLowerCase();
|
||||
filteredListModel.clear();
|
||||
for (int i = 0; i < testListModel.size(); i++) {
|
||||
TestRun r = testListModel.getElementAt(i);
|
||||
if (query.isEmpty() || (r.title != null && r.title.toLowerCase().contains(query))) {
|
||||
filteredListModel.addElement(r);
|
||||
}
|
||||
}
|
||||
if (filteredListModel.size() > 0 && filteredList.getSelectedIndex() < 0) {
|
||||
filteredList.setSelectedIndex(0);
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshOutputView() {
|
||||
if (!renderMarkdownBox.isSelected()) {
|
||||
outputCardsLayout.show(outputCards, "raw");
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedRunId == null) {
|
||||
outputCardsLayout.show(outputCards, "raw");
|
||||
return;
|
||||
}
|
||||
|
||||
TestRun run = runs.get(selectedRunId);
|
||||
if (run == null) {
|
||||
outputCardsLayout.show(outputCards, "raw");
|
||||
return;
|
||||
}
|
||||
|
||||
String finalResp;
|
||||
synchronized (run) {
|
||||
finalResp = run.finalResponse;
|
||||
}
|
||||
if (finalResp == null || finalResp.trim().isEmpty()) {
|
||||
// while streaming, stick to raw for performance
|
||||
outputCardsLayout.show(outputCards, "raw");
|
||||
return;
|
||||
}
|
||||
|
||||
String html = MarkdownRenderer.toHtml(finalResp);
|
||||
markdownPane.setText(html);
|
||||
markdownPane.setCaretPosition(0);
|
||||
outputCardsLayout.show(outputCards, "md");
|
||||
}
|
||||
private static String bytesToString(byte[] bytes) {
|
||||
if (bytes == null || bytes.length == 0) return "";
|
||||
return new String(bytes, StandardCharsets.ISO_8859_1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTabCaption() {
|
||||
return "CyberStrikeAI";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getUiComponent() {
|
||||
return root;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package burp;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
final class HttpMessageFormatter {
|
||||
private HttpMessageFormatter() {}
|
||||
|
||||
static String getRequestTitle(IExtensionHelpers helpers, IHttpRequestResponse msg) {
|
||||
IRequestInfo reqInfo = helpers.analyzeRequest(msg);
|
||||
String method = reqInfo.getMethod();
|
||||
if (reqInfo.getUrl() == null) {
|
||||
return method + " (unknown)";
|
||||
}
|
||||
String host = reqInfo.getUrl().getHost();
|
||||
String path = reqInfo.getUrl().getPath();
|
||||
if (path == null || path.isEmpty()) path = "/";
|
||||
String query = reqInfo.getUrl().getQuery();
|
||||
String shortPath = path;
|
||||
if (shortPath.length() > 80) shortPath = shortPath.substring(0, 77) + "...";
|
||||
String q = (query != null && !query.isEmpty()) ? "?" : "";
|
||||
return method + " " + host + shortPath + q;
|
||||
}
|
||||
|
||||
static String toPrompt(IExtensionHelpers helpers, IHttpRequestResponse msg) {
|
||||
IRequestInfo reqInfo = helpers.analyzeRequest(msg);
|
||||
String method = reqInfo.getMethod();
|
||||
String url = reqInfo.getUrl() != null ? reqInfo.getUrl().toString() : "(unknown)";
|
||||
|
||||
byte[] reqBytes = msg.getRequest();
|
||||
int bodyOffset = reqInfo.getBodyOffset();
|
||||
String headers = String.join("\n", reqInfo.getHeaders());
|
||||
String body = "";
|
||||
if (reqBytes != null && reqBytes.length > bodyOffset) {
|
||||
body = new String(reqBytes, bodyOffset, reqBytes.length - bodyOffset, StandardCharsets.ISO_8859_1);
|
||||
}
|
||||
|
||||
// Include response summary if available
|
||||
String respSnippet = "";
|
||||
byte[] respBytes = msg.getResponse();
|
||||
if (respBytes != null && respBytes.length > 0) {
|
||||
IResponseInfo respInfo = helpers.analyzeResponse(respBytes);
|
||||
List<String> respHeaders = respInfo.getHeaders();
|
||||
int respBodyOffset = respInfo.getBodyOffset();
|
||||
String respBody = "";
|
||||
if (respBytes.length > respBodyOffset) {
|
||||
int max = Math.min(respBytes.length - respBodyOffset, 4096);
|
||||
respBody = new String(respBytes, respBodyOffset, max, StandardCharsets.ISO_8859_1);
|
||||
}
|
||||
respSnippet = "\n\n[Optional: Response (truncated)]\n"
|
||||
+ String.join("\n", respHeaders)
|
||||
+ "\n\n"
|
||||
+ respBody;
|
||||
}
|
||||
|
||||
return ""
|
||||
+ "针对该流量做web渗透测试,并输出测试结果,要求:只针对该接口流量做测试,切勿拓展其他接口\n\n"
|
||||
+ "[Target]\n"
|
||||
+ method + " " + url + "\n\n"
|
||||
+ "[Request]\n"
|
||||
+ headers + "\n\n"
|
||||
+ body
|
||||
+ respSnippet;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
package burp;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Minimal Markdown -> HTML renderer for Burp UI.
|
||||
* Supports: headings (#..######), fenced code blocks (```), inline code (`),
|
||||
* bold (**), lists (-/*), paragraphs, and basic escaping.
|
||||
*
|
||||
* Not a full CommonMark implementation; kept dependency-free on purpose.
|
||||
*/
|
||||
final class MarkdownRenderer {
|
||||
private MarkdownRenderer() {}
|
||||
|
||||
static String toHtml(String markdown) {
|
||||
if (markdown == null) markdown = "";
|
||||
|
||||
List<String> lines = splitLines(markdown);
|
||||
StringBuilder out = new StringBuilder(4096);
|
||||
out.append("<html><head><meta charset='utf-8'>")
|
||||
.append("<style>")
|
||||
.append("body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Arial,sans-serif;font-size:12px;line-height:1.4;margin:10px;}")
|
||||
.append("code,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;}")
|
||||
.append("pre{background:#f6f8fa;border:1px solid #e5e7eb;border-radius:6px;padding:10px;overflow:auto;}")
|
||||
.append("h1,h2,h3{margin:0.8em 0 0.4em 0;}")
|
||||
.append("ul{margin:0.4em 0 0.6em 1.2em;padding:0;}")
|
||||
.append("</style></head><body>");
|
||||
|
||||
boolean inCode = false;
|
||||
boolean inList = false;
|
||||
StringBuilder codeBuf = new StringBuilder();
|
||||
|
||||
for (String raw : lines) {
|
||||
String line = raw == null ? "" : raw;
|
||||
|
||||
if (line.trim().startsWith("```")) {
|
||||
if (!inCode) {
|
||||
inCode = true;
|
||||
codeBuf.setLength(0);
|
||||
} else {
|
||||
// close code
|
||||
out.append("<pre><code>")
|
||||
.append(escapeHtml(codeBuf.toString()))
|
||||
.append("</code></pre>");
|
||||
inCode = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inCode) {
|
||||
codeBuf.append(line).append("\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
String trimmed = line.trim();
|
||||
if (trimmed.isEmpty()) {
|
||||
if (inList) {
|
||||
out.append("</ul>");
|
||||
inList = false;
|
||||
}
|
||||
out.append("<div style='height:6px'></div>");
|
||||
continue;
|
||||
}
|
||||
|
||||
// headings
|
||||
int h = headingLevel(trimmed);
|
||||
if (h > 0) {
|
||||
if (inList) {
|
||||
out.append("</ul>");
|
||||
inList = false;
|
||||
}
|
||||
String text = trimmed.substring(h).trim();
|
||||
out.append("<h").append(h).append(">")
|
||||
.append(inlineFormat(text))
|
||||
.append("</h").append(h).append(">");
|
||||
continue;
|
||||
}
|
||||
|
||||
// list items
|
||||
if (trimmed.startsWith("- ") || trimmed.startsWith("* ")) {
|
||||
if (!inList) {
|
||||
out.append("<ul>");
|
||||
inList = true;
|
||||
}
|
||||
String item = trimmed.substring(2).trim();
|
||||
out.append("<li>").append(inlineFormat(item)).append("</li>");
|
||||
continue;
|
||||
}
|
||||
|
||||
// normal paragraph
|
||||
if (inList) {
|
||||
out.append("</ul>");
|
||||
inList = false;
|
||||
}
|
||||
out.append("<p>").append(inlineFormat(trimmed)).append("</p>");
|
||||
}
|
||||
|
||||
if (inCode) {
|
||||
out.append("<pre><code>")
|
||||
.append(escapeHtml(codeBuf.toString()))
|
||||
.append("</code></pre>");
|
||||
}
|
||||
if (inList) {
|
||||
out.append("</ul>");
|
||||
}
|
||||
|
||||
out.append("</body></html>");
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
private static int headingLevel(String s) {
|
||||
int i = 0;
|
||||
while (i < s.length() && s.charAt(i) == '#') i++;
|
||||
if (i >= 1 && i <= 6 && i < s.length() && Character.isWhitespace(s.charAt(i))) return i;
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static String inlineFormat(String text) {
|
||||
// escape first, then apply simple replacements using placeholders
|
||||
String escaped = escapeHtml(text);
|
||||
|
||||
// inline code: `code`
|
||||
escaped = replaceInlineCode(escaped);
|
||||
|
||||
// bold: **text**
|
||||
escaped = replaceBold(escaped);
|
||||
|
||||
return escaped;
|
||||
}
|
||||
|
||||
private static String replaceInlineCode(String s) {
|
||||
StringBuilder out = new StringBuilder(s.length() + 16);
|
||||
boolean in = false;
|
||||
StringBuilder buf = new StringBuilder();
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
if (c == '`') {
|
||||
if (!in) {
|
||||
in = true;
|
||||
buf.setLength(0);
|
||||
} else {
|
||||
out.append("<code>").append(buf).append("</code>");
|
||||
in = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (in) buf.append(c);
|
||||
else out.append(c);
|
||||
}
|
||||
if (in) {
|
||||
// unmatched backtick: keep as literal
|
||||
out.append("`").append(buf);
|
||||
}
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
private static String replaceBold(String s) {
|
||||
// simple non-nested **...**
|
||||
StringBuilder out = new StringBuilder(s.length() + 16);
|
||||
int i = 0;
|
||||
while (i < s.length()) {
|
||||
int start = s.indexOf("**", i);
|
||||
if (start < 0) {
|
||||
out.append(s.substring(i));
|
||||
break;
|
||||
}
|
||||
int end = s.indexOf("**", start + 2);
|
||||
if (end < 0) {
|
||||
out.append(s.substring(i));
|
||||
break;
|
||||
}
|
||||
out.append(s.substring(i, start));
|
||||
out.append("<b>").append(s, start + 2, end).append("</b>");
|
||||
i = end + 2;
|
||||
}
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
private static String escapeHtml(String s) {
|
||||
if (s == null) return "";
|
||||
return s.replace("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace("\"", """);
|
||||
}
|
||||
|
||||
private static List<String> splitLines(String s) {
|
||||
String[] parts = s.split("\\r?\\n", -1);
|
||||
List<String> lines = new ArrayList<>(parts.length);
|
||||
for (String p : parts) lines.add(p);
|
||||
return lines;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package burp;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Minimal JSON extractor for the SSE payloads we emit:
|
||||
* {"type":"...","message":"...","data":...}
|
||||
*
|
||||
* This is NOT a general-purpose JSON parser; it's intentionally small to avoid external deps.
|
||||
*/
|
||||
final class SimpleJson {
|
||||
private SimpleJson() {}
|
||||
|
||||
static Map<String, String> extractTopLevelStringFields(String json, String... keys) {
|
||||
Map<String, String> out = new HashMap<>();
|
||||
if (json == null) return out;
|
||||
for (String key : keys) {
|
||||
out.put(key, extractStringField(json, key));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static String extractStringField(String json, String key) {
|
||||
if (json == null || key == null) return "";
|
||||
String needle = "\"" + key + "\"";
|
||||
int k = json.indexOf(needle);
|
||||
if (k < 0) return "";
|
||||
int colon = json.indexOf(':', k + needle.length());
|
||||
if (colon < 0) return "";
|
||||
int i = colon + 1;
|
||||
while (i < json.length() && Character.isWhitespace(json.charAt(i))) i++;
|
||||
if (i >= json.length() || json.charAt(i) != '"') return "";
|
||||
i++; // after opening quote
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean esc = false;
|
||||
while (i < json.length()) {
|
||||
char c = json.charAt(i++);
|
||||
if (esc) {
|
||||
switch (c) {
|
||||
case '"': sb.append('"'); break;
|
||||
case '\\': sb.append('\\'); break;
|
||||
case '/': sb.append('/'); break;
|
||||
case 'b': sb.append('\b'); break;
|
||||
case 'f': sb.append('\f'); break;
|
||||
case 'n': sb.append('\n'); break;
|
||||
case 'r': sb.append('\r'); break;
|
||||
case 't': sb.append('\t'); break;
|
||||
case 'u':
|
||||
if (i + 3 < json.length()) {
|
||||
String hex = json.substring(i, i + 4);
|
||||
try {
|
||||
sb.append((char) Integer.parseInt(hex, 16));
|
||||
i += 4;
|
||||
} catch (NumberFormatException ignored) {
|
||||
// best-effort: keep raw
|
||||
sb.append("\\u").append(hex);
|
||||
i += 4;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
sb.append(c);
|
||||
}
|
||||
esc = false;
|
||||
continue;
|
||||
}
|
||||
if (c == '\\') {
|
||||
esc = true;
|
||||
continue;
|
||||
}
|
||||
if (c == '"') {
|
||||
break;
|
||||
}
|
||||
sb.append(c);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,3 @@
|
||||
artifactId=cyberstrikeai-burp-extension
|
||||
groupId=ai.cyberstrike
|
||||
version=1.0.0
|
||||
@@ -0,0 +1,14 @@
|
||||
burp/CyberStrikeAIClient$StreamListener.class
|
||||
burp/CyberStrikeAIClient$Config.class
|
||||
burp/CyberStrikeAIClient$AgentMode.class
|
||||
burp/MarkdownRenderer.class
|
||||
burp/SimpleJson.class
|
||||
burp/CyberStrikeAIClient.class
|
||||
burp/CyberStrikeAITab$DotIcon.class
|
||||
burp/CyberStrikeAITab.class
|
||||
burp/CyberStrikeAITab$1.class
|
||||
burp/BurpExtender$1.class
|
||||
burp/BurpExtender.class
|
||||
burp/CyberStrikeAITab$TestRun.class
|
||||
burp/CyberStrikeAITab$TestRunCellRenderer.class
|
||||
burp/HttpMessageFormatter.class
|
||||
@@ -0,0 +1,6 @@
|
||||
/Users/temp/Downloads/CyberStrikeAI-main/plugins/burp-suite/cyberstrikeai-burp-extension/src/main/java/burp/BurpExtender.java
|
||||
/Users/temp/Downloads/CyberStrikeAI-main/plugins/burp-suite/cyberstrikeai-burp-extension/src/main/java/burp/CyberStrikeAIClient.java
|
||||
/Users/temp/Downloads/CyberStrikeAI-main/plugins/burp-suite/cyberstrikeai-burp-extension/src/main/java/burp/CyberStrikeAITab.java
|
||||
/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
|
||||
Reference in New Issue
Block a user