{"record":{"id":"5114f6e32ff822eb","repo":"crowdsecurity/crowdsec","slug":"cannot-copy-a-folder-onto-itself","errorCode":null,"errorMessage":"cannot copy a folder onto itself","messagePattern":"cannot copy a folder onto itself","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/hubtest/utils.go","lineNumber":62,"sourceCode":"}\n\n// checkPathNotContained returns an error if 'subpath' is inside 'path'\nfunc checkPathNotContained(path string, subpath string) error {\n\tabsPath, err := filepath.Abs(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tabsSubPath, err := filepath.Abs(subpath)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcurrent := absSubPath\n\n\tfor {\n\t\tif current == absPath {\n\t\t\treturn errors.New(\"cannot copy a folder onto itself\")\n\t\t}\n\n\t\tup := filepath.Dir(current)\n\t\tif current == up {\n\t\t\tbreak\n\t\t}\n\n\t\tcurrent = up\n\t}\n\n\treturn nil\n}\n\n// CopyDir copies the content of a directory to another directory.\n// It delegates the operation to os.CopyFS with an additional check to prevent infinite loops.\nfunc CopyDir(src string, dest string) error {\n\tif err := checkPathNotContained(src, dest); err != nil {\n\t\treturn err","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/crowdsecurity/crowdsec/blob/909b5157986a2b2c2163300fdaef5ed01289f7d2/pkg/hubtest/utils.go#L44-L80","documentation":"checkPathNotContained walks up from the destination path; if the source absolute path is an ancestor of (or equal to) the destination, copying would move a folder into itself and corrupt/lose data, so it is rejected with this error. It is called by CopyDir before any copying happens.","triggerScenarios":"Calling CopyDir(src, dst) where dst is inside src, or dst equals src — e.g. `cscli hubtest copy . ./sub` or copying /tmp/a into /tmp/a/b.","commonSituations":"Shell expansion or a config variable resolving to the same directory; accidentally passing a parent folder as the destination; scripts that concatenate paths without checking containment.","solutions":["Choose a destination directory that is not inside the source directory","Print both absolute paths and compare before running the copy","If you intended an in-place move, use os.Rename instead of CopyDir","Add a pre-check in the calling script using the same ancestor-walk logic"],"exampleFix":"// before: recursion attempt\nCopyDir(\"/tmp/scenarios\", \"/tmp/scenarios/new\")\n// after: disjoint destination\nCopyDir(\"/tmp/scenarios\", \"/tmp/copy-of-scenarios\")","handlingStrategy":"validation","validationCode":"func isInside(dst, src string) bool {\n    absDst, _ := filepath.Abs(dst); absSrc, _ := filepath.Abs(src)\n    rel, err := filepath.Rel(absSrc, absDst)\n    return err == nil && rel != \"..\" && !strings.HasPrefix(rel, \"..\")+string(os.PathSeparator) && rel != \".\" && !strings.HasPrefix(rel, \"..\")\n}\n// call CopyDir only if !isInside(dst, src)","typeGuard":null,"tryCatchPattern":"if err := CopyDir(src, dst); err != nil {\n    if strings.Contains(err.Error(), \"cannot copy a folder onto itself\") {\n        return fmt.Errorf(\"destination %s is inside source %s; pick another target\", dst, src)\n    }\n    return err\n}","preventionTips":["Compare filepath.Abs of src and dst before copying","Never use the source directory itself (or a shell-expanded variable) as destination","Prefer os.Rename for in-place moves instead of CopyDir","Log resolved absolute paths in scripts so containment mistakes are visible"],"tags":["filesystem","copy","path-validation"],"backgroundTag":"path-traversal-blocked","analyzedSha":"909b5157986a2b2c2163300fdaef5ed01289f7d2","analyzedAt":"2026-09-06T12:27:26.012Z","contentChangedAt":"2026-09-06T12:27:26.012Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}