{"record":{"id":"5df4aeadfc74f2b9","repo":"golang/go","slug":"bad-tool-name-q","errorCode":null,"errorMessage":"bad tool name: %q","messagePattern":"bad tool name: %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/cmd/go/internal/base/tool.go","lineNumber":34,"sourceCode":"\n// Tool returns the path to the named builtin tool (for example, \"vet\").\n// If the tool cannot be found, Tool exits the process.\nfunc Tool(toolName string) string {\n\ttoolPath, err := ToolPath(toolName)\n\tif err != nil && len(cfg.BuildToolexec) == 0 {\n\t\t// Give a nice message if there is no tool with that name.\n\t\tfmt.Fprintf(os.Stderr, \"go: no such tool %q\\n\", toolName)\n\t\tSetExitStatus(2)\n\t\tExit()\n\t}\n\treturn toolPath\n}\n\n// ToolPath returns the path at which we expect to find the named tool\n// (for example, \"vet\"), and the error (if any) from statting that path.\nfunc ToolPath(toolName string) (string, error) {\n\tif !ValidToolName(toolName) {\n\t\treturn \"\", fmt.Errorf(\"bad tool name: %q\", toolName)\n\t}\n\ttoolPath := filepath.Join(build.ToolDir, toolName) + cfg.ToolExeSuffix()\n\terr := toolStatCache.Do(toolPath, func() error {\n\t\t_, err := os.Stat(toolPath)\n\t\treturn err\n\t})\n\treturn toolPath, err\n}\n\nfunc ValidToolName(toolName string) bool {\n\tif toolName == \"\" {\n\t\treturn false\n\t}\n\tfor _, c := range toolName {\n\t\tswitch {\n\t\tcase 'a' <= c && c <= 'z', '0' <= c && c <= '9', c == '_':\n\t\tdefault:\n\t\t\treturn false","sourceCodeStart":16,"sourceCodeEnd":52,"githubUrl":"https://github.com/golang/go/blob/b6b368adc57c96c3151d224d172029f233ead2c3/src/cmd/go/internal/base/tool.go#L16-L52","documentation":"Returned by base.ToolPath(toolName) when ValidToolName(toolName) is false. The go command builds the expected binary path as filepath.Join(build.ToolDir, toolName)+ToolExeSuffix, so a malformed tool name would produce a nonsense path; validation rejects it up front. An empty string or any character outside the accepted set trips the guard.","triggerScenarios":"Calling base.ToolPath(\"\") or with a name containing path separators, spaces, or characters not allowed by ValidToolName (which rejects empty and non-simple names).","commonSituations":"Code that builds tool names dynamically from user/file input; off-by-one slicing producing an empty string; passing a path instead of a bare tool name.","solutions":["Validate with base.ValidToolName(name) before calling ToolPath.","Use the known tool-name constants (e.g. \"vet\", \"build\") rather than constructing names from input.","Trim and reject empty strings at the call site.","If the name originates from argv/config, sanitize to alphanumeric+underscore before forwarding."],"exampleFix":"// before\np, err := base.ToolPath(name) // panics-style error when name=\"\"\n\n// after\nif !base.ValidToolName(name) {\n    return fmt.Errorf(\"invalid tool name %q\", name)\n}\np, err := base.ToolPath(name)","handlingStrategy":"validation","validationCode":"if !base.ValidToolName(name) {\n    return fmt.Errorf(\"rejecting invalid tool name %q\", name)\n}\npath, err := base.ToolPath(name)","typeGuard":"func isValidToolName(s string) bool {\n    if s == \"\" { return false }\n    for _, r := range s {\n        if r == '/' || r == '\\\\' || r == ' ' || unicode.IsControl(r) {\n            return false\n        }\n    }\n    return true\n}","tryCatchPattern":null,"preventionTips":["Never build tool names from raw user input without sanitizing.","Prefer named constants (\"vet\", \"build\") over dynamic strings.","Unit-test the empty-string and separator cases at the call site."],"tags":["tool","validation","argument","path"],"analyzedSha":"b6b368adc57c96c3151d224d172029f233ead2c3","analyzedAt":"2026-08-12T00:22:02.250Z","schemaVersion":2},"datasetVersion":"2026-08-12T08:17:17.861Z"}