{"record":{"id":"3a32bf58aa4f4b44","repo":"hasura/graphql-engine","slug":"can-t-move-move-target-v-is-not-a-subpath-from","errorCode":null,"errorMessage":"can't move, move target %v is not a subpath from=%q, to=%q","messagePattern":"can't move, move target (.+?) is not a subpath from=%q, to=%q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/plugins/move.go","lineNumber":84,"sourceCode":"\t\treturn nil, errors.E(op, fmt.Errorf(\"could not get files using a glob string: %w\", err))\n\t}\n\n\tif len(gl) == 0 {\n\t\treturn nil, errors.E(\n\t\t\top,\n\t\t\tfmt.Errorf(\"no files in the plugin archive matched the glob pattern=%s\", fo.From),\n\t\t)\n\t}\n\n\tmoves := make([]move, 0, len(gl))\n\tfor _, v := range gl {\n\t\tnewPath := filepath.Join(newDir, filepath.Base(filepath.FromSlash(v)))\n\t\t// Check secure path\n\t\tm := move{from: v, to: newPath}\n\t\tif !isMoveAllowed(fromDir, toDir, m) {\n\t\t\treturn nil, errors.E(\n\t\t\t\top,\n\t\t\t\tfmt.Errorf(\n\t\t\t\t\t\"can't move, move target %v is not a subpath from=%q, to=%q\",\n\t\t\t\t\tm,\n\t\t\t\t\tfromDir,\n\t\t\t\t\ttoDir,\n\t\t\t\t),\n\t\t\t)\n\t\t}\n\n\t\tmoves = append(moves, m)\n\t}\n\n\treturn moves, nil\n}\n\nfunc getDirectMove(fromDir, toDir string, fo FileOperation) (move, bool, error) {\n\tvar (\n\t\top errors.Op = \"plugins.getDirectMove\"\n\t\tm  move","sourceCodeStart":66,"sourceCodeEnd":102,"githubUrl":"https://github.com/hasura/graphql-engine/blob/724551b9ae87845594ef0408cff0e50eb6c90dc5/cli/plugins/move.go#L66-L102","documentation":"findMoveTargets computed a move whose source or destination is not a subpath of the allowed source/destination base directories, and isMoveAllowed rejected it. This is a security guard preventing a plugin's FileOperations from moving files outside the staging/install directories.","triggerScenarios":"A FileOperation where the resolved destination (toDir + basename of the glob match) escapes toDir, e.g. To contains enough '..' segments to climb out, or fromDir/toDir resolution makes the joined path fall outside the base.","commonSituations":"Manifest To field like \"../../elsewhere\"; symlinks or odd relative paths in fromDir/toDir; a crafted malicious plugin index attempting path traversal.","solutions":["Audit the plugin manifest's From/To fields and remove any '..' or absolute-path segments in To","Ensure To is a plain relative destination inside the install directory","If you maintain the plugin index, validate FileOperations with IsSubPath before publishing","Treat unexpected occurrences as a red flag: the plugin index may be tampered with — reinstall from the official index"],"exampleFix":"// before\nTo: \"../ escape/bin\"\n// after\nTo: \"bin\"","handlingStrategy":"validation","validationCode":"_, okFrom := plugins.IsSubPath(fromDir, filepath.Join(fromDir, filepath.FromSlash(fo.From)))\n_, okTo := plugins.IsSubPath(toDir, filepath.Join(toDir, filepath.FromSlash(fo.To)))\nif !okFrom || !okTo {\n    return errors.New(\"FileOperation escapes allowed directories\")\n}","typeGuard":null,"tryCatchPattern":"if err := moveFiles(...); err != nil && strings.Contains(err.Error(), \"not a subpath\") {\n    // reject/fix manifest entry; do not blindly retry\n}","preventionTips":["Treat any subpath rejection as possible path traversal, not noise","Validate From/To with IsSubPath before calling install APIs","Only consume trusted plugin indexes"],"tags":["go","security","path-traversal","plugin-install"],"backgroundTag":"path-traversal-blocked","analyzedSha":"724551b9ae87845594ef0408cff0e50eb6c90dc5","analyzedAt":"2026-08-28T07:32:55.105Z","schemaVersion":2},"datasetVersion":"2026-08-28T11:17:15.048Z"}