MCPcopy Create free account
hub / github.com/acoyfellow/cloudbox / validateRun

Function validateRun

src/http.ts:328–353  ·  view source on GitHub ↗
(input: ContainerRunRequest | null)

Source from the content-addressed store, hash-verified

326 const commands = [...(input.commands ?? []), ...(input.verify ?? [])];
327 if (commands.length > 4) return false;
328 if (commands.some((cmd) => !/^(echo |test |pwd$|ls( |$)|node --version$|npm --version$|pnpm --version$|bun --version$)/.test(cmd))) return false;
329 // Reject shell metacharacters that could chain into other commands. Demo runs
330 // are sandboxed to a curated allow-list; block obvious injection attempts so
331 // a permissive prefix like `echo ` cannot smuggle additional commands.
332 // A single `echo ... > HANDOFF.md` is allowed so the hosted demo can create
333 // the artifact it returns, but arbitrary redirection remains blocked.
334 if (commands.some((cmd) => /[;&|`$\n\r\\]/.test(cmd))) return false;
335 if (commands.some((cmd) => /[<>]/.test(cmd) && !/^echo [A-Za-z0-9 _.,:-]+ > HANDOFF\.md$/.test(cmd))) return false;
336 return !!input.repo && /^https:\/\/github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\/?$/.test(input.repo);
337}
338
339function validateRun(input: ContainerRunRequest | null): Response | null {
340 if (!input || typeof input !== "object") return jsonErrorResponse(400, "bad_run", "expected JSON body");
341 if (!input.repo || typeof input.repo !== "string") return jsonErrorResponse(400, "bad_run", "repo is required");
342 const isGithub = /^https:\/\/github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\/?$/.test(input.repo);
343 const isGitlab = /^https:\/\/gitlab\.cfdata\.org\/[A-Za-z0-9_./-]+(?:\.git)?$/.test(input.repo);
344 if (!isGithub && !(input.auth === "gitlab" && isGitlab)) return jsonErrorResponse(400, "bad_run", "repo must be a public GitHub URL or a gitlab.cfdata.org URL with auth=gitlab");
345 if (input.auth !== undefined && input.auth !== "none" && input.auth !== "gitlab") return jsonErrorResponse(400, "bad_run", "auth must be none or gitlab");
346 if (input.clone !== undefined && input.clone !== "shallow" && input.clone !== "blobless") return jsonErrorResponse(400, "bad_run", "clone must be shallow or blobless");
347 if (input.sparse !== undefined && (!Array.isArray(input.sparse) || input.sparse.length > 64 || input.sparse.some((path) => typeof path !== "string" || !path || path.length > 240 || path.startsWith("/") || path.split("/").includes("..") || /[\n\r`$\\]/.test(path)))) return jsonErrorResponse(400, "bad_run", "sparse must be safe relative paths");
348 if (input.ref !== undefined && (typeof input.ref !== "string" || input.ref.length > 120 || /[^A-Za-z0-9_./-]/.test(input.ref))) return jsonErrorResponse(400, "bad_run", "ref must be a short git ref");
349 for (const key of ["commands", "verify"] as const) {
350 const list = input[key];
351 if (list !== undefined && !Array.isArray(list)) return jsonErrorResponse(400, "bad_run", `${key} must be an array`);
352 if (list && list.length > 12) return jsonErrorResponse(400, "bad_run", `${key} has too many commands`);
353 if (list?.some((cmd) => typeof cmd !== "string" || cmd.length > 1_000)) return jsonErrorResponse(400, "bad_run", `${key} contains an invalid command`);
354 }
355 if (!input.commands?.length && !input.verify?.length) return jsonErrorResponse(400, "bad_run", "at least one command or verify command is required");
356 if (input.artifact !== undefined && (typeof input.artifact !== "string" || input.artifact.length > 240)) return jsonErrorResponse(400, "bad_run", "artifact must be a short relative path");

Callers 1

http.tsFile · 0.70

Calls 1

jsonErrorResponseFunction · 0.90

Tested by

no test coverage detected