{"record":{"id":"f89adcbf0b66c378","repo":"helm/helm","slug":"path-contains-which-is-illegal-f89adc","errorCode":null,"errorMessage":"path contains '..', which is illegal","messagePattern":"path contains '\\.\\.', which is illegal","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/plugin/installer/extractor.go","lineNumber":106,"sourceCode":"//   - The path component `..` is considered suspicious, and therefore illegal\n//   - The character \\ (backslash) is treated as a path separator and is converted to /.\n//   - Beginning a path with a path separator is illegal\n//   - Rudimentary symlink protections are offered by SecureJoin.\nfunc cleanJoin(root, dest string) (string, error) {\n\t// On Windows, this is a drive separator. On UNIX-like, this is the path list separator.\n\t// In neither case do we want to trust a TAR that contains these.\n\tif strings.Contains(dest, \":\") {\n\t\treturn \"\", errors.New(\"path contains ':', which is illegal\")\n\t}\n\n\t// The Go tar library does not convert separators for us.\n\t// We assume here, as we do elsewhere, that `\\\\` means a Windows path.\n\tdest = strings.ReplaceAll(dest, \"\\\\\", \"/\")\n\n\t// We want to alert the user that something bad was attempted. Cleaning it\n\t// is not a good practice.\n\tif slices.Contains(strings.Split(dest, \"/\"), \"..\") {\n\t\treturn \"\", errors.New(\"path contains '..', which is illegal\")\n\t}\n\n\t// If a path is absolute, the creator of the TAR is doing something shady.\n\tif path.IsAbs(dest) {\n\t\treturn \"\", errors.New(\"path is absolute, which is illegal\")\n\t}\n\n\t// SecureJoin will do some cleaning, as well as some rudimentary checking of symlinks.\n\t// The directory needs to be cleaned prior to passing to SecureJoin or the location may end up\n\t// being wrong or returning an error. This was introduced in v0.4.0.\n\troot = filepath.Clean(root)\n\tnewpath, err := securejoin.SecureJoin(root, dest)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn filepath.ToSlash(newpath), nil\n}","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/helm/helm/blob/2a29f1770b62844b27197d2507377361d45ad7c0/internal/plugin/installer/extractor.go#L88-L124","documentation":"Thrown by cleanJoin (internal/plugin/installer/extractor.go:106) when a tar member name, after backslashes are normalized to '/', contains '..' as a path segment. Parent-directory traversal is the classic tar-slip attack, and Helm's policy is to reject (not clean) any path that looks like an escape attempt from the extraction directory.","triggerScenarios":"Installing a plugin archive (HTTP tarball via TarGzExtractor.Extract, local tarball, or OCI extractTar) whose member name contains a '..' segment, e.g. '../../etc/passwd' or 'pkg/../../escape'. A single header triggers the error and aborts the entire install.","commonSituations":"Hand-packed tarballs that include relative parent references; archives produced by tools that preserve '../../' fragments; malicious third-party plugin archives. Occasionally a repackaging script that walks above its root dir creates such entries accidentally.","solutions":["Repack from inside the plugin directory so member names never traverse upward: 'cd myplugin && tar -czf ../myplugin-1.0.0.tgz .'","Inspect entries first with 'tar -tzf file.tgz' and remove/relocate any entry containing '..' segments","If the archive came from a third party, do not attempt to bypass the check - the tarball is presumed malicious; obtain a clean artifact"],"exampleFix":"// before: entry '../../../plugin.yaml' or 'myplugin/../../x' in the archive\ntar -tzf bad.tgz   # shows ../.. entries\n\n// after: rebuild with only downward-relative paths\ncd myplugin && tar -czf ../myplugin-1.0.0.tgz .","handlingStrategy":"validation","validationCode":"func tarHasTraversal(path string) (bool, error) {\n\tf, err := os.Open(path)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer f.Close()\n\tgz, err := gzip.NewReader(f)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\ttr := tar.NewReader(gz)\n\tfor {\n\t\th, err := tr.Next()\n\t\tif err == io.EOF {\n\t\t\treturn false, nil\n\t\t}\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tnorm := strings.ReplaceAll(h.Name, \"\\\\\", \"/\")\n\t\tif slices.Contains(strings.Split(norm, \"/\"), \"..\") {\n\t\t\treturn true, nil\n\t\t}\n\t}\n}","typeGuard":null,"tryCatchPattern":"if err := installer.Install(i); err != nil {\n\tif strings.Contains(err.Error(), \"path contains '..'\") {\n\t\t// tar-slip attempt or bad packaging: reject the artifact entirely\n\t\treturn err\n\t}\n\treturn err\n}","preventionTips":["Never build archives by walking above the packaging root","Add a CI lint step that scans 'tar -tzf' output for '..' segments","Treat any archive that fails these checks as malicious, not as a packaging nuisance to auto-fix"],"tags":["security","tar","path-traversal","plugin","archive"],"backgroundTag":null,"analyzedSha":"2a29f1770b62844b27197d2507377361d45ad7c0","analyzedAt":"2026-08-15T22:02:47.490Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}