{"record":{"id":"2c53a470098a7906","repo":"vitessio/vitess","slug":"invalid-hook-name-q-v","errorCode":null,"errorMessage":"invalid hook name %q: %v","messagePattern":"invalid hook name %q: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/hook/hook.go","lineNumber":111,"sourceCode":"}\n\n// NewHookWithEnv returns a Hook object with the provided name, params and ExtraEnv.\nfunc NewHookWithEnv(name string, params []string, env map[string]string) *Hook {\n\treturn &Hook{Name: name, Parameters: params, ExtraEnv: env}\n}\n\n// findHook tries to locate the hook, and returns the exec.Cmd for it.\nfunc (hook *Hook) findHook(ctx context.Context) (*exec.Cmd, int, error) {\n\t// Find our root.\n\troot, err := vtenv.VtRoot()\n\tif err != nil {\n\t\treturn nil, HOOK_VTROOT_ERROR, fmt.Errorf(\"cannot get VTROOT: %v\", err)\n\t}\n\n\t// See if the hook exists.\n\tvthook, err := fileutil.SafePathJoin(filepath.Join(root, \"vthook\"), hook.Name)\n\tif err != nil {\n\t\treturn nil, HOOK_INVALID_NAME, fmt.Errorf(\"invalid hook name %q: %v\", hook.Name, err)\n\t}\n\t_, err = os.Stat(vthook)\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\treturn nil, HOOK_DOES_NOT_EXIST, fmt.Errorf(\"missing hook %v\", vthook)\n\t\t}\n\n\t\treturn nil, HOOK_STAT_FAILED, fmt.Errorf(\"cannot stat hook %v: %v\", vthook, err)\n\t}\n\n\t// Configure the command.\n\tlog.Info(fmt.Sprintf(\"hook: executing hook: %v %v\", vthook, strings.Join(hook.Parameters, \" \")))\n\tcmd := exec.CommandContext(ctx, vthook, hook.Parameters...)\n\tif len(hook.ExtraEnv) > 0 {\n\t\tcmd.Env = os.Environ()\n\t\tfor key, value := range hook.ExtraEnv {\n\t\t\tcmd.Env = append(cmd.Env, key+\"=\"+value)\n\t\t}","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/hook/hook.go#L93-L129","documentation":"After resolving the vthook directory, the hook name is validated with fileutil.SafePathJoin to prevent path traversal or illegal names. If the name is unsafe (contains '/', '..', absolute path components, or other rejected characters), findHook returns HOOK_INVALID_NAME. This is a security guard: hook names must be simple names of scripts inside the vthook directory.","triggerScenarios":"Calling any Hook execution API with hook.Name containing path separators, '..' segments, a leading '/', empty/illegal characters, or otherwise failing fileutil.SafePathJoin validation.","commonSituations":"Building hook names dynamically from user input or config values that include subdirectories (e.g. 'tools/myhook.sh'); names copied from file paths rather than bare script names.","solutions":["Set hook.Name to a bare file name with no path components (e.g. 'myhook' not 'subdir/myhook')","Sanitize or reject user-supplied hook names before constructing the Hook","Check the %q in the message to see the exact rejected name and which character caused SafePathJoin to fail"],"exampleFix":"// before\nhook := &hook.Hook{Name: path.Join(\"bin\", \"cleanup.sh\")}\n// after\nhook := &hook.Hook{Name: \"cleanup.sh\"}","handlingStrategy":"validation","validationCode":"func validHookName(name string) bool {\n    if name == \"\" || strings.ContainsAny(name, \"/\\\\\") || strings.Contains(name, \"..\") {\n        return false\n    }\n    return name == filepath.Base(name)\n}","typeGuard":null,"tryCatchPattern":"hr := h.ExecuteContext(ctx)\nif hr.ExitStatus == hook.HOOK_INVALID_NAME {\n    return fmt.Errorf(\"rejecting hook %q: invalid name\", h.Name)\n}","preventionTips":["Restrict hook names to [A-Za-z0-9._-] with no path separators","Never build hook names from unsanitized user input","Use fileutil.SafePathJoin yourself when constructing paths to hook scripts"],"tags":["validation","hook","path-traversal"],"backgroundTag":"invalid-path-name","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}