{"record":{"id":"2734026a164c22d3","repo":"tailscale/tailscale","slug":"invalid-appname-40q","errorCode":null,"errorMessage":"invalid AppName %.40q","messagePattern":"invalid AppName %\\.40q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"derp/derp_client.go","lineNumber":123,"sourceCode":"\t}\n\tfor i := range len(name) {\n\t\tif b := name[i]; b < ' ' || b > '~' {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc NewClient(privateKey key.NodePrivate, nc Conn, brw *bufio.ReadWriter, logf logger.Logf, opts ...ClientOpt) (*Client, error) {\n\tvar opt clientOpt\n\tfor _, o := range opts {\n\t\tif o == nil {\n\t\t\treturn nil, errors.New(\"nil ClientOpt\")\n\t\t}\n\t\to.update(&opt)\n\t}\n\tif !ValidAppName(opt.AppName) {\n\t\treturn nil, fmt.Errorf(\"invalid AppName %.40q\", opt.AppName)\n\t}\n\treturn newClient(privateKey, nc, brw, logf, opt)\n}\n\nfunc newClient(privateKey key.NodePrivate, nc Conn, brw *bufio.ReadWriter, logf logger.Logf, opt clientOpt) (*Client, error) {\n\tc := &Client{\n\t\tprivateKey:  privateKey,\n\t\tpublicKey:   privateKey.Public(),\n\t\tlogf:        logf,\n\t\tnc:          nc,\n\t\tbr:          brw.Reader,\n\t\tbw:          brw.Writer,\n\t\tmeshKey:     opt.MeshKey,\n\t\tcanAckPings: opt.CanAckPings,\n\t\tisProber:    opt.IsProber,\n\t\tappName:     opt.AppName,\n\t\tclock:       tstime.StdClock{},\n\t}","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/tailscale/tailscale/blob/a7769cbc33a3eba62bb16fc803b97077c2969d16/derp/derp_client.go#L105-L141","documentation":"derp.NewClient validates the configured AppName via derp.ValidAppName and rejects anything invalid. AppName must be a non-empty, trimmed string of at most 255 bytes containing only printable, non-space runes ( effectively letters, digits, and punctuation like '-', '_', '.'). This guards the DERPI field on the wire so servers don't receive garbage or overlong identifiers.","triggerScenarios":"Calling derp.NewClient (or NewNetworkClient) with a ClientOpt (e.g. derp.MeshKey, derp.CancelForwardingConfig... specifically derp.AppName(\"...\")) whose value contains whitespace/control chars, leading/trailing spaces, non-printable Unicode, or exceeds 255 bytes; also passing an empty AppName.","commonSituations":"Copying an AppName from config with trailing newline or space; using a UUID with spaces; embedding a user-controlled string into the app name; upgrading to a tailscale version where AppName validation was added (older versions accepted anything).","solutions":["Sanitize the app name before passing it: strings.TrimSpace and filter to printable non-space runes.","Ensure the name is 1-255 bytes and non-empty; shorten derived/concatenated names.","If the value comes from config/env, validate it at startup with derp.ValidAppName and fail fast with a clear message."],"exampleFix":"// before\nc, err := derp.NewClient(key, conn, brw, logf, derp.AppName(rawName))\n\n// after\nname := strings.TrimSpace(rawName)\nif !derp.ValidAppName(name) {\n    log.Fatalf(\"bad app name %q\", name)\n}\nc, err := derp.NewClient(key, conn, brw, logf, derp.AppName(name))","handlingStrategy":"validation","validationCode":"name := strings.TrimSpace(raw)\nif len(name) == 0 || len(name) > 255 || strings.ContainsFunc(name, func(r rune) bool { return r <= ' ' || r > '~' }) {\n    return fmt.Errorf(\"invalid app name %q\", raw)\n}\n// or simply:\nif !derp.ValidAppName(raw) { return fmt.Errorf(\"invalid app name %q\", raw) }","typeGuard":"func ValidAppName(s string) bool {\n\tif len(s) == 0 || len(s) > 255 { return false }\n\tfor _, r := range s {\n\t\tif r <= ' ' || r > '~' { return false }\n\t}\n\treturn true\n}","tryCatchPattern":"c, err := derp.NewClient(k, nc, brw, logf, derp.AppName(name))\nif err != nil {\n    if strings.Contains(err.Error(), \"invalid AppName\") {\n    \treturn fmt.Errorf(\"app name %q rejected by DERP: %w\", name, err)\n    }\n    return err\n}","preventionTips":["Derive AppNames from a fixed allowlist of identifiers, never free-form user input.","Trim and length-check names at config load time.","Unit-test client construction with the exact names used in production."],"tags":["derp","tailscale","validation","client-config"],"backgroundTag":"app-name-validation-failed","analyzedSha":"a7769cbc33a3eba62bb16fc803b97077c2969d16","analyzedAt":"2026-08-27T02:43:10.927Z","contentChangedAt":"2026-08-27T02:43:10.927Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}