{"record":{"id":"614e6c57748a0522","repo":"dagger/dagger","slug":"invalid-remove-path-q-absolute-paths-are-not-all","errorCode":null,"errorMessage":"invalid remove path %q: absolute paths are not allowed","messagePattern":"invalid remove path %q: absolute paths are not allowed","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"engine/client/filesync.go","lineNumber":345,"sourceCode":"\t}\n\n\tfor {\n\t\tmsg := filesync.BytesMessage{}\n\t\tif err := stream.RecvMsg(&msg); err != nil {\n\t\t\tif errors.Is(err, io.EOF) {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\t\tif _, err := destF.Write(msg.Data); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n}\n\nfunc safeLocalExportRemovePath(absRoot, removePath string) (string, error) {\n\tif filepath.IsAbs(removePath) || filepath.VolumeName(removePath) != \"\" {\n\t\treturn \"\", fmt.Errorf(\"invalid remove path %q: absolute paths are not allowed\", removePath)\n\t}\n\tif strings.Contains(removePath, `\\`) {\n\t\treturn \"\", fmt.Errorf(\"invalid remove path %q: backslashes are not allowed in diff paths\", removePath)\n\t}\n\n\tcleanRel := path.Clean(strings.TrimSuffix(removePath, \"/\"))\n\tif cleanRel == \".\" || cleanRel == \"..\" || strings.HasPrefix(cleanRel, \"../\") {\n\t\treturn \"\", fmt.Errorf(\"invalid remove path %q: escapes export root\", removePath)\n\t}\n\n\ttarget := filepath.Join(absRoot, filepath.FromSlash(cleanRel))\n\trel, err := filepath.Rel(absRoot, target)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"validate remove path %q: %w\", removePath, err)\n\t}\n\tif rel == \".\" || rel == \"..\" || strings.HasPrefix(rel, \"..\"+string(filepath.Separator)) || filepath.IsAbs(rel) {\n\t\treturn \"\", fmt.Errorf(\"invalid remove path %q: escapes export root\", removePath)\n\t}","sourceCodeStart":327,"sourceCodeEnd":363,"githubUrl":"https://github.com/dagger/dagger/blob/82ba2681dbe30d3547a1dc50ea495900ab5b6047/engine/client/filesync.go#L327-L363","documentation":"safeLocalExportRemovePath rejects remove paths that are absolute (or carry a Windows volume name) during local export diff sync. This is a path-traversal guard: deletes are only allowed for paths relative to the export root. The remote sync diff sent an absolute path, which the client refuses.","triggerScenarios":"A filesync remove/delete operation carries a removePath like \"/etc/passwd\" or \"C:\\\\tmp\\\\f\" (VolumeName non-empty) into safeLocalExportRemovePath via DiffCopy.","commonSituations":"A buggy or malicious remote engine sends absolute delete paths; a sync root configured with absolute-style diff paths; cross-OS sync where Windows drive letters leak into paths.","solutions":["Ensure the sync engine emits delete paths relative to the session root.","Strip the leading root prefix from paths before issuing remove operations.","Update the client/engine pair to matching versions so diff-path conventions agree."],"exampleFix":"// before\nremovePath := \"/home/dev/project/file.txt\"\n// after: make it relative to the export root\nremovePath = strings.TrimPrefix(removePath, root+\"/\")","handlingStrategy":"validation","validationCode":"func isSafeRemovePath(p string) bool {\n    return !filepath.IsAbs(p) && filepath.VolumeName(p) == \"\"\n}","typeGuard":null,"tryCatchPattern":"if _, err := safeLocalExportRemovePath(root, p); err != nil {\n    log.Printf(\"skipping unsafe remove %q: %v\", p, err)\n    return nil // or convert to a relative path and retry\n}","preventionTips":["Always emit diff remove paths relative to the session root.","Never construct remove paths by joining the root client-side.","Strip the root prefix on the sender before sending deletes."],"tags":["security","path-traversal","filesync"],"backgroundTag":"absolute-path-not-allowed","analyzedSha":"82ba2681dbe30d3547a1dc50ea495900ab5b6047","analyzedAt":"2026-09-05T07:21:37.930Z","contentChangedAt":"2026-09-05T07:21:37.930Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}