{"record":{"id":"a79efd995acec9f9","repo":"argoproj/argo-workflows","slug":"illegal-file-path-after-symlink-resolution-s-res","errorCode":null,"errorMessage":"illegal file path after symlink resolution: %s resolves outside destination","messagePattern":"illegal file path after symlink resolution: (.+?) resolves outside destination","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"workflow/executor/executor.go","lineNumber":1186,"sourceCode":"\t\t\t\t// Before writing the file, check if the parent directory resolves outside dest\n\t\t\t\tparentDir := filepath.Dir(target)\n\n\t\t\t\t// Resolve the destination directory\n\t\t\t\tresolvedDest, err := filepath.EvalSymlinks(dest)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\t// Check if parent exists and if so, verify it doesn't resolve outside dest\n\t\t\t\tif _, lstatErr := os.Lstat(parentDir); lstatErr == nil {\n\t\t\t\t\t// Parent exists, resolve it to check for symlink traversal\n\t\t\t\t\tresolvedParent, evalErr := filepath.EvalSymlinks(parentDir)\n\t\t\t\t\tif evalErr != nil {\n\t\t\t\t\t\treturn evalErr\n\t\t\t\t\t}\n\t\t\t\t\t// Check if resolved parent is outside dest\n\t\t\t\t\tif !strings.HasPrefix(resolvedParent+string(os.PathSeparator), resolvedDest+string(os.PathSeparator)) && resolvedParent != resolvedDest {\n\t\t\t\t\t\treturn fmt.Errorf(\"illegal file path after symlink resolution: %s resolves outside destination\", header.Name)\n\t\t\t\t\t}\n\t\t\t\t} else if !os.IsNotExist(lstatErr) {\n\t\t\t\t\treturn lstatErr\n\t\t\t\t} else {\n\t\t\t\t\t// Parent doesn't exist, create it\n\t\t\t\t\tif mkdirErr := os.MkdirAll(parentDir, 0o755); mkdirErr != nil {\n\t\t\t\t\t\treturn mkdirErr\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tf, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif _, err := io.Copy(f, tr); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif err := f.Close(); err != nil {","sourceCodeStart":1168,"sourceCodeEnd":1204,"githubUrl":"https://github.com/argoproj/argo-workflows/blob/35bff19146f5a6ada77468c431f2624bd577e373/workflow/executor/executor.go#L1168-L1204","documentation":"For regular files/dirs in the tar, the executor evaluates any existing symlinked parent directories (filepath.EvalSymlinks on the parent path) and confirms the resolved location is still inside the destination. If the physical parent resolves outside dest — meaning a previously extracted symlink redirects the write — extraction aborts with this error.","triggerScenarios":"Writing a tar member under a path whose parent directory chain traverses a symlink created earlier in the same archive (or pre-existing in dest) that points outside the extraction root, so the final write would land outside dest even though the joined name looked safe.","commonSituations":"Multi-stage attacks where an archive first extracts a symlink like 'sub -> /tmp' and then writes 'sub/file' (path 'dest/sub/file' passes prefix check but resolves to /tmp/file); archives recombining paths from multiple sources.","solutions":["Repackage the artifact without symlinks in directory positions (use real directories)","Inspect the archive (tar -tvf) for symlink members before loading it","Extract to a fresh, empty destination so no pre-existing symlinks can redirect paths","Treat artifacts from untrusted producers as hostile and re-verify contents"],"exampleFix":"# before (archive: 'sub' -> /tmp symlink, then 'sub/file')\n# after (archive uses real directory)\nmkdir sub && cp file sub/ && tar -C root -cf out.tar .","handlingStrategy":"validation","validationCode":"// Detect archives where directory-position symlinks could redirect writes:\n// tar -tvf artifact.tar | awk '$1 ~ /^l/ {print}'   # review all symlinks\n// Prefer archives with no symlinks in directory positions","typeGuard":null,"tryCatchPattern":"if err := loadArtifacts(ctx); err != nil {\n\tif strings.Contains(err.Error(), \"resolves outside destination\") {\n\t\t// symlinked parent redirects writes: reject/repackage the archive\n\t}\n}","preventionTips":["Avoid symlinks in directory positions inside distributed tarballs","Extract to a fresh empty directory to avoid pre-existing symlinks","Review symlink members of untrusted archives before loading","Keep executor updated — this EvalSymlinks check is a recent hardening"],"tags":["security","artifacts","symlink","tar","path-traversal"],"backgroundTag":"zip-slip-path-traversal","analyzedSha":"35bff19146f5a6ada77468c431f2624bd577e373","analyzedAt":"2026-09-03T19:34:35.908Z","contentChangedAt":"2026-09-03T19:34:35.908Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}