break-anything 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/bin/brk.js +34 -16
- package/docs/readme/README_zh.md +1 -1
- package/package.json +1 -1
- package/src/mcp.js +1 -1
- package/src/setup.js +109 -0
package/README.md
CHANGED
|
@@ -60,8 +60,9 @@ locally. Nothing is pointed anywhere you don't own.
|
|
|
60
60
|
npx break-anything setup
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
One command. It auto-detects Claude Desktop, Claude Code, Cursor, Codex, Gemini CLI and Windsurf
|
|
64
|
+
on your machine and registers Break Anything into each — backing up every file it touches,
|
|
65
|
+
preserving your other servers, safe to re-run. Restart your agent, then just ask:
|
|
65
66
|
|
|
66
67
|
```text
|
|
67
68
|
Use Break Anything on this project. Show me only the vulnerabilities that are
|
package/bin/brk.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { scanDependencies } from "../src/scan.js";
|
|
3
3
|
import { renderHuman } from "../src/report.js";
|
|
4
4
|
|
|
5
|
-
const VERSION = "0.
|
|
5
|
+
const VERSION = "0.3.0";
|
|
6
6
|
|
|
7
7
|
const HELP = `Break Anything (brk) v${VERSION}
|
|
8
8
|
点你的 Agent 指向你拥有或已授权的软件,找到漏洞、证明它、修好它。
|
|
@@ -14,7 +14,7 @@ const HELP = `Break Anything (brk) v${VERSION}
|
|
|
14
14
|
brk sast [path] 静态代码分析找漏洞(需 semgrep)
|
|
15
15
|
brk mcp 以 MCP server 模式启动(供 Agent 调用)
|
|
16
16
|
brk doctor 检查运行环境与网络连通性
|
|
17
|
-
brk setup
|
|
17
|
+
brk setup 自动把 Break Anything 注册进本机各家 Agent(--dry-run 预演,--print 仅打印配置)
|
|
18
18
|
brk --help 显示本帮助
|
|
19
19
|
brk --version 显示版本
|
|
20
20
|
|
|
@@ -142,7 +142,38 @@ async function main() {
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
if (cmd === "setup") {
|
|
145
|
-
|
|
145
|
+
const { flags } = parseFlags(rest);
|
|
146
|
+
const { runSetup, manualConfig } = await import("../src/setup.js");
|
|
147
|
+
if (flags.print) {
|
|
148
|
+
process.stdout.write(
|
|
149
|
+
"把下面的配置加入你的 MCP 客户端,然后重启 Agent:\n\n" +
|
|
150
|
+
JSON.stringify(manualConfig, null, 2) + "\n",
|
|
151
|
+
);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const dry = !!flags["dry-run"];
|
|
155
|
+
const { results, absent, anyPresent } = await runSetup({ dryRun: dry });
|
|
156
|
+
process.stdout.write(`Break Anything · setup${dry ? " (dry run)" : ""}\n\n`);
|
|
157
|
+
if (!anyPresent) {
|
|
158
|
+
process.stdout.write(
|
|
159
|
+
"未检测到受支持的 Agent。把下面的配置手动加入你的 MCP 客户端:\n\n" +
|
|
160
|
+
JSON.stringify(manualConfig, null, 2) + "\n",
|
|
161
|
+
);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const label = {
|
|
165
|
+
created: "✓ 已注册(新建配置)", updated: "✓ 已注册", already: "• 已存在,无需改动",
|
|
166
|
+
"would-create": "将新建并注册", "would-update": "将注册",
|
|
167
|
+
skipped: "⚠ 跳过", error: "✗ 出错",
|
|
168
|
+
};
|
|
169
|
+
for (const r of results) {
|
|
170
|
+
process.stdout.write(` ${label[r.status] || r.status} ${r.agent.name}\n`);
|
|
171
|
+
process.stdout.write(` ${r.agent.file}\n`);
|
|
172
|
+
if (r.backup) process.stdout.write(` 已备份原配置 → ${r.backup}\n`);
|
|
173
|
+
if (r.reason) process.stdout.write(` ${r.reason}\n`);
|
|
174
|
+
}
|
|
175
|
+
if (absent.length) process.stdout.write(`\n未检测到(已跳过):${absent.map((a) => a.name).join("、")}\n`);
|
|
176
|
+
if (!dry) process.stdout.write(`\n重启你的 Agent,然后对它说:"用 Break Anything 扫描我的项目"。\n`);
|
|
146
177
|
return;
|
|
147
178
|
}
|
|
148
179
|
|
|
@@ -168,19 +199,6 @@ async function doctor() {
|
|
|
168
199
|
process.stdout.write(out.join("\n") + "\n");
|
|
169
200
|
}
|
|
170
201
|
|
|
171
|
-
function printSetup() {
|
|
172
|
-
const config = {
|
|
173
|
-
mcpServers: {
|
|
174
|
-
"break-anything": { command: "npx", args: ["-y", "break-anything", "mcp"] },
|
|
175
|
-
},
|
|
176
|
-
};
|
|
177
|
-
process.stdout.write(
|
|
178
|
-
"把下面的配置加入你的 MCP 客户端(Claude Code / Cursor / Codex 等):\n\n" +
|
|
179
|
-
JSON.stringify(config, null, 2) +
|
|
180
|
-
"\n\n然后重启 Agent,对它说:用 Break Anything 扫描我的项目依赖。\n",
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
202
|
main().catch((err) => {
|
|
185
203
|
process.stderr.write(`错误:${err instanceof Error ? err.stack || err.message : String(err)}\n`);
|
|
186
204
|
process.exitCode = 1;
|
package/docs/readme/README_zh.md
CHANGED
|
@@ -47,7 +47,7 @@ Break Anything 把你的编码 Agent 指向你自己的代码、应用和基础
|
|
|
47
47
|
npx break-anything setup
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
一行命令。它自动探测本机的 Claude Desktop、Claude Code、Cursor、Codex、Gemini CLI、Windsurf,把 Break Anything 注册进每一个——每个文件先备份、保留你原有的其它 server、可安全重复运行。重启 Agent,然后直接说:
|
|
51
51
|
|
|
52
52
|
```text
|
|
53
53
|
用 Break Anything 扫这个项目。只给我代码里真正可达的漏洞,证明最严重的那条,给我修复方案。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "break-anything",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "One CLI and MCP server for coding agents to find vulnerabilities across code, dependencies, apps, and infrastructure — on targets you own or are authorized to test.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
package/src/mcp.js
CHANGED
package/src/setup.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir, copyFile } from "node:fs/promises";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
// 一行把 Break Anything 的 MCP server 注册进本机各家编码 Agent。
|
|
7
|
+
// 原则:只探测已安装的 Agent;先备份;只增/改 break-anything 这一个条目,绝不动别的;
|
|
8
|
+
// 幂等、可重复运行;坏配置文件只跳过不破坏。
|
|
9
|
+
|
|
10
|
+
const SERVER_NAME = "break-anything";
|
|
11
|
+
const ENTRY = { command: "npx", args: ["-y", "break-anything", "mcp"] };
|
|
12
|
+
|
|
13
|
+
// 各家 Agent 的配置文件与"已安装"标记目录(覆盖 macOS / Windows / Linux)
|
|
14
|
+
export function agents(home = os.homedir(), platform = process.platform, env = process.env) {
|
|
15
|
+
const appData = env.APPDATA || path.join(home, "AppData", "Roaming");
|
|
16
|
+
const claudeDesktop =
|
|
17
|
+
platform === "darwin" ? path.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json")
|
|
18
|
+
: platform === "win32" ? path.join(appData, "Claude", "claude_desktop_config.json")
|
|
19
|
+
: path.join(home, ".config", "Claude", "claude_desktop_config.json");
|
|
20
|
+
return [
|
|
21
|
+
{ id: "claude-desktop", name: "Claude Desktop", file: claudeDesktop, format: "json", markers: [path.dirname(claudeDesktop)] },
|
|
22
|
+
{ id: "claude-code", name: "Claude Code", file: path.join(home, ".claude.json"), format: "json", markers: [path.join(home, ".claude.json"), path.join(home, ".claude")] },
|
|
23
|
+
{ id: "cursor", name: "Cursor", file: path.join(home, ".cursor", "mcp.json"), format: "json", markers: [path.join(home, ".cursor")] },
|
|
24
|
+
{ id: "windsurf", name: "Windsurf", file: path.join(home, ".codeium", "windsurf", "mcp_config.json"), format: "json", markers: [path.join(home, ".codeium", "windsurf"), path.join(home, ".codeium")] },
|
|
25
|
+
{ id: "gemini", name: "Gemini CLI", file: path.join(home, ".gemini", "settings.json"), format: "json", markers: [path.join(home, ".gemini")] },
|
|
26
|
+
{ id: "codex", name: "Codex", file: path.join(home, ".codex", "config.toml"), format: "toml", markers: [path.join(home, ".codex")] },
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isPresent(agent) {
|
|
31
|
+
return existsSync(agent.file) || agent.markers.some((m) => existsSync(m));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ---- 纯函数(便于单测)----
|
|
35
|
+
|
|
36
|
+
// JSON:把 break-anything 合并进 mcpServers,保留其它所有内容
|
|
37
|
+
export function mergeMcpJson(obj) {
|
|
38
|
+
const next = obj && typeof obj === "object" ? { ...obj } : {};
|
|
39
|
+
next.mcpServers = { ...(next.mcpServers || {}) };
|
|
40
|
+
next.mcpServers[SERVER_NAME] = { ...ENTRY };
|
|
41
|
+
return next;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// TOML:是否已有我们的 section
|
|
45
|
+
export function tomlHasServer(text) {
|
|
46
|
+
return /\[mcp_servers\.break-anything\]/.test(text || "");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// TOML:追加我们的 section(不解析整个文件,只安全追加)
|
|
50
|
+
export function appendTomlServer(text) {
|
|
51
|
+
const block =
|
|
52
|
+
`\n[mcp_servers.${SERVER_NAME}]\n` +
|
|
53
|
+
`command = "npx"\n` +
|
|
54
|
+
`args = ["-y", "break-anything", "mcp"]\n`;
|
|
55
|
+
const base = text && !text.endsWith("\n") ? text + "\n" : text || "";
|
|
56
|
+
return base + block;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ---- 应用到单个 Agent ----
|
|
60
|
+
async function applyOne(agent, { dryRun }) {
|
|
61
|
+
const existed = existsSync(agent.file);
|
|
62
|
+
let backup = null;
|
|
63
|
+
|
|
64
|
+
if (agent.format === "json") {
|
|
65
|
+
let obj = {};
|
|
66
|
+
if (existed) {
|
|
67
|
+
const raw = await readFile(agent.file, "utf8");
|
|
68
|
+
try { obj = raw.trim() ? JSON.parse(raw) : {}; }
|
|
69
|
+
catch { return { agent, status: "skipped", reason: "现有配置不是合法 JSON,未改动(请手动添加)" }; }
|
|
70
|
+
if (obj.mcpServers && obj.mcpServers[SERVER_NAME]) {
|
|
71
|
+
// 已存在且一致则跳过;否则更新
|
|
72
|
+
const cur = JSON.stringify(obj.mcpServers[SERVER_NAME]);
|
|
73
|
+
if (cur === JSON.stringify(ENTRY)) return { agent, status: "already" };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const next = mergeMcpJson(obj);
|
|
77
|
+
if (dryRun) return { agent, status: existed ? "would-update" : "would-create" };
|
|
78
|
+
await mkdir(path.dirname(agent.file), { recursive: true });
|
|
79
|
+
if (existed) { backup = agent.file + ".bak"; await copyFile(agent.file, backup); }
|
|
80
|
+
await writeFile(agent.file, JSON.stringify(next, null, 2) + "\n");
|
|
81
|
+
return { agent, status: existed ? "updated" : "created", backup };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// toml (Codex)
|
|
85
|
+
let text = existed ? await readFile(agent.file, "utf8") : "";
|
|
86
|
+
if (tomlHasServer(text)) return { agent, status: "already" };
|
|
87
|
+
if (dryRun) return { agent, status: existed ? "would-update" : "would-create" };
|
|
88
|
+
await mkdir(path.dirname(agent.file), { recursive: true });
|
|
89
|
+
if (existed) { backup = agent.file + ".bak"; await copyFile(agent.file, backup); }
|
|
90
|
+
await writeFile(agent.file, appendTomlServer(text));
|
|
91
|
+
return { agent, status: existed ? "updated" : "created", backup };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ---- 入口 ----
|
|
95
|
+
export async function runSetup({ dryRun = false, home, platform, env } = {}) {
|
|
96
|
+
const all = agents(home, platform, env);
|
|
97
|
+
const present = all.filter(isPresent);
|
|
98
|
+
const absent = all.filter((a) => !isPresent(a));
|
|
99
|
+
const results = [];
|
|
100
|
+
for (const a of present) {
|
|
101
|
+
try { results.push(await applyOne(a, { dryRun })); }
|
|
102
|
+
catch (e) { results.push({ agent: a, status: "error", reason: e instanceof Error ? e.message : String(e) }); }
|
|
103
|
+
}
|
|
104
|
+
return { results, absent, anyPresent: present.length > 0 };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export const manualConfig = {
|
|
108
|
+
mcpServers: { [SERVER_NAME]: ENTRY },
|
|
109
|
+
};
|