{"record":{"id":"bda665e166408e18","repo":"lima-vm/lima","slug":"sudo-field-must-not-contain-newline-characters","errorCode":null,"errorMessage":"sudo field must not contain newline characters","messagePattern":"sudo field must not contain newline characters","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/guestagent/fakecloudinit/fakecloudinit_darwin.go","lineNumber":322,"sourceCode":"\t}\n\tfor _, f := range []string{pwPath, dotSSHPath, authKeysPath} {\n\t\tif err = os.Chown(f, uid, -1); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to chown %#q for user %#q: %w\", f, u.Name, err)\n\t\t}\n\t}\n\tif u.Sudo != \"\" {\n\t\tif err := writeSudoers(u.Name, u.Sudo); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write sudoers file for user %#q: %w\", u.Name, err)\n\t\t}\n\t}\n\treturn nil\n}\n\n// writeSudoers appends a sudoers entry for the given user.\n// writeSudoers is expected be called only once on creating the user account.\nfunc writeSudoers(userName, sudo string) error {\n\tif strings.Contains(sudo, \"\\n\") {\n\t\treturn errors.New(\"sudo field must not contain newline characters\")\n\t}\n\tif err := os.MkdirAll(\"/etc/sudoers.d\", 0o700); err != nil {\n\t\treturn fmt.Errorf(\"failed to create /etc/sudoers.d directory: %w\", err)\n\t}\n\tsudoersPath := \"/etc/sudoers.d/90-cloud-init-users\"\n\tf, err := os.OpenFile(sudoersPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o400)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to open sudoers file %#q: %w\", sudoersPath, err)\n\t}\n\tif _, err = fmt.Fprintf(f, \"%s %s\\n\", userName, sudo); err != nil {\n\t\t_ = f.Close()\n\t\treturn fmt.Errorf(\"failed to write to sudoers file %#q for user %#q: %w\", sudoersPath, userName, err)\n\t}\n\treturn f.Close()\n}\n\nfunc writeFiles(ctx context.Context, entry cloudinittypes.WriteFile) error {\n\tif entry.Path == \"\" {","sourceCodeStart":304,"sourceCodeEnd":340,"githubUrl":"https://github.com/lima-vm/lima/blob/dd909d0973cd84fa35f9e1693181b4585ea616c1/pkg/guestagent/fakecloudinit/fakecloudinit_darwin.go#L304-L340","documentation":"writeSudoers rejects a `sudo` value containing a newline character. Each sudoers file line is written as `userName sudo`; an embedded newline would inject arbitrary sudoers directives — a security hazard — so the agent refuses up front rather than writing a corrupt /etc/sudoers.d/90-cloud-init-users.","triggerScenarios":"The user-data `users[].sudo` string passed through processUserData → createUser → writeSudoers contains '\\n'. Happens with multi-line YAML block scalars (`|`/`>`), comma-separated rules split across lines, or copy-pasted Linux cloud-init snippets.","commonSituations":"Copy-pasting multi-rule sudoers stanzas from Linux tutorials; YAML block scalar accidentally adding a trailing newline; tooling joining multiple sudo entries into one field.","solutions":["Make the `sudo` field a single line, e.g. `ALL=(ALL) NOPASSWD: ALL`","Replace YAML block scalars (`|`, `>`) with a plain quoted scalar for the sudo field","If multiple rules are needed, place a full sudoers drop-in manually instead of the sudo field","Trim trailing newlines from values generated by scripts"],"exampleFix":"# before\nsudo: |\n  ALL=(ALL) NOPASSWD: ALL\n# after\nsudo: \"ALL=(ALL) NOPASSWD: ALL\"","handlingStrategy":"validation","validationCode":"func validateSudo(sudo string) error {\n    if strings.Contains(sudo, \"\\n\") || strings.Contains(sudo, \"\\r\") {\n        return errors.New(\"sudo must not contain newline characters\")\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err := processUserData(ctx, data); err != nil {\n    if strings.Contains(err.Error(), \"must not contain newline\") {\n        log.Printf(\"invalid sudo value in user-data: %v — flatten to one line\", err)\n    }\n}","preventionTips":["Use plain quoted scalars, never `|`/`>` blocks, for sudo values","Trim trailing newlines from generated configuration values","Lint user-data for multi-line sudo before limactl start"],"tags":["validation","sudoers","security","cloud-init"],"backgroundTag":"invalid-sudoers-value","analyzedSha":"dd909d0973cd84fa35f9e1693181b4585ea616c1","analyzedAt":"2026-09-01T14:24:59.842Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}