[GH-ISSUE #3119] Error 205: ServerUserNotInSudo #1446

Open
opened 2026-10-13 20:42:37 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @ph4 on GitHub (Sep 3, 2026).
Original GitHub issue: https://github.com/amnezia-vpn/amnezia-client/issues/3119

Describe the bug
Amnezia REQUIRES the user to be in a sudo or a wheel group, ignoring if the user can actually use sudo without password!

To Reproduce
Steps to reproduce the behavior:

  1. Install Debian 13 server
  2. Create a user
  3. Allow this user to do passwordless sudo via /etc/sudoers.d/

Expected behavior
App checks that user can actually do sudo and proceeds as normal

Any platform issue

Originally created by @ph4 on GitHub (Sep 3, 2026). Original GitHub issue: https://github.com/amnezia-vpn/amnezia-client/issues/3119 **Describe the bug** Amnezia REQUIRES the user to be in a sudo or a wheel group, ignoring if the user can actually use sudo without password! **To Reproduce** Steps to reproduce the behavior: 1. Install Debian 13 server 2. Create a user 3. Allow this user to do passwordless sudo via /etc/sudoers.d/ **Expected behavior** App checks that user can actually do sudo and proceeds as normal **Any platform issue**
Author
Owner

@HeisLuka commented on GitHub (Sep 8, 2026):

Confirmed in current dev: this is a preflight capability bug rather than a Debian-specific issue.

check_user_in_sudo.sh currently gates the actual sudo probe behind group membership:

if [ "$CUR_USER" = "root" ] ||
( groups "$CUR_USER" | grep -E '<(sudo|wheel)>' ); then
sudo -nu $CUR_USER $pm $opt >/dev/null
sudo -n $pm $opt >/dev/null
fi

Therefore a perfectly valid configuration such as:

user: amnezia
groups: amnezia

/etc/sudoers.d/amnezia:
amnezia ALL=(ALL) NOPASSWD: ALL

never reaches the actual sudo -n ... test.

The C++ side then interprets the absence of sudo/wheel in the script output as:

ErrorCode::ServerUserNotInSudo

so the current invariant is effectively:

member_of(sudo || wheel)
=> maybe capable

!member_of(sudo || wheel)
=> definitely incapable

which is not how sudoers works.

A minimal regression case would be:

GIVEN:
user is not in sudo/wheel
sudo is installed
sudo -n <required-probe> succeeds

EXPECT:
preflight succeeds

CURRENT:
Error 205 ServerUserNotInSudo

I would make the executable capability authoritative and treat group membership, at most, as diagnostic metadata.

Conceptually:

if [ "$CUR_USER" = "root" ]; then
capability=ok
elif ! command -v sudo >/dev/null 2>&1; then
capability=no_sudo_binary
elif sudo -n >/dev/null 2>&1; then
capability=ok
else
capability=denied_or_password_required
fi

This also gives much better error semantics:

sudo binary missing
!=
sudo exists but password required
!=
sudoers denies operation
!=
user merely isn't in a conventional group

I would also avoid parsing free-form output for the literal strings "sudo" and "wheel". Have the script return an explicit machine-readable result or exit code and map that in InstallController.

One further point: the current probe is based on:

sudo -n $pm $opt

i.e. essentially package-manager --version.

That proves only that this particular command can run under sudo. If Amnezia intends to support fine-grained /etc/sudoers.d/ policies, the stronger invariant is:

PRECHECK_OK
=>
every privileged capability required by the selected install/update path
is available

rather than:

apt-get --version worked
=>
Docker/firewall/sysctl/etc. will work

If the product contract instead requires unrestricted passwordless sudo, then that should be tested directly and documented as such.

Suggested regression matrix:

  1. sudo-group + NOPASSWD
    -> PASS

  2. no sudo/wheel group + NOPASSWD: ALL in sudoers.d
    -> PASS

  3. wheel group but sudoers explicitly denies required command
    -> FAIL capability check

  4. sudo group but password required
    -> FAIL with password-required reason

  5. root, sudo package absent
    -> PASS where direct root execution is supported

This would make the preflight describe what the installer can actually do rather than how the administrator chose to grant that permission.

<!-- gh-comment-id:5587445223 --> @HeisLuka commented on GitHub (Sep 8, 2026): Confirmed in current dev: this is a preflight capability bug rather than a Debian-specific issue. check_user_in_sudo.sh currently gates the actual sudo probe behind group membership: if [ "$CUR_USER" = "root" ] || ( groups "$CUR_USER" | grep -E '\<(sudo|wheel)\>' ); then sudo -nu $CUR_USER $pm $opt >/dev/null sudo -n $pm $opt >/dev/null fi Therefore a perfectly valid configuration such as: user: amnezia groups: amnezia /etc/sudoers.d/amnezia: amnezia ALL=(ALL) NOPASSWD: ALL never reaches the actual sudo -n ... test. The C++ side then interprets the absence of sudo/wheel in the script output as: ErrorCode::ServerUserNotInSudo so the current invariant is effectively: member_of(sudo || wheel) => maybe capable !member_of(sudo || wheel) => definitely incapable which is not how sudoers works. A minimal regression case would be: GIVEN: user is not in sudo/wheel sudo is installed `sudo -n <required-probe>` succeeds EXPECT: preflight succeeds CURRENT: Error 205 ServerUserNotInSudo I would make the executable capability authoritative and treat group membership, at most, as diagnostic metadata. Conceptually: if [ "$CUR_USER" = "root" ]; then capability=ok elif ! command -v sudo >/dev/null 2>&1; then capability=no_sudo_binary elif sudo -n <capability-probe> >/dev/null 2>&1; then capability=ok else capability=denied_or_password_required fi This also gives much better error semantics: sudo binary missing != sudo exists but password required != sudoers denies operation != user merely isn't in a conventional group I would also avoid parsing free-form output for the literal strings "sudo" and "wheel". Have the script return an explicit machine-readable result or exit code and map that in InstallController. One further point: the current probe is based on: sudo -n $pm $opt i.e. essentially package-manager --version. That proves only that this particular command can run under sudo. If Amnezia intends to support fine-grained /etc/sudoers.d/ policies, the stronger invariant is: PRECHECK_OK => every privileged capability required by the selected install/update path is available rather than: apt-get --version worked => Docker/firewall/sysctl/etc. will work If the product contract instead requires unrestricted passwordless sudo, then that should be tested directly and documented as such. Suggested regression matrix: 1. sudo-group + NOPASSWD -> PASS 2. no sudo/wheel group + NOPASSWD: ALL in sudoers.d -> PASS 3. wheel group but sudoers explicitly denies required command -> FAIL capability check 4. sudo group but password required -> FAIL with password-required reason 5. root, sudo package absent -> PASS where direct root execution is supported This would make the preflight describe what the installer can actually do rather than how the administrator chose to grant that permission.
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
amnezia-vpn/amnezia-client#1446
No description provided.