@kendoo.agentdesk/agentdesk 0.25.1 → 0.26.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 +1 -0
- package/bin/agentdesk.mjs +8 -0
- package/cli/screenshot.mjs +21 -3
- package/cli/security-check.mjs +187 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -123,6 +123,7 @@ agentdesk team <TASK-ID> Run a team session on an existing task
|
|
|
123
123
|
agentdesk team -d "..." Describe what you want — task created automatically
|
|
124
124
|
agentdesk <agent> -d "..." Run a single agent (jane, dennis, sam, bart, vera, luna, mark, nora)
|
|
125
125
|
agentdesk daemon Start daemon for remote sessions
|
|
126
|
+
agentdesk security-check [-d <dir>] Multi-surface security audit (server / CLI / UI / prompts)
|
|
126
127
|
agentdesk update Update to the latest version
|
|
127
128
|
```
|
|
128
129
|
|
package/bin/agentdesk.mjs
CHANGED
|
@@ -74,6 +74,7 @@ if (!command || command === "help" || command === "--help") {
|
|
|
74
74
|
agentdesk team -d "..." Create a task and run a session
|
|
75
75
|
agentdesk <agent> -d "..." Run a single agent (jane, dennis, sam, bart, vera, luna, mark, nora)
|
|
76
76
|
agentdesk daemon Start daemon for remote sessions
|
|
77
|
+
agentdesk security-check [-d <dir>] Multi-surface security audit (server / CLI / UI / prompts)
|
|
77
78
|
agentdesk update Update to the latest version
|
|
78
79
|
|
|
79
80
|
Options:
|
|
@@ -207,6 +208,13 @@ else if (command === "daemon") {
|
|
|
207
208
|
await runDaemon();
|
|
208
209
|
}
|
|
209
210
|
|
|
211
|
+
else if (command === "security-check") {
|
|
212
|
+
const { runSecurityCheck, parseSecurityCheckArgs } = await import("../cli/security-check.mjs");
|
|
213
|
+
const opts = parseSecurityCheckArgs(args.slice(1));
|
|
214
|
+
const code = await runSecurityCheck(opts);
|
|
215
|
+
process.exit(code);
|
|
216
|
+
}
|
|
217
|
+
|
|
210
218
|
else if (command === "update") {
|
|
211
219
|
const { execSync } = await import("child_process");
|
|
212
220
|
const currentVersion = pkg.version;
|
package/cli/screenshot.mjs
CHANGED
|
@@ -31,9 +31,27 @@ async function loadPuppeteer() {
|
|
|
31
31
|
try {
|
|
32
32
|
return await import("puppeteer");
|
|
33
33
|
} catch {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
// AD-50: require explicit consent before installing puppeteer (a ~200 MB
|
|
35
|
+
// dependency that pulls in a Chromium download). Previously this ran
|
|
36
|
+
// silently on first screenshot — surprising, and a supply-chain compromise
|
|
37
|
+
// of puppeteer would have applied transparently.
|
|
38
|
+
if (process.env.AGENTDESK_SCREENSHOT_AUTO_INSTALL === "1") {
|
|
39
|
+
console.log("Installing puppeteer (AGENTDESK_SCREENSHOT_AUTO_INSTALL=1)...");
|
|
40
|
+
} else if (process.stdin.isTTY) {
|
|
41
|
+
const { createInterface } = await import("readline");
|
|
42
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
43
|
+
const answer = await new Promise(resolve =>
|
|
44
|
+
rl.question("Screenshot requires puppeteer (~200 MB). Install now? [y/N] ", resolve)
|
|
45
|
+
);
|
|
46
|
+
rl.close();
|
|
47
|
+
if (!/^y(es)?$/i.test(String(answer).trim())) {
|
|
48
|
+
throw new Error("Puppeteer install declined. Re-run with AGENTDESK_SCREENSHOT_AUTO_INSTALL=1 to skip this prompt.");
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
throw new Error("puppeteer is not installed and no terminal is available to confirm. Set AGENTDESK_SCREENSHOT_AUTO_INSTALL=1 to install non-interactively.");
|
|
52
|
+
}
|
|
53
|
+
const { execFileSync } = await import("child_process");
|
|
54
|
+
execFileSync("npm", ["install", "--no-save", "puppeteer"], { stdio: "inherit" });
|
|
37
55
|
return await import("puppeteer");
|
|
38
56
|
}
|
|
39
57
|
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// `agentdesk security-check` — runs a multi-surface security audit on a
|
|
2
|
+
// codebase by spawning a Claude process scoped to the target directory.
|
|
3
|
+
//
|
|
4
|
+
// The audit pattern: identify which surfaces exist (server / CLI / UI /
|
|
5
|
+
// prompts), spawn parallel investigators per surface via Claude's Agent
|
|
6
|
+
// tool, return one triaged punch list (Critical / High / Medium / Low)
|
|
7
|
+
// with file:line citations and concrete fix recommendations.
|
|
8
|
+
//
|
|
9
|
+
// This is a batch full-codebase audit, NOT a PR/diff review. For diff
|
|
10
|
+
// review use Claude Code's built-in `/security-review` skill instead.
|
|
11
|
+
|
|
12
|
+
import { spawn } from "child_process";
|
|
13
|
+
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
14
|
+
import { resolve, join } from "path";
|
|
15
|
+
|
|
16
|
+
const PROMPT = `You are running a multi-surface security audit on the codebase in the current working directory.
|
|
17
|
+
|
|
18
|
+
Goal: produce one triaged punch list of security findings (Critical / High / Medium / Low) with file:line citations and concrete fix recommendations.
|
|
19
|
+
|
|
20
|
+
Step 1 — Survey what's here:
|
|
21
|
+
- Server-shaped dirs: server/, api/, backend/, services/, plus *.py / *.go / *.rs / *.mjs / *.ts that look like HTTP/WS server entrypoints.
|
|
22
|
+
- CLI-shaped dirs: bin/, cli/, cmd/, top-level scripts.
|
|
23
|
+
- UI-shaped dirs: src/, app/, web/, frontend/, components/, index.html.
|
|
24
|
+
- Prompts / config inlined into LLM calls: prompts/, .claude/, CLAUDE.md, MCP configs, agent skill files.
|
|
25
|
+
|
|
26
|
+
Tell the user which surfaces you'll cover before launching investigators. Skip any that don't exist.
|
|
27
|
+
|
|
28
|
+
Step 2 — Spawn parallel investigators (one per existing surface) via the Agent tool with subagent_type "general-purpose". Send them in a single message with multiple tool calls so they run concurrently.
|
|
29
|
+
|
|
30
|
+
Each investigator gets the surface path and a focused checklist:
|
|
31
|
+
|
|
32
|
+
Server / API — auth bypass (missing auth gates, JWT weaknesses, CSRF, session fixation), authorization / IDOR on every :id route, input validation / injection (SQL via string concat, command injection, path traversal, SSRF, eval, prototype pollution), credential handling (encryption mode, IV reuse, key derivation, leak surfaces), rate limiting on auth endpoints, WebSocket auth (auth-by-message is non-standard), dependency vulns, secret exposure, error handling that leaks stack traces.
|
|
33
|
+
|
|
34
|
+
CLI / daemon — command injection (every exec/execSync template-literal sink), path traversal in fs.writeFile / path.join, sandbox claims vs implementation (denylists are usually wrong), credential exposure (tokens in logs/argv/errors/temp files), URL parsing SSRF, prompt-injection vectors when CLI feeds external content into an LLM, daemon-push abuse (signed? confirmation required?), config/credential precedence bugs.
|
|
35
|
+
|
|
36
|
+
UI / frontend / prompts — XSS in any dangerouslySetInnerHTML or markdown render of user-controlled content (agent output is untrusted), JWT/secrets in localStorage, bundled VITE_/NEXT_PUBLIC env vars in the public bundle, CSRF / SameSite, open redirect / clickjacking / missing frame-ancestors, <a href> without scheme allowlist (javascript: URLs run same-origin), prompt-injection robustness (untrusted-data delimiters? hard system rule against following directives in user content?), dependency vulns, CSP / security headers.
|
|
37
|
+
|
|
38
|
+
Each investigator returns a triaged list (Critical / High / Medium / Low) with file:line citations and concrete fix recommendations. Cap their responses at ~700 words; tell them to prioritize ruthlessly.
|
|
39
|
+
|
|
40
|
+
Step 3 — Synthesize:
|
|
41
|
+
- Group findings by severity (not by surface — the user wants the queue, not the org chart).
|
|
42
|
+
- Look for chains where a Medium reaches a Critical — note them.
|
|
43
|
+
- Identify the 2-3 structural patterns driving the findings (e.g., "untrusted data treated as trusted across boundaries", "sandbox claim doesn't match implementation"). These root causes are more useful than per-line fixes.
|
|
44
|
+
- Propose a triage plan in dependency order (which fixes ship together, what order minimizes the exposure window).
|
|
45
|
+
|
|
46
|
+
Step 4 — Present:
|
|
47
|
+
|
|
48
|
+
# Security audit — <project name> — <date>
|
|
49
|
+
|
|
50
|
+
## Headline numbers
|
|
51
|
+
- N Critical (exploitable as-is)
|
|
52
|
+
- N High (real risk, needs a chain or specific condition)
|
|
53
|
+
- N Medium (defense-in-depth gaps)
|
|
54
|
+
- N Low (hardening opportunities)
|
|
55
|
+
|
|
56
|
+
## The N things to fix this week
|
|
57
|
+
<top items with file:line + why-exploitable + concrete fix>
|
|
58
|
+
|
|
59
|
+
## Full punch list (compact)
|
|
60
|
+
### CRITICAL
|
|
61
|
+
- file.ext:LL — short description. Why exploitable: ... Suggested fix: ...
|
|
62
|
+
### HIGH ...
|
|
63
|
+
### MEDIUM ...
|
|
64
|
+
### LOW ...
|
|
65
|
+
|
|
66
|
+
## The pattern
|
|
67
|
+
<2-3 sentence root-cause analysis>
|
|
68
|
+
|
|
69
|
+
## Suggested triage plan
|
|
70
|
+
<ordered plan with rough day-by-day>
|
|
71
|
+
|
|
72
|
+
Important:
|
|
73
|
+
- Do NOT fix anything. Audit and report only.
|
|
74
|
+
- Be honest about scope — a real audit is days. This one pass produces a focused triage list.
|
|
75
|
+
- For any CHANGELOG entry referencing security work, use NEUTRAL phrasing ("Security improvements. No action required.") — never name the bug class on public release notes.
|
|
76
|
+
`;
|
|
77
|
+
|
|
78
|
+
function colorize() {
|
|
79
|
+
const isTTY = process.stdout.isTTY;
|
|
80
|
+
const c = (code) => (s) => isTTY ? `\x1b[${code}m${s}\x1b[0m` : s;
|
|
81
|
+
return { dim: c(2), green: c(32), cyan: c(36), yellow: c(33), red: c(31) };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function runSecurityCheck({ cwd, out }) {
|
|
85
|
+
const { dim, green, cyan, yellow, red } = colorize();
|
|
86
|
+
const targetDir = resolve(cwd || process.cwd());
|
|
87
|
+
if (!existsSync(targetDir)) {
|
|
88
|
+
console.error(`${red("Directory not found:")} ${targetDir}`);
|
|
89
|
+
return 1;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Default output path: .agentdesk/security-audit-<timestamp>.md inside the audited project.
|
|
93
|
+
const stamp = new Date().toISOString().replace(/[:T]/g, "-").slice(0, 16);
|
|
94
|
+
const outPath = resolve(out || join(targetDir, ".agentdesk", `security-audit-${stamp}.md`));
|
|
95
|
+
mkdirSync(join(outPath, ".."), { recursive: true });
|
|
96
|
+
|
|
97
|
+
console.log("");
|
|
98
|
+
console.log(` ${green("agentdesk security-check")}`);
|
|
99
|
+
console.log(` ${dim("Target:")} ${targetDir}`);
|
|
100
|
+
console.log(` ${dim("Report:")} ${outPath}`);
|
|
101
|
+
console.log("");
|
|
102
|
+
console.log(` ${dim("Spawning Claude with the audit prompt. This typically takes 5–15 minutes.")}`);
|
|
103
|
+
console.log(` ${dim("Output streams to your terminal AND lands in the report file when done.")}`);
|
|
104
|
+
console.log("");
|
|
105
|
+
|
|
106
|
+
// Spawn `claude -p <prompt>` against the target directory. The prompt tells
|
|
107
|
+
// Claude to use its Agent tool to fan out to parallel investigators.
|
|
108
|
+
const child = spawn("claude", ["-p", PROMPT, "--permission-mode", "acceptEdits"], {
|
|
109
|
+
cwd: targetDir,
|
|
110
|
+
stdio: ["inherit", "pipe", "inherit"],
|
|
111
|
+
env: process.env,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
let captured = "";
|
|
115
|
+
child.stdout.on("data", (buf) => {
|
|
116
|
+
const chunk = buf.toString();
|
|
117
|
+
captured += chunk;
|
|
118
|
+
process.stdout.write(chunk);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return new Promise((resolveExit) => {
|
|
122
|
+
child.on("error", (err) => {
|
|
123
|
+
console.error(`\n ${red("Failed to spawn claude:")} ${err.message}`);
|
|
124
|
+
console.error(` ${dim("Is the Claude Code CLI installed and on your PATH? https://claude.ai/code")}`);
|
|
125
|
+
resolveExit(1);
|
|
126
|
+
});
|
|
127
|
+
child.on("close", (code) => {
|
|
128
|
+
try {
|
|
129
|
+
const header = [
|
|
130
|
+
`# Security audit — ${targetDir.split("/").pop()}`,
|
|
131
|
+
`Generated: ${new Date().toISOString()}`,
|
|
132
|
+
`Target: \`${targetDir}\``,
|
|
133
|
+
`Tool: agentdesk security-check`,
|
|
134
|
+
``,
|
|
135
|
+
`---`,
|
|
136
|
+
``,
|
|
137
|
+
].join("\n");
|
|
138
|
+
writeFileSync(outPath, header + captured);
|
|
139
|
+
console.log("");
|
|
140
|
+
console.log(` ${green("Report written:")} ${outPath}`);
|
|
141
|
+
if (code !== 0) {
|
|
142
|
+
console.log(` ${yellow("Claude exited with code")} ${code}${dim(" — report may be partial.")}`);
|
|
143
|
+
}
|
|
144
|
+
} catch (err) {
|
|
145
|
+
console.error(`\n ${red("Failed to write report:")} ${err.message}`);
|
|
146
|
+
}
|
|
147
|
+
resolveExit(code === null ? 1 : code);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function parseSecurityCheckArgs(args) {
|
|
153
|
+
let cwd = null;
|
|
154
|
+
let out = null;
|
|
155
|
+
for (let i = 0; i < args.length; i++) {
|
|
156
|
+
if ((args[i] === "--cwd" || args[i] === "-d") && args[i + 1]) {
|
|
157
|
+
cwd = args[++i];
|
|
158
|
+
} else if (args[i] === "--out" && args[i + 1]) {
|
|
159
|
+
out = args[++i];
|
|
160
|
+
} else if (args[i] === "--help" || args[i] === "-h") {
|
|
161
|
+
printHelp();
|
|
162
|
+
process.exit(0);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return { cwd, out };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function printHelp() {
|
|
169
|
+
console.log(`
|
|
170
|
+
agentdesk security-check — multi-surface security audit
|
|
171
|
+
|
|
172
|
+
Usage:
|
|
173
|
+
agentdesk security-check Audit the current directory
|
|
174
|
+
agentdesk security-check -d <dir> Audit a different directory
|
|
175
|
+
agentdesk security-check --out <file> Custom report output path
|
|
176
|
+
|
|
177
|
+
Spawns a Claude process scoped to the target directory. Claude surveys
|
|
178
|
+
the codebase, fans out to parallel investigators (server / CLI / UI /
|
|
179
|
+
prompts), and returns a triaged punch list with file:line citations.
|
|
180
|
+
|
|
181
|
+
Output:
|
|
182
|
+
Streams to your terminal as Claude works, and saves a copy to:
|
|
183
|
+
<target>/.agentdesk/security-audit-<timestamp>.md (by default)
|
|
184
|
+
|
|
185
|
+
Requires the Claude Code CLI on your PATH. https://claude.ai/code
|
|
186
|
+
`);
|
|
187
|
+
}
|