T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/vendor/cli.mjs:9716
- Finding
- Unauthenticated Local Rendering Endpoints Expose Document Data and Permit Output Corruption<![CDATA[ ## Vulnerability Details **File Location**: `scripts/vendor/cli.mjs:9716-9758` **Vulnerability Type**: Unauthenticated loopback HTTP endpoints **Risk Level**: Medium ### Vulnerable Code ```js server.on("request", async (req, res) => { try { const requestUrl = new URL(req.url ?? "/", "http://127.0.0.1"); if (req.method === "GET" && requestUrl.pathname === "/favicon.ico") { res.writeHead(204, noStoreHeaders()); res.end(); return; } if (req.method === "GET" && requestUrl.pathname === "/") { sendText(res, 200, buildHtmlPage(), "text/html; charset=utf-8"); return; } if (req.method === "GET" && requestUrl.pathname === "/renderer.js") { sendText(res, 200, rendererJs, "text/javascript; charset=utf-8"); return; } if (req.method === "GET" && requestUrl.pathname === "/job") { sendJson(res, 200, payload); return; } if (req.method === "POST" && requestUrl.pathname === "/result") { const bytes = await readRequestBytes(req); const mimeType = String(req.headers["content-type"] ?? "").trim() || (payload.format === "svg" ? "image/svg+xml" : "image/png"); await mkdir2(path4.dirname(outputPath), { recursive: true }); await writeFile2(outputPath, bytes); const done = { schema: "kmind-cli-render-session@v1", status: "done", format: payload.format, outputPath, byteLength: bytes.byteLength, mimeType }; sendJson(res, 200, done); settleResult?.({ kind: "success", done }); return; } if (req.method === "POST" && requestUrl.pathname === "/failure") { const bytes = await readRequestBytes(req); const message = Buffer.from(bytes).toString("utf8").trim() || "Browser render failed."; sendJson(res, 200, { ok: true }); settleResult?.({ kind: "failure", error: new CliUserError(message, 1) }); return; } send ...[truncated 3088 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Generate a cryptographically random capability token for each render session, for example using `crypto.randomBytes(32)`. 2. Include the token in the launched browser URL or deliver it through another protected per-session mechanism. 3. Require the token on `/job`, `/result`, and `/failure`, and compare it using a timing-safe comparison where appropriate. 4. Reject requests with unexpected `Host`, `Origin`, or `Sec-Fetch-Site` values. Treat these checks as defense in depth rather than a replacement for authentication. 5. Accept only one authenticated result and disable all job endpoints immediately after the session settles. 6. Validate the submitted result against the requested format: - Require and verify the PNG signature for PNG output. - Parse and validate SVG output, rejecting scripts, event-handler attributes, external references, and other active content if portable SVG is expected. 7. Do not trust the request-supplied `Content-Type`; derive the stored type from the requested output format. 8. Minimize `/job` contents to data strictly required by the renderer. ]]>
