vercel/turborepo · error · Error

Invalid : path must be an absolute, non-empty string…

Error message

Invalid ${description}: path must be an absolute, non-empty string without NUL or newline characters and cannot start with "-"

What it means

A defensive guard (assertSafeGitArgument) in @turbo-utils/examples.ts that validates any path handed to a git subprocess. It rejects values that are empty, non-strings, contain NUL or newline bytes, start with '-', or are not absolute filesystem paths. This blocks argument injection (e.g. '--upload-pack=...') and option smuggling into git commands.

Solutions

  1. Pass an absolute path: wrap the root with path.resolve(root) before calling the API
  2. If the path comes from user input, validate/normalize it before use (reject NUL/newlines/'-' prefixes)
  3. Ensure you are not passing an empty string for the root directory
  4. Check for stray control characters if the path was assembled from env vars or config files

Example fix

// before
import { downloadAndExtractExample } from "@turbo-utils/examples";
await downloadAndExtractExample(".", "basic"); // throws: relative path

// after
import { resolve } from "node:path";
import { downloadAndExtractExample } from "@turbo-utils/examples";
await downloadAndExtractExample(resolve("."), "basic");
Defensive patterns

Strategy: validation

Validate before calling

import { isAbsolute, resolve } from "node:path";

function toSafeGitPath(root: string): string {
  const abs = isAbsolute(root) ? root : resolve(root);
  if (abs.includes("\0") || abs.includes("\n") || abs.startsWith("-")) {
    throw new Error(`Unsafe root path: ${JSON.stringify(root)}`);
  }
  return abs;
}
// await downloadAndExtractExample(toSafeGitPath(root), name);

Type guard

function isSafeGitArgument(value: string): boolean {
  return (
    value.length > 0 &&
    isAbsolute(value) &&
    !value.includes("\0") &&
    !value.includes("\n") &&
    !value.startsWith("-")
  );
}

Prevention

When it happens

Trigger: Calling downloadAndExtractExample(root, name) with a relative root (like '.' or 'my-app'), an empty root, or a root string containing \0 or \n or leading with '-'. The resolved project root and the derived .turbo-clone-temp directory are both validated before runGit executes.

Common situations: Scripts calling createProject/downloadAndExtractExample with process.cwd() assumed absolute (it can be relative via execa cwd), Windows paths that were not resolved, user-supplied install targets taken straight from argv, or malicious/odd input passed as a destination.

Related errors


AI-assisted analysis of vercel/turborepo@f9245100cf (2026-08-17). Data as JSON: /api/errors/398806495de86eb2. Report an issue: GitHub.

Appendix: source

Thrown at packages/turbo-utils/src/examples.ts:493

/**
 * Validates that a path is safe to use as a git CLI argument.
 * Prevents argument injection by rejecting paths that:
 * - Are empty or not a string
 * - Contain NUL bytes (could truncate the argument)
 * - Start with "-" (could be interpreted as a git option)
 * - Are not absolute filesystem paths (helps ensure they cannot be mistaken
 *   for URLs or additional git options like "--upload-pack")
 */
function assertSafeGitArgument(value: string, description: string): void {
  if (
    !value ||
    typeof value !== "string" ||
    value.includes("\0") ||
    value.includes("\n") ||
    value.startsWith("-") ||
    !isAbsolute(value)
  ) {
    throw new Error(
      `Invalid ${description}: path must be an absolute, non-empty string without NUL or newline characters and cannot start with "-"`
    );
  }
}

function formatError(error: unknown): string {
  if (error && typeof error === "object" && "stderr" in error) {
    const { stderr } = error as { stderr?: unknown };
    if (typeof stderr === "string" || Buffer.isBuffer(stderr)) {
      const output = stderr.toString().trim();
      if (output) {
        return output;
      }
    }
  }

  return error instanceof Error ? error.message : String(error);
}

View on GitHub (pinned to f9245100cf)