{"record":{"id":"a1d6bd2e3e281965","repo":"microsoft/aspire","slug":"symlink-entry-name-targets-entry-linkname-which-resolves","errorCode":null,"errorMessage":"Symlink '{entry.Name}' targets '{entry.LinkName}' which resolves outside the destination directory.","messagePattern":"Symlink '(.+?)' targets '(.+?)' which resolves outside the destination directory\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Aspire.Cli/Bundles/BundleService.cs","lineNumber":932,"sourceCode":"\n                    // Preserve Unix file permissions from tar entry (e.g., execute bit)\n                    if (!environment.IsWindows() && entry.Mode != default)\n                    {\n                        File.SetUnixFileMode(fullPath, (UnixFileMode)entry.Mode);\n                    }\n                    break;\n\n                case TarEntryType.SymbolicLink:\n                    if (string.IsNullOrEmpty(entry.LinkName))\n                    {\n                        continue;\n                    }\n                    // Validate symlink target stays within the extraction directory\n                    var linkTarget = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(fullPath)!, entry.LinkName));\n                    if (!linkTarget.StartsWith(normalizedDestination + Path.DirectorySeparatorChar, StringComparison.Ordinal) &&\n                        !linkTarget.Equals(normalizedDestination, StringComparison.Ordinal))\n                    {\n                        throw new InvalidOperationException($\"Symlink '{entry.Name}' targets '{entry.LinkName}' which resolves outside the destination directory.\");\n                    }\n                    var linkDir = Path.GetDirectoryName(fullPath);\n                    if (linkDir is not null)\n                    {\n                        Directory.CreateDirectory(linkDir);\n                    }\n                    if (File.Exists(fullPath))\n                    {\n                        File.Delete(fullPath);\n                    }\n                    File.CreateSymbolicLink(fullPath, entry.LinkName);\n                    break;\n            }\n        }\n    }\n}\n","sourceCodeStart":914,"sourceCodeEnd":949,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Cli/Bundles/BundleService.cs#L914-L949","documentation":"BundleService throws this while extracting a bundle archive when a symlink entry would resolve to a path outside the destination directory. It is a zip-slip style path traversal guard: after resolving the link target relative to the link's directory, the full path must remain inside the normalized destination. Throwing prevents an attacker-crafted bundle from writing files outside the extraction root.","triggerScenarios":"Extracting a bundle containing a symlink whose LinkName is absolute, or contains '..' segments, or otherwise resolves (via Path.GetFullPath) to a path outside normalizedDestination.","commonSituations":"Using a hand-edited or third-party bundle archive; bundles repacked with symlinks pointing to shared libraries outside the extraction dir; bundles moved between machines where relative link targets resolve differently.","solutions":["Fix the bundle so symlink targets are relative and resolve inside the destination directory","Inspect the bundle entries with an archive tool to find the offending symlink (entry.Name -> entry.LinkName)","If you own bundle creation, rewrite absolute/parent-escaping link targets to relative paths within the archive"],"exampleFix":"// before (in bundle manifest): symlink target escaping root\nentry.LinkName = \"../../../../etc/passwd\"\n// after: relative target inside destination\nentry.LinkName = \"../lib/foo.so\"","handlingStrategy":"validation","validationCode":"var resolved = Path.GetFullPath(Path.Combine(dir, linkName));\nif (!resolved.StartsWith(destRoot + Path.DirectorySeparatorChar) && resolved != destRoot)\n    throw new InvalidOperationException(\"Symlink escapes destination directory\");","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Only extract bundles from trusted sources","Audit symlink entries in archives before extraction","Keep link targets relative and inside the archive root"],"tags":["security","path-traversal","symlink","extraction"],"backgroundTag":"path-traversal-blocked","analyzedSha":"25830f84bd145686607ad00c057b3f84e2e51d43","analyzedAt":"2026-09-16T11:10:06.193Z","contentChangedAt":"2026-09-16T11:10:06.193Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}