@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 +9 -0
- package/cli/login.mjs +5 -4
- package/cli/screenshot.mjs +21 -3
- package/cli/session-sandbox.mjs +14 -0
- package/package.json +1 -1
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`);
|
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
|
}
|
package/cli/session-sandbox.mjs
CHANGED
|
@@ -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");
|