{"record":{"id":"41258e8438e9737a","repo":"hasura/graphql-engine","slug":"can-t-move-move-target-v-is-out-of-bounds-from","errorCode":null,"errorMessage":"can't move, move target %v is out of bounds from=%q, to=%q","messagePattern":"can't move, move target (.+?) is out of bounds from=%q, to=%q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/plugins/move.go","lineNumber":150,"sourceCode":"\t}\n\n\t// Build new file name\n\ttoFilePath, err := filepath.Abs(\n\t\tfilepath.Join(filepath.FromSlash(toDir), filepath.FromSlash(fo.To)),\n\t)\n\tif err != nil {\n\t\treturn m, false, errors.E(\n\t\t\top,\n\t\t\tfmt.Errorf(\"could not get the relative path for the move dst: %w\", err),\n\t\t)\n\t}\n\n\t// Check sane path\n\tm = move{from: fromFilePath, to: toFilePath}\n\tif !isMoveAllowed(fromDir, toDir, m) {\n\t\treturn move{}, false, errors.E(\n\t\t\top,\n\t\t\tfmt.Errorf(\n\t\t\t\t\"can't move, move target %v is out of bounds from=%q, to=%q\",\n\t\t\t\tm,\n\t\t\t\tfromDir,\n\t\t\t\ttoDir,\n\t\t\t),\n\t\t)\n\t}\n\n\treturn m, true, nil\n}\n\nfunc isMoveAllowed(fromBase, toBase string, m move) bool {\n\t_, okFrom := IsSubPath(fromBase, m.from)\n\t_, okTo := IsSubPath(toBase, m.to)\n\n\treturn okFrom && okTo\n}\n","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/hasura/graphql-engine/blob/724551b9ae87845594ef0408cff0e50eb6c90dc5/cli/plugins/move.go#L132-L168","documentation":"getDirectMove built a direct from→to move for a non-glob FileOperation and isMoveAllowed rejected it: either the source path is not under fromDir or the destination is not under toDir. This guards against manifest entries that read from or write outside the staging/install trees.","triggerScenarios":"A FileOperation.From containing '..' or an absolute path escaping fromDir, or a To that resolves outside toDir, when the entry is treated as a direct file (not a glob).","commonSituations":"Manifest authored with '..' in From/To; archive extracted somewhere other than expected so joined paths escape the base; malicious/tampered plugin index attempting traversal.","solutions":["Fix the manifest so From is a path inside the extracted archive and To is inside the install directory, with no '..' or leading '/'","Manually inspect the archive layout and align From with it","Update/refresh the plugin index in case the entries were corrected upstream","If unsolicited, verify the plugin index source — the entry may be a traversal attack"],"exampleFix":"// before\nFrom: \"./../../etc/passwd\"\n// after\nFrom: \"bin/plugin\"","handlingStrategy":"validation","validationCode":"src := filepath.Clean(filepath.Join(fromDir, fo.From))\ndst := filepath.Clean(filepath.Join(toDir, fo.To))\nif _, ok := plugins.IsSubPath(fromDir, src); !ok { return errors.New(\"From escapes source dir\") }\nif _, ok := plugins.IsSubPath(toDir, dst); !ok { return errors.New(\"To escapes dest dir\") }","typeGuard":null,"tryCatchPattern":"if err := moveFiles(...); err != nil && strings.Contains(err.Error(), \"out of bounds\") {\n    // reject the manifest entry; never strip the guard\n}","preventionTips":["Lint manifests for '..' or absolute paths in From/To","Run IsSubPath checks in plugin index CI","Investigate out-of-bounds hits as possible malicious manifests"],"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"}