{"record":{"id":"92dc5e46cf64a2ad","repo":"hasura/graphql-engine","slug":"plugin-name-q-not-allowed","errorCode":null,"errorMessage":"plugin name %q not allowed","messagePattern":"plugin name %q not allowed","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"cli/plugins/scanner.go","lineNumber":83,"sourceCode":"\t}\n\n\tfiles, err := c.findPluginManifestFiles(indexDir)\n\tif err != nil {\n\t\treturn nil, errors.E(op, fmt.Errorf(\"failed to scan plugins in index directory: %w\", err))\n\t}\n\n\treturn c.LoadPlugins(files), nil\n}\n\n// LoadPluginByName loads a plugins index file by its name. When plugin\n// file not found, it returns an error that can be checked with stderrors.Is(err, fs.ErrNotExist).\nfunc (c *Config) LoadPluginByName(pluginName string) (*PluginVersions, error) {\n\tvar op errors.Op = \"plugins.Config.LoadPluginByName\"\n\n\tc.Logger.Debugf(\"loading plugin %s\", pluginName)\n\n\tif !IsSafePluginName(pluginName) {\n\t\treturn nil, errors.E(op, fmt.Errorf(\"plugin name %q not allowed\", pluginName))\n\t}\n\n\tfiles, err := c.findPluginManifestFiles(c.Paths.IndexPluginsPath())\n\tif err != nil {\n\t\treturn nil, errors.E(op, fmt.Errorf(\"failed to scan plugins in index directory: %w\", err))\n\t}\n\n\tps := c.LoadPlugins(files, pluginName)\n\tif _, ok := ps[pluginName]; !ok {\n\t\treturn nil, errors.E(op, os.ErrNotExist)\n\t}\n\n\treturn ps[pluginName], nil\n}\n\nfunc (c *Config) LoadPlugins(files []string, pluginName ...string) Plugins {\n\tc.Logger.Debugf(\"loading plugins\")\n","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/hasura/graphql-engine/blob/724551b9ae87845594ef0408cff0e50eb6c90dc5/cli/plugins/scanner.go#L65-L101","documentation":"LoadPluginByName rejects the requested plugin name via IsSafePluginName before touching the filesystem. This is a path-traversal guard: names containing slashes, '..' segments, absolute paths, or characters outside the allowed pattern are refused so they cannot escape the plugins index directory.","triggerScenarios":"Calling Config.GetPlugin or Config.Upgrade with a name like \"../secrets\", \"/etc/passwd\", \"foo/bar\", an empty string, or any name failing the safe-name regex.","commonSituations":"User-supplied plugin names passed unvalidated from a script or web form; typos including path separators; programmatically constructed names that accidentally include a leading './' or Windows-style backslashes.","solutions":["Sanitize/validate the name with plugins.IsSafePluginName before calling the API","Strip path separators and '..' segments from user input, or reject such input at the boundary","Use the exact plugin name shown by `plugin list` (usually lowercase alphanumeric with dashes)"],"exampleFix":"// before\np, err := cfg.GetPlugin(r.URL.Query().Get(\"name\")) // \"../etc/passwd\" -> error\n\n// after\nname := r.URL.Query().Get(\"name\")\nif !plugins.IsSafePluginName(name) {\n\thttp.Error(w, \"invalid plugin name\", http.StatusBadRequest)\n\treturn\n}\np, err := cfg.GetPlugin(name)","handlingStrategy":"validation","validationCode":"if !plugins.IsSafePluginName(name) { return fmt.Errorf(\"invalid plugin name: %q\", name) }","typeGuard":"func isValidPluginName(s string) bool {\n\tif s == \"\" || len(s) > 100 { return false }\n\tif strings.ContainsAny(s, \"/\\\\\") || strings.Contains(s, \"..\") { return false }\n\tfor _, r := range s {\n\t\tif !(r >= 'a' && r <= 'z' || r >= '0' && r <= '9' || r == '-') { return false }\n\t}\n\treturn true\n}","tryCatchPattern":null,"preventionTips":["Validate plugin names at the input boundary (CLI args, HTTP params) before passing to plugin APIs","Use the exact name reported by plugin list","Never construct names by concatenating user input with paths"],"tags":["plugins","input-validation","path-traversal","security"],"backgroundTag":"path-traversal-rejected","analyzedSha":"724551b9ae87845594ef0408cff0e50eb6c90dc5","analyzedAt":"2026-08-28T07:32:55.105Z","schemaVersion":2},"datasetVersion":"2026-08-28T11:17:15.048Z"}