{"record":{"id":"86d8c4e25a3bbeb2","repo":"vercel/turborepo","slug":"invalid-example-name-name","errorCode":null,"errorMessage":"Invalid example name: ${name}","messagePattern":"Invalid example name: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/turbo-utils/src/examples.ts","lineNumber":540,"sourceCode":"  try {\n    rmSync(tempDir, {\n      recursive: true,\n      force: true,\n      maxRetries: 5,\n      retryDelay: 100\n    });\n  } catch (error) {\n    warn(\n      `Unable to remove temporary directory ${tempDir}:\\n${formatError(error)}`\n    );\n  }\n}\n\nexport async function downloadAndExtractExample(root: string, name: string) {\n  // Validate example name to prevent path traversal and argument injection\n  // Only allow alphanumeric characters, hyphens, and underscores\n  if (!name || !/^[a-zA-Z0-9_-]+$/.test(name)) {\n    throw new Error(`Invalid example name: ${name}`);\n  }\n\n  // Normalize and validate the root directory to prevent unsafe git arguments\n  const normalizedRoot = resolve(root);\n  assertSafeGitArgument(normalizedRoot, \"project root\");\n\n  const tempDir = join(normalizedRoot, \".turbo-clone-temp\");\n  assertSafeGitArgument(tempDir, \"temporary directory\");\n\n  try {\n    // Clone with partial clone (no blobs) and no checkout\n    runGit([\n      \"clone\",\n      \"--filter=blob:none\",\n      \"--no-checkout\",\n      \"--depth\",\n      \"1\",\n      \"--sparse\",","sourceCodeStart":522,"sourceCodeEnd":558,"githubUrl":"https://github.com/vercel/turborepo/blob/9f94a7d215b3881942527dd526afa3fc650869d5/packages/turbo-utils/src/examples.ts#L522-L558","documentation":"downloadAndExtractExample() validates the example name against /^[a-zA-Z0-9_-]+$/ before doing any work, explicitly to block path traversal (../) and git argument injection. Empty names, or names containing dots, slashes, spaces, or any character outside letters/digits/hyphen/underscore, are rejected.","triggerScenarios":"Calling downloadAndExtractExample(root, name) with values like 'examples/basic', '../basic', 'basic.', 'my example', '' — anything failing the strict charset regex. Note that dots are rejected, so even plausible-looking names with '.v2' suffixes fail.","commonSituations":"Users pasting a path from the docs (examples/with-tailwind) instead of the bare name; scripts interpolating unsanitized user input; trailing dots or spaces from copy-paste.","solutions":["Pass the bare example directory name only: 'basic', 'with-vite', not a path","Normalize input before calling: name.trim().split('/').pop()","For arbitrary repos or nested paths, use downloadAndExtractRepo with a repo shorthand instead"],"exampleFix":"// before\nawait downloadAndExtractExample(root, 'examples/basic'); // throws\n\n// after\nawait downloadAndExtractExample(root, 'basic');","handlingStrategy":"validation","validationCode":"const EXAMPLE_NAME = /^[a-zA-Z0-9_-]+$/;\n\nfunction normalizeExampleName(input: string): string {\n  return input.trim().split(\"/\").pop() ?? \"\";\n}\n\nconst name = normalizeExampleName(userInput);\nif (!EXAMPLE_NAME.test(name)) throw new Error(`Unsupported example name: ${userInput}`);","typeGuard":"function isValidExampleName(name: string): boolean {\n  return /^[a-zA-Z0-9_-]+$/.test(name);\n}","tryCatchPattern":"try {\n  await downloadAndExtractExample(root, name);\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith(\"Invalid example name:\")) {\n    // re-prompt with a sanitized name or list valid examples\n  } else throw e;\n}","preventionTips":["Accept bare directory names in CLIs; strip 'examples/' prefixes from user input","Validate against the same [a-zA-Z0-9_-] charset in your own tooling","Never pass free-form user input as the example name without the regex check"],"tags":["validation","path-traversal","security","scaffolding"],"backgroundTag":"path-traversal-blocked","analyzedSha":"9f94a7d215b3881942527dd526afa3fc650869d5","analyzedAt":"2026-08-16T19:47:29.531Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}