Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 38 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,19 +70,46 @@ pensar pentest --target https://example.com --cwd ./my-app

# Targeted pentest with specific objectives
pensar targeted-pentest --target https://example.com --objective "Test authentication bypass"

# Pair with Burp Suite Proxy and MCP
pensar burp config set --enabled true --url http://127.0.0.1:9876/sse
pensar burp status
pensar pentest --target https://example.com --burp
```

| Flag | Command | Description |
| --------------------------------- | ------------------------- | -------------------------------------------------------- |
| `--target <url>` | pentest, targeted-pentest | Target URL (required) |
| `--cwd <path>` | pentest | Source code path for whitebox mode |
| `--mode <mode>` | pentest | `exfil` for pivoting and flag extraction |
| `--model <model>` | pentest, targeted-pentest | AI model (default: auto-selected) |
| `--extended-thinking` | pentest | Enable extended thinking for supported models |
| `--task-driven` | pentest | Enable task-driven architecture (experimental) |
| `--prompt <text\|@file>` | pentest | Custom guidance for the agent |
| `--threat-model <text\|@file>` | pentest | Threat model to guide testing |
| `--objective <text>` | targeted-pentest | Testing objective (repeatable) |
| `--burp` | pentest, targeted-pentest | Route traffic through Burp and enable Burp MCP |
| `--burp-proxy <url>` | pentest, targeted-pentest | Burp Proxy URL (default `http://127.0.0.1:8080`) |
| `--burp-mcp-url <url>` | pentest, targeted-pentest | Burp MCP SSE URL (default `http://127.0.0.1:9876/sse`) |
| `--burp-mcp-proxy-jar <path>` | pentest, targeted-pentest | Path to PortSwigger's MCP stdio proxy JAR |
| `--burp-mcp-proxy-command <path>` | pentest, targeted-pentest | Java executable for the MCP stdio proxy (default `java`) |
| `--burp-insecure-tls` | pentest, targeted-pentest | Ignore TLS errors when proxying through Burp |

### Burp Suite MCP

Apex can connect to PortSwigger's Burp Suite MCP Server over its local SSE endpoint. Install the MCP Server extension in Burp, enable the MCP tab/server, then configure Apex:

```bash
pensar burp config set --enabled true --url http://127.0.0.1:9876/sse
pensar burp status
pensar burp tools
pensar burp proxy-history --target https://example.com --limit 20
pensar burp repeater --request-file request.txt
```

| Flag | Command | Description |
| ------------------------------ | ------------------------- | ---------------------------------------------- |
| `--target <url>` | pentest, targeted-pentest | Target URL (required) |
| `--cwd <path>` | pentest | Source code path for whitebox mode |
| `--mode <mode>` | pentest | `exfil` for pivoting and flag extraction |
| `--model <model>` | pentest, targeted-pentest | AI model (default: auto-selected) |
| `--extended-thinking` | pentest | Enable extended thinking for supported models |
| `--task-driven` | pentest | Enable task-driven architecture (experimental) |
| `--prompt <text\|@file>` | pentest | Custom guidance for the agent |
| `--threat-model <text\|@file>` | pentest | Threat model to guide testing |
| `--objective <text>` | targeted-pentest | Testing objective (repeatable) |
In `/operator`, use `/burp status`, `/burp tools`, `/burp history`, and `/burp repeater`. Burp tools are only available to agents when Burp is enabled for the session and actions remain subject to Apex scope and Burp's own target approval model.

Security notes: proxy history and raw requests may contain cookies, credentials, request bodies, and manually browsed traffic. Apex defaults to localhost Burp MCP endpoints, warns on non-local URLs, redacts sensitive headers in Burp action logs, and blocks config-modifying MCP tools unless explicitly enabled. Data returned from Burp tools can be processed by your configured AI provider.

### W&B Weave Tracing

Expand Down
2 changes: 2 additions & 0 deletions fern/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ navigation:
path: ./docs/cli/fixes.mdx
- page: pensar logs
path: ./docs/cli/logs.mdx
- page: pensar burp
path: ./docs/cli/burp.mdx
- page: pensar upgrade
path: ./docs/cli/upgrade.mdx
- page: pensar doctor
Expand Down
61 changes: 61 additions & 0 deletions fern/docs/cli/burp.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
title: pensar burp
---

Manage Apex's Burp Suite MCP integration.

## Setup

1. Install PortSwigger's MCP Server extension in Burp Suite.
2. Open Burp's MCP tab and enable the MCP server.
3. Keep the default local listener unless you have a reason to expose it elsewhere:

```bash
http://127.0.0.1:9876/sse
```

Some Burp MCP clients use `http://127.0.0.1:9876` without `/sse`; Apex accepts either URL when configured.

## Configure Apex

```bash
pensar burp config set --enabled true --url http://127.0.0.1:9876/sse
pensar burp status
pensar burp tools
```

Configuration is stored in `~/.pensar/config.json` under `burpMcp`. Config-modifying Burp MCP tools are disabled unless you explicitly set `--allow-config-mutation`.

## Commands

```bash
pensar burp status
pensar burp tools
pensar burp config
pensar burp config set --url http://127.0.0.1:9876/sse --enabled true
pensar burp proxy-history --target https://example.com --limit 20
pensar burp repeater --request-file request.txt
pensar burp send --request-file request.txt
```

`proxy-history`, `repeater`, and `send` enforce configured `allowedTargets` when present and fail closed when the integration is disabled or Burp MCP is unreachable.

## TUI

In `/operator`, use:

```text
/burp status
/burp tools
/burp history
/burp repeater
```

Burp agent tools are only visible when the session has Burp enabled.

## Security Notes

- Apex does not bypass Burp MCP target approval. If Burp rejects an action, Apex surfaces the failure.
- Default endpoints are localhost-only. Non-local MCP URLs produce a warning and should only be used for trusted endpoints.
- Proxy history may include credentials, cookies, request bodies, and traffic from manual browsing. Apex redacts sensitive headers in its Burp action logs, but agent tool inputs and outputs may still be processed by the configured AI provider.
- Do not enable config mutation unless you understand the impact. Apex blocks config-modifying MCP tools by default.
105 changes: 104 additions & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
resolveThreatModelPrompt,
combinePromptParts,
} from "./tui/utils/command-flags";
import type { SessionConfig } from "./core/session";

const args = process.argv.slice(2);
const command = args[0];
Expand Down Expand Up @@ -67,6 +68,79 @@ function getAllArgs(flag: string, argv = args): string[] {
return values;
}

function buildBurpSuiteConfig(argv = args): SessionConfig["burpSuite"] {
const enabled =
hasFlag("--burp", argv) ||
getArg("--burp-proxy", argv) !== undefined ||
getArg("--burp-transport", argv) !== undefined ||
getArg("--burp-mcp-url", argv) !== undefined ||
getArg("--burp-mcp-proxy-jar", argv) !== undefined ||
getArg("--burp-mcp-proxy-command", argv) !== undefined ||
getArg("--burp-timeout-ms", argv) !== undefined ||
hasFlag("--burp-allow-config-mutation", argv) ||
hasFlag("--burp-insecure-tls", argv);

if (!enabled) return undefined;

const mcpSseUrl = getArg("--burp-mcp-url", argv);
const mcpProxyJar = getArg("--burp-mcp-proxy-jar", argv);
const transport = getArg("--burp-transport", argv);
const timeoutMsRaw = getArg("--burp-timeout-ms", argv);
const timeoutMs = timeoutMsRaw ? parseInt(timeoutMsRaw, 10) : undefined;
const mcpProxyArgs = mcpProxyJar
? [
"-jar",
mcpProxyJar,
"--sse-url",
mcpSseUrl ?? "http://127.0.0.1:9876/sse",
]
: undefined;

return {
enabled: true,
...(transport === "sse" || transport === "stdio" ? { transport } : {}),
...(getArg("--burp-proxy", argv)
? { proxyUrl: getArg("--burp-proxy", argv) }
: {}),
...(mcpSseUrl ? { mcpSseUrl } : {}),
...(getArg("--burp-mcp-proxy-command", argv)
? { mcpProxyCommand: getArg("--burp-mcp-proxy-command", argv) }
: {}),
...(mcpProxyArgs ? { mcpProxyArgs } : {}),
...(timeoutMs && Number.isFinite(timeoutMs) ? { timeoutMs } : {}),
...(hasFlag("--burp-allow-config-mutation", argv)
? { allowConfigMutation: true }
: {}),
...(hasFlag("--burp-insecure-tls", argv) ? { ignoreTlsErrors: true } : {}),
};
}

function mergeBurpSuiteConfig(
sessionConfig: SessionConfig["burpSuite"],
userConfig: Awaited<
ReturnType<typeof import("./core/config").config.get>
>["burpMcp"],
): SessionConfig["burpSuite"] {
if (!sessionConfig?.enabled) return sessionConfig;
return {
enabled: true,
transport: sessionConfig.transport ?? userConfig?.transport,
proxyUrl: sessionConfig.proxyUrl ?? userConfig?.proxyUrl,
sseUrl:
sessionConfig.sseUrl ?? sessionConfig.mcpSseUrl ?? userConfig?.sseUrl,
mcpSseUrl:
sessionConfig.mcpSseUrl ?? sessionConfig.sseUrl ?? userConfig?.sseUrl,
mcpProxyCommand: sessionConfig.mcpProxyCommand ?? userConfig?.stdioCommand,
mcpProxyArgs: sessionConfig.mcpProxyArgs ?? userConfig?.stdioArgs,
timeoutMs: sessionConfig.timeoutMs ?? userConfig?.timeoutMs,
allowedTargets: sessionConfig.allowedTargets ?? userConfig?.allowedTargets,
allowConfigMutation:
sessionConfig.allowConfigMutation ?? userConfig?.allowConfigMutation,
ignoreTlsErrors:
sessionConfig.ignoreTlsErrors ?? userConfig?.ignoreTlsErrors,
};
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicated mergeBurpSuiteConfig across CLI and TUI

Medium Severity

mergeBurpSuiteConfig is copy-pasted identically in src/cli.ts and src/tui/command-registry.ts. This duplicated logic increases maintenance burden — a bug fix or field addition in one copy can easily be missed in the other. The function could live in a shared utility (e.g., alongside resolveBurpSuiteConfig in burpConfig.ts) and be imported from both call sites.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 62879d29e5ae565d781f7587b63c6fe73bb25b76. Configure here.


function attachCliAgentStreamListeners(bus: AgentEventBus): void {
bus.on("text-delta", (d) => process.stdout.write(d.text));
bus.on("tool-call-complete", (d) => console.log(`\n→ ${d.toolName}`));
Expand Down Expand Up @@ -106,6 +180,7 @@ Usage:
pensar pentests List and manage pentests
pensar issues List and manage security issues
pensar fixes View security fixes
pensar burp <command> Manage Burp Suite MCP integration
pensar logs View agent execution logs
pensar upgrade Update pensar to the latest version
pensar doctor Check dependencies and install missing tools
Expand All @@ -121,11 +196,21 @@ pentest options:
--task-driven Enable task-driven architecture (experimental)
--prompt <text|@file> Guidance for the pentest agent (inline text or @filepath)
--threat-model <text|@file> Threat model to guide the pentest (inline or @filepath)
--burp Pair with Burp Suite (proxy + MCP when configured)
--burp-proxy <url> Burp Proxy URL (default: http://127.0.0.1:8080)
--burp-transport <type> Burp MCP transport: sse or stdio (default: sse)
--burp-mcp-url <url> Burp MCP SSE URL (default: http://127.0.0.1:9876/sse)
--burp-mcp-proxy-jar <path> Path to Burp MCP stdio proxy JAR
--burp-mcp-proxy-command <path> Java executable for the Burp MCP proxy (default: java)
--burp-timeout-ms <ms> Burp MCP connection/tool timeout
--burp-allow-config-mutation Allow Burp MCP config-modifying tools
--burp-insecure-tls Ignore TLS errors when proxying through Burp

targeted-pentest options:
--target <url> (required) Target URL / domain / IP
--objective <text> (required, repeatable) Testing objective
--model <model> AI model (default: claude-sonnet-4-5)
--burp Pair with Burp Suite (proxy + MCP when configured)

threat-model options:
--output, -o <path> Output file path (default: ./threat-model.md)
Expand Down Expand Up @@ -161,6 +246,7 @@ async function runPentest() {
const threatModelRaw = getArg("--threat-model");
const enableThinking = hasFlag("--extended-thinking");
const taskDriven = hasFlag("--task-driven");
const burpSuite = buildBurpSuiteConfig();

// Resolve and combine threat model + prompt
const resolvedTm = threatModelRaw
Expand All @@ -170,6 +256,10 @@ async function runPentest() {
const prompt = combinePromptParts(resolvedTm, resolvedPrompt);

const pensarConfig = await appConfig.get();
const effectiveBurpSuite = mergeBurpSuiteConfig(
burpSuite,
pensarConfig.burpMcp,
);
const dynamicDefault =
getDefaultModelForConfig(pensarConfig)?.id ?? "claude-sonnet-4-5";
const model = (getArg("--model") ?? dynamicDefault) as AIModel;
Expand All @@ -183,7 +273,7 @@ async function runPentest() {
console.log(`${sep}
PENTEST ORCHESTRATION
${sep}
Target: ${target}${cwd ? `\nCwd: ${cwd} (whitebox)` : ""}${exfilMode ? "\nMode: exfil" : ""}
Target: ${target}${cwd ? `\nCwd: ${cwd} (whitebox)` : ""}${exfilMode ? "\nMode: exfil" : ""}${effectiveBurpSuite?.enabled ? "\nBurp: enabled" : ""}
Model: ${model}${enableThinking ? "\nThinking: enabled" : ""}${taskDriven ? "\nTask-driven: enabled" : ""}
`);

Expand All @@ -195,6 +285,7 @@ Model: ${model}${enableThinking ? "\nThinking: enabled" : ""}${taskDriven ? "\
...(exfilMode ? { exfilMode: true } : {}),
...(prompt ? { prompt } : {}),
...(taskDriven ? { taskDriven: true } : {}),
...(effectiveBurpSuite ? { burpSuite: effectiveBurpSuite } : {}),
},
});

Expand Down Expand Up @@ -238,8 +329,13 @@ async function runTargetedPentest() {

const target = getArgRequired("--target");
const objectives = getAllArgs("--objective");
const burpSuite = buildBurpSuiteConfig();

const pensarConfig = await appConfig.get();
const effectiveBurpSuite = mergeBurpSuiteConfig(
burpSuite,
pensarConfig.burpMcp,
);
const dynamicDefault =
getDefaultModelForConfig(pensarConfig)?.id ?? "claude-sonnet-4-5";
const model = (getArg("--model") ?? dynamicDefault) as AIModel;
Expand All @@ -257,6 +353,7 @@ async function runTargetedPentest() {
TARGETED PENTEST
${sep}
Target: ${target}
${effectiveBurpSuite?.enabled ? "Burp: enabled\n" : ""}
Model: ${model}
Objectives:
${objectivesList}
Expand All @@ -265,6 +362,9 @@ ${objectivesList}
const session = await sessions.create({
name: "Targeted Pentest",
targets: [target],
config: {
...(effectiveBurpSuite ? { burpSuite: effectiveBurpSuite } : {}),
},
});

const { bus: targetedBus, cleanup: wandbCleanup } =
Expand Down Expand Up @@ -385,6 +485,9 @@ if (command === "version" || command === "--version" || command === "-v") {
} else if (command === "logs") {
process.argv = [process.argv[0], process.argv[1], ...args.slice(1)];
await import("./cli/logs");
} else if (command === "burp") {
process.argv = [process.argv[0], process.argv[1], ...args.slice(1)];
await import("./cli/burp");
} else if (command === "threat-model") {
await runThreatModel();
} else if (command === "doctor") {
Expand Down
Loading
Loading