lima-vm/lima · error

sudo field must not contain newline characters

Error message

sudo field must not contain newline characters

What it means

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.

Source

Thrown at pkg/guestagent/fakecloudinit/fakecloudinit_darwin.go:322

	}
	for _, f := range []string{pwPath, dotSSHPath, authKeysPath} {
		if err = os.Chown(f, uid, -1); err != nil {
			return fmt.Errorf("failed to chown %#q for user %#q: %w", f, u.Name, err)
		}
	}
	if u.Sudo != "" {
		if err := writeSudoers(u.Name, u.Sudo); err != nil {
			return fmt.Errorf("failed to write sudoers file for user %#q: %w", u.Name, err)
		}
	}
	return nil
}

// writeSudoers appends a sudoers entry for the given user.
// writeSudoers is expected be called only once on creating the user account.
func writeSudoers(userName, sudo string) error {
	if strings.Contains(sudo, "\n") {
		return errors.New("sudo field must not contain newline characters")
	}
	if err := os.MkdirAll("/etc/sudoers.d", 0o700); err != nil {
		return fmt.Errorf("failed to create /etc/sudoers.d directory: %w", err)
	}
	sudoersPath := "/etc/sudoers.d/90-cloud-init-users"
	f, err := os.OpenFile(sudoersPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o400)
	if err != nil {
		return fmt.Errorf("failed to open sudoers file %#q: %w", sudoersPath, err)
	}
	if _, err = fmt.Fprintf(f, "%s %s\n", userName, sudo); err != nil {
		_ = f.Close()
		return fmt.Errorf("failed to write to sudoers file %#q for user %#q: %w", sudoersPath, userName, err)
	}
	return f.Close()
}

func writeFiles(ctx context.Context, entry cloudinittypes.WriteFile) error {
	if entry.Path == "" {

View on GitHub (pinned to dd909d0973)

Solutions

  1. Make the `sudo` field a single line, e.g. `ALL=(ALL) NOPASSWD: ALL`
  2. Replace YAML block scalars (`|`, `>`) with a plain quoted scalar for the sudo field
  3. If multiple rules are needed, place a full sudoers drop-in manually instead of the sudo field
  4. Trim trailing newlines from values generated by scripts

Example fix

# before
sudo: |
  ALL=(ALL) NOPASSWD: ALL
# after
sudo: "ALL=(ALL) NOPASSWD: ALL"
Defensive patterns

Strategy: validation

Validate before calling

func validateSudo(sudo string) error {
    if strings.Contains(sudo, "\n") || strings.Contains(sudo, "\r") {
        return errors.New("sudo must not contain newline characters")
    }
    return nil
}

Try / catch

if err := processUserData(ctx, data); err != nil {
    if strings.Contains(err.Error(), "must not contain newline") {
        log.Printf("invalid sudo value in user-data: %v — flatten to one line", err)
    }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of lima-vm/lima@dd909d0973 (2026-09-01). Data as JSON: /api/errors/bda665e166408e18. Report an issue: GitHub.