@kendoo.agentdesk/agentdesk 0.25.0 → 0.25.2

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/cli/daemon.mjs CHANGED
@@ -92,7 +92,16 @@ function logSessionMetadata(sessionId, metadata) {
92
92
 
93
93
  // --- Find project on local filesystem ---
94
94
 
95
+ // AD-47: server-pushed projectName flows into path.join() here. Validate
96
+ // against a strict character set so values like "../../etc" can't stat
97
+ // arbitrary host paths or land in projects.json with traversal sequences
98
+ // for later use.
99
+ const PROJECT_NAME_RE = /^[A-Za-z0-9._-]{1,64}$/;
100
+
95
101
  function findProjectLocally(projectName) {
102
+ if (!PROJECT_NAME_RE.test(String(projectName || ""))) {
103
+ return null;
104
+ }
96
105
  const home = process.env.HOME || process.env.USERPROFILE;
97
106
  // Search common code directories for a folder matching the project name
98
107
  const searchRoots = [
package/cli/login.mjs CHANGED
@@ -81,11 +81,12 @@ export async function runLogin() {
81
81
 
82
82
  const state = randomUUID();
83
83
 
84
- // Start a local server to receive the API key callback
84
+ // Start a local server to receive the API key callback. AD-48: no CORS
85
+ // headers — the dashboard hits this endpoint as a top-level navigation
86
+ // (window.location.href = ...), which doesn't require CORS. Removing the
87
+ // wildcard ACAO closes the timing oracle that let any origin XHR-probe
88
+ // whether a login was in progress on this port.
85
89
  const server = createServer((req, res) => {
86
- // Allow CORS from agentdesk.live
87
- res.setHeader("Access-Control-Allow-Origin", "*");
88
- res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
89
90
  if (req.method === "OPTIONS") { res.writeHead(200); res.end(); return; }
90
91
 
91
92
  const url = new URL(req.url, `http://localhost`);
@@ -31,9 +31,27 @@ async function loadPuppeteer() {
31
31
  try {
32
32
  return await import("puppeteer");
33
33
  } catch {
34
- console.log("Installing puppeteer...");
35
- const { execSync } = await import("child_process");
36
- execSync("npm install --no-save puppeteer", { stdio: "inherit" });
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
  }
@@ -24,6 +24,16 @@ import { join } from "path";
24
24
  import { tmpdir } from "os";
25
25
  import { randomUUID } from "crypto";
26
26
 
27
+ // AD-46: refuse any value with embedded newlines before it lands in YAML or
28
+ // gitconfig. Real GitHub PATs / emails / names won't have them, but a server-
29
+ // pushed identity field with a stray \n could inject an extra config line.
30
+ function assertNoNewline(name, value) {
31
+ if (typeof value !== "string") return;
32
+ if (/[\r\n]/.test(value)) {
33
+ throw new Error(`Refusing to write scratch config: ${name} contains a newline`);
34
+ }
35
+ }
36
+
27
37
  export function createScratchHome({ projectId, sessionId, creds = {}, commitIdentity = {} }) {
28
38
  if (!creds.GITHUB_TOKEN) {
29
39
  // Caller is responsible for running session-preflight before reaching
@@ -31,6 +41,10 @@ export function createScratchHome({ projectId, sessionId, creds = {}, commitIden
31
41
  // fail loudly instead.
32
42
  throw new Error("createScratchHome called without GITHUB_TOKEN — preflight should have caught this");
33
43
  }
44
+ // AD-46: validate inputs before they're concatenated into structured files.
45
+ assertNoNewline("GITHUB_TOKEN", creds.GITHUB_TOKEN);
46
+ assertNoNewline("commitIdentity.name", commitIdentity.name);
47
+ assertNoNewline("commitIdentity.email", commitIdentity.email);
34
48
 
35
49
  const base = join(tmpdir(), "agentdesk-sessions", `${safe(projectId)}-${sessionId || randomUUID().slice(0, 8)}`);
36
50
  const ghConfigDir = join(base, "gh");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kendoo.agentdesk/agentdesk",
3
- "version": "0.25.0",
3
+ "version": "0.25.2",
4
4
  "description": "AI team orchestrator for Claude Code — run collaborative agent sessions from your terminal",
5
5
  "type": "module",
6
6
  "bin": {