{"record":{"id":"29753f132542ac5b","repo":"twpayne/chezmoi","slug":"s-s-relative-path-outside-target-directory","errorCode":null,"errorMessage":"%s: %s: relative path outside target directory","messagePattern":"(.+?): (.+?): relative path outside target directory","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/chezmoi/sourcestate.go","lineNumber":1468,"sourceCode":"\t\tif external.TargetPath != \"\" {\n\t\t\ttargetPath = external.TargetPath\n\t\t}\n\t\tif targetPath == \"\" {\n\t\t\treturn fmt.Errorf(\"%s: empty path\", sourceAbsPath)\n\t\t}\n\n\t\texternalPath := path.Clean(targetPath)\n\t\tif strings.HasPrefix(externalPath, \"/\") || filepath.IsAbs(externalPath) {\n\t\t\treturn fmt.Errorf(\"%s: %s: path is not relative\", sourceAbsPath, targetPath)\n\t\t}\n\t\tswitch relPath, err := filepath.Rel(\".\", externalPath); {\n\t\tcase err != nil:\n\t\t\treturn fmt.Errorf(\"%s: %s: %w\", sourceAbsPath, targetPath, err)\n\t\tcase relPath == \".\":\n\t\t\treturn fmt.Errorf(\"%s: %s: empty relative path\", sourceAbsPath, targetPath)\n\t\tcase relPath == \"..\", strings.HasPrefix(relPath, \"../\"),\n\t\t\truntime.GOOS == \"windows\" && strings.HasPrefix(relPath, \"..\"+string(filepath.Separator)):\n\t\t\treturn fmt.Errorf(\"%s: %s: relative path outside target directory\", sourceAbsPath, targetPath)\n\t\t}\n\t\ttargetRelPath := parentTargetSourceRelPath.JoinString(externalPath)\n\t\texternal.sourceAbsPath = sourceAbsPath\n\t\ts.externals[targetRelPath] = append(s.externals[targetRelPath], &external)\n\t}\n\treturn nil\n}\n\n// addExternalDir adds all externals in externalsDirAbsPath to s.\nfunc (s *SourceState) addExternalDir(ctx context.Context, externalsDirAbsPath AbsPath) error {\n\twalkFunc := func(ctx context.Context, externalAbsPath AbsPath, fileInfo fs.FileInfo, err error) error {\n\t\tif externalAbsPath == externalsDirAbsPath {\n\t\t\treturn nil\n\t\t}\n\t\tif err == nil && fileInfo.Mode().Type() == fs.ModeSymlink {\n\t\t\tfileInfo, err = s.system.Stat(externalAbsPath)\n\t\t}\n\t\tswitch {","sourceCodeStart":1450,"sourceCodeEnd":1486,"githubUrl":"https://github.com/twpayne/chezmoi/blob/f901167e4685db90da56d6a2a19df642cb3e0247/internal/chezmoi/sourcestate.go#L1450-L1486","documentation":"chezmoi rejects an external entry whose path escapes the destination (target) directory. filepath.Rel(\".\", externalPath) produced \"..\" or a \"../\"-prefixed path (including Windows backslash separator variants), meaning the external would be written outside chezmoi's target tree. This is a safety check against path traversal.","triggerScenarios":"An external entry target path containing \"..\" segments, or an absolute path that relativizes to something outside \".\" when processed in sourceStateExternal walk of internal/chezmoi/sourcestate.go.","commonSituations":"Typo'd target paths like \"../scripts/tool.sh\", symlinked or templated paths that resolve above the destination directory, users trying to install files into locations outside the target with an external.","solutions":["Remove \"..\" components from the external's target path so it stays under the destination directory","Install the file outside the target using a separate mechanism (e.g. a script) instead of an external","Check symlinked paths to ensure they do not resolve outside the target"],"exampleFix":"# before\n[\"../bin/tool\"]\n  type = \"file\"\n  url = \"https://example.com/tool\"\n# after\n[\".local/bin/tool\"]\n  type = \"file\"\n  url = \"https://example.com/tool\"","handlingStrategy":"validation","validationCode":"rel, err := filepath.Rel(\".\", externalPath)\nif err != nil || rel == \".\" || rel == \"..\" || strings.HasPrefix(rel, \"../\") {\n    return fmt.Errorf(\"external path %q must stay inside the target directory\", externalPath)\n}","typeGuard":"func insideTarget(p string) bool {\n    rel, err := filepath.Rel(\".\", filepath.Clean(p))\n    return err == nil && rel != \".\" && rel != \"..\" && !strings.HasPrefix(rel, \"../\")\n}","tryCatchPattern":null,"preventionTips":["Never use \"..\" in external target paths","Clean and relativize paths from templates before writing them into external definitions"],"tags":["path","path-traversal","external"],"backgroundTag":"path-outside-target-directory","analyzedSha":"f901167e4685db90da56d6a2a19df642cb3e0247","analyzedAt":"2026-09-01T18:16:41.508Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T01:17:15.007Z"}