vercel/turborepo · warning
Blocked symlink: ${entry.path}
Error message
Blocked symlink: ${entry.path} What it means
During streaming tarball extraction (streamingExtract for `create-turbo --example`), every tar entry is checked: after Zip-Slip path validation, entries whose type is a symlink or hard link (isLinkEntry) are refused. This blocks the classic tar-symlink attack where a link entry points outside the extraction root and a later entry writes through it. The entry is skipped (entry.resume()) and extraction continues; the cost is that any legitimately symlinked file in the example will be missing from the generated project.
Source
Thrown at packages/turbo-utils/src/examples.ts:385
const pathParts = entry.path.split("/");
const strippedPath = pathParts.slice(strip).join("/");
if (!strippedPath) {
entry.resume();
return;
}
// Validate the path stays within the target directory (Zip Slip protection)
// Pass pre-resolved root for performance
if (!isPathSafe(root, strippedPath, resolvedRoot)) {
error(`Blocked path traversal attempt: ${entry.path}`);
entry.resume();
return;
}
// Block symlinks and hard links to prevent symlink attacks
if (entry.type && isLinkEntry(entry.type)) {
warn(`Blocked symlink: ${entry.path}`);
entry.resume();
return;
}
const destPath = resolve(resolvedRoot, strippedPath);
if (entry.type === "Directory") {
if (!createdDirs.has(destPath)) {
mkdirSync(destPath, { recursive: true });
createdDirs.add(destPath);
}
entry.resume();
} else if (entry.type === "File") {
const dirPath = dirname(destPath);
if (!createdDirs.has(dirPath)) {
mkdirSync(dirPath, { recursive: true });
createdDirs.add(dirPath);
}View on GitHub (pinned to f9245100cf)
Solutions
- Check whether the example really contains symlinks: browse github.com/vercel/turborepo/tree/main/examples/<name> or `git clone` the repo and run `git ls-tree -r examples/<name>` (mode 120000 = symlink).
- If the symlink is essential to the example, scaffold manually via git and re-add the link yourself: `git clone --depth 1 --filter=blob:none --sparse https://github.com/vercel/turborepo.git && git sparse-checkout set examples/<name>`.
- Commit the previously-symlinked content as a real file (or a postinstall script that recreates the link) in the example so the tarball path works for everyone.
- If you did not expect any symlink, treat it as a supply-chain red flag: verify the codeload URL/SHA you are fetching and report it to the Turborepo maintainers.
Example fix
# before: example repo has a symlink examples/my-app/shared-config -> ../../shared/config # after: commit the file so tarball extraction includes it examples/my-app/shared-config # regular file with the config contents
Defensive patterns
Strategy: validation
Validate before calling
// Before scaffolding, ask GitHub's tree API whether the example contains symlinks (mode 120000)
async function exampleHasSymlinks(example: string): Promise<boolean> {
const res = await fetch(
`https://api.github.com/repos/vercel/turborepo/git/trees/main?recursive=1`
);
const { tree } = (await res.json()) as { tree: Array<{ path: string; mode: string }> };
return tree.some((e) => e.path.startsWith(`examples/${example}/`) && e.mode === "120000");
} Type guard
// Mirror of the library's own guard, usable on tar entries you process yourself
import type { ReadEntry } from "tar";
function isLinkEntry(entryType: string): boolean {
return entryType === "SymbolicLink" || entryType === "Link";
}
function isSafeEntry(entry: ReadEntry): boolean {
return !isLinkEntry(entry.type);
} Prevention
- Don't commit symlinks inside examples/ of the upstream repo — ship real files or a postinstall script that recreates links.
- After scaffolding with --example, diff the generated tree against github.com/vercel/turborepo/tree/main/examples/<name> to spot silently skipped link entries.
- If you maintain your own tar serving, strip or materialize link entries so consumers of streamingExtract never hit the guard.
When it happens
Trigger: The codeload.github.com tarball for vercel/turborepo (main) contains an entry of type 'SymbolicLink' or 'Link' inside examples/<name>/ that passes the path-safety and filter checks — i.e. the example's directory in the repo actually contains a git symlink — so the guard fires and the file is not materialized.
Common situations: A Turborepo example adds a symlink for shared config or a yarn/npm workspace alias; after scaffolding, the created project fails or behaves oddly because the symlinked file is absent; malicious or proxy-substituted tarballs attempting symlink-based extraction attacks.
Related errors
- Directory path contains potentially unsafe characters: ${dir
- Invalid ${description}: path must be an absolute, non-empty
- refusing to write structured log to symlink: {}
- Unable to write .gitignore
- Unable to read package.json
AI-assisted analysis of vercel/turborepo@f9245100cf (2026-08-17).
Data as JSON: /api/errors/6cab0b7d0bdd5abb.
Report an issue: GitHub.