comfyui-mcp
Version:
Local-first, agent-native control plane for ComfyUI — MCP server + autonomous sidebar agent that drives your live graph in natural language on ANY LLM: Claude/ChatGPT/Gemini on your subscription (no API key), free local models via Ollama (fully offline),
58 lines • 2.95 kB
JavaScript
import { ValidationError } from "../utils/errors.js";
const CONTROL_CHARS_RE = /[\x00-\x1f\x7f]/;
const PERCENT_ESCAPE_RE = /%[0-9a-f]{2}/i;
const ENCODED_SEPARATOR_RE = /%(?:2f|5c)/i;
const MAX_PERCENT_DECODE_PASSES = 8;
function decodeWorkflowFilenameForValidation(filename) {
let decoded = filename;
for (let pass = 0; pass <= MAX_PERCENT_DECODE_PASSES; pass++) {
// ComfyUI's userdata route can decode the path after the request URL has
// already been decoded once by the HTTP framework. Inspect every layer so
// a caller cannot smuggle a separator through as `%252f`/`%255c`.
if (ENCODED_SEPARATOR_RE.test(decoded)) {
throw new ValidationError(`Invalid workflow filename "${filename}": percent-encoded path separators are not allowed.`);
}
if (!PERCENT_ESCAPE_RE.test(decoded))
return decoded;
let next;
try {
next = decodeURIComponent(decoded);
}
catch {
throw new ValidationError(`Invalid workflow filename "${filename}": malformed percent-encoding is not allowed.`);
}
if (next === decoded)
return decoded;
decoded = next;
}
throw new ValidationError(`Invalid workflow filename "${filename}": too many layers of percent-encoding.`);
}
/**
* Return the canonical filename used by the ComfyUI workflow library.
*
* The library only exposes JSON workflows in its browser, recursive listing,
* and autoload paths. A caller may omit the extension, but the name sent to
* ComfyUI must still end in `.json`. The original spelling and case are kept
* so existing names such as `VIDEO/Clip.JSON` remain addressable.
*/
export function normalizeWorkflowFilename(filename) {
if (typeof filename !== "string" || filename.trim().length === 0) {
throw new ValidationError("Workflow filename must be a non-empty library-relative path.");
}
const decoded = decodeWorkflowFilenameForValidation(filename);
if (CONTROL_CHARS_RE.test(decoded)) {
throw new ValidationError("Workflow filename must not contain control characters or NUL bytes.");
}
if (decoded.includes("\\")) {
throw new ValidationError(`Invalid workflow filename "${filename}": use forward slashes, not backslashes.`);
}
if (decoded.startsWith("/") || /^[A-Za-z]:/.test(decoded) || decoded.includes(":")) {
throw new ValidationError(`Invalid workflow filename "${filename}": it must be relative to ComfyUI's workflows directory.`);
}
const segments = decoded.split("/");
if (segments.some((segment) => segment.length === 0 || segment === "." || segment === "..")) {
throw new ValidationError(`Invalid workflow filename "${filename}": empty, ".", and ".." path segments are not allowed.`);
}
return filename.toLowerCase().endsWith(".json") ? filename : `${filename}.json`;
}
//# sourceMappingURL=workflow-filename.js.map