{"record":{"id":"363a154ed2bf942b","repo":"wavetermdev/waveterm","slug":"invalid-environment-variable-name-q","errorCode":null,"errorMessage":"invalid environment variable name: %q","messagePattern":"invalid environment variable name: %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/genconn/genconn.go","lineNumber":142,"sourceCode":"\t\treturn nil, fmt.Errorf(\"failed to get stdout pipe: %w\", err)\n\t}\n\treturn syncbuf.MakeSyncBufferFromReader(stdout), nil\n}\n\nfunc MakeStderrSyncBuffer(proc ShellProcessController) (*syncbuf.SyncBuffer, error) {\n\tstderr, err := proc.StderrPipe()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get stderr pipe: %w\", err)\n\t}\n\treturn syncbuf.MakeSyncBufferFromReader(stderr), nil\n}\n\nfunc BuildShellCommand(opts CommandSpec) (string, error) {\n\t// Build environment variables\n\tvar envVars strings.Builder\n\tfor key, value := range opts.Env {\n\t\tif !isValidEnvVarName(key) {\n\t\t\treturn \"\", fmt.Errorf(\"invalid environment variable name: %q\", key)\n\t\t}\n\t\tenvVars.WriteString(fmt.Sprintf(\"%s=%s \", key, shellutil.HardQuote(value)))\n\t}\n\n\t// Build the command\n\tshellCmd := opts.Cmd\n\tif opts.Cwd != \"\" {\n\t\tshellCmd = fmt.Sprintf(\"cd %s && %s\", shellutil.HardQuote(opts.Cwd), shellCmd)\n\t}\n\n\t// Quote the command for `sh -c`\n\treturn fmt.Sprintf(\"sh -c %s\", shellutil.HardQuote(envVars.String()+shellCmd)), nil\n}\n\nfunc isValidEnvVarName(name string) bool {\n\tvalidEnvVarName := regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)\n\treturn validEnvVarName.MatchString(name)\n}","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/genconn/genconn.go#L124-L160","documentation":"BuildShellCommand renders a CommandSpec into a single 'sh -c' command string, inlining environment variables as KEY=value assignments. Before inlining, each env var key is validated with isValidEnvVarName (regex ^[a-zA-Z_][a-zA-Z0-9_]*$); this error is thrown for any key that does not match, because such a key would produce a broken or injection-prone shell assignment. The command is not built or run.","triggerScenarios":"CommandSpec.Env contains a key that is empty, contains characters outside [a-zA-Z0-9_], starts with a digit, or contains '=' (already an assignment) — e.g. env maps built from raw strings like \"FOO=bar\" instead of key/value pairs.","commonSituations":"Passing OS environment strings (os.Environ() output split incorrectly), Windows-style or WSL env values with odd characters in the key, config-driven env vars with typos like 'MY-VAR' or '1PATH', or copying whole env blocks including keys like 'ProgramFiles(x86)'.","solutions":["Sanitize CommandSpec.Env keys: keep only those matching ^[a-zA-Z_][a-zA-Z0-9_]*$ and drop or rename the rest before calling.","If you have raw 'K=V' strings, split them into map entries instead of using 'K=V' as the key.","For keys like 'ProgramFiles(x86)' that legitimately cannot be passed this way, drop them or export them inside Cmd itself (e.g. via export statements in the command string).","Log the offending key (it is quoted with %q) and fix it at the source of the env map construction.","Add a pre-flight validation of the env map in caller code to fail fast with a clear message."],"exampleFix":"// before\nenv := map[string]string{\"MY-VAR\": \"x\", \"1BAD\": \"y\"}\n_, err := genconn.BuildShellCommand(genconn.CommandSpec{Cmd: \"run\", Env: env}) // errors\n// after\nenv := map[string]string{\"MY-VAR\": \"x\", \"1BAD\": \"y\"}\nsafeEnv := make(map[string]string)\nfor k, v := range env {\n    if regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`).MatchString(k) {\n        safeEnv[k] = v\n    } else {\n        log.Printf(\"dropping invalid env key %q\", k)\n    }\n}\ncmdStr, err := genconn.BuildShellCommand(genconn.CommandSpec{Cmd: \"run\", Env: safeEnv})","handlingStrategy":"validation","validationCode":"var validEnvKey = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)\n\nfunc sanitizeEnv(env map[string]string) map[string]string {\n    safe := make(map[string]string, len(env))\n    for k, v := range env {\n        if validEnvKey.MatchString(k) {\n            safe[k] = v\n        }\n    }\n    return safe\n}\n// usage: BuildShellCommand(CommandSpec{Cmd: cmd, Env: sanitizeEnv(env)})","typeGuard":"func isValidEnvKey(k string) bool {\n    for i, c := range k {\n        if !(c == '_' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||\n            (i > 0 && c >= '0' && c <= '9')) {\n            return false\n        }\n    }\n    return len(k) > 0\n}","tryCatchPattern":"cmdStr, err := genconn.BuildShellCommand(spec)\nif err != nil {\n    var badKey string\n    if fmt.Sscanf(err.Error(), \"invalid environment variable name: %q\", &badKey) == 1 {\n        return fmt.Errorf(\"fix env key %q in caller config (allowed: [A-Za-z_][A-Za-z0-9_]*)\", badKey)\n    }\n    return err\n}","preventionTips":["Never build Env from raw K=V strings — split them into map keys/values","Pre-sanitize env maps with the same regex the library uses","Watch for platform env keys that are illegal in sh (e.g. 'ProgramFiles(x86)')","Validate env from user config at load time, not at command build time","Keep keys ASCII; non-ASCII keys will be rejected by the regex"],"tags":["shell","environment-variables","validation","command-building"],"backgroundTag":"invalid-env-var-name","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}