Hello,
I ran into a bug in privleap that makes it unusable for any account whose
username contains an uppercase letter. Details and reproduction below.
Summary
normalize_user_id() rejects any username containing uppercase letters, so
accounts like Example_User can never be granted a comm socket. privleap is
effectively unusable for those users, and leapctl@<uid>.service restarts
forever.
Environment
- Kicksecure 18 on Debian 13.6 (trixie), kernel 6.12.107+deb13-amd64
- privleap 3:6.1-1
- user-sysmaint-split 3:12.7-1, systemcheck 3:52.2-1
- Affected account:
Example_User, UID 1000
What happens
privleapd.service is active and creates comm sockets normally:
$ ls /run/privleapd/comm/
root sdwdate systemcheck tb-updater
Every one of those names is lowercase. The socket for UID 1000 is missing, and
the unit that should create it never succeeds:
$ systemctl status leapctl@1000.service
Active: activating (auto-restart) (Result: exit-code)
Process: ExecStart=/usr/bin/leapctl --create 1000 (code=exited, status=1/FAILURE)
$ systemctl show leapctl@1000.service -p NRestarts -p ExecMainStatus
NRestarts=1806
ExecMainStatus=1
That count was taken after 10 hours of uptime: one attempt every 20 seconds
(RestartSec=20), each spawning a Python process, for as long as the machine
stays on.
Root cause
user_name_regex in privleap.py:1230 accepts lowercase only:
user_name_regex: re.Pattern[str] = re.compile(r"[a-z_][-a-z0-9_]*\$?\Z")
normalize_user_id() (privleap.py:1628) gates on it, so an existing account
is reported as nonexistent:
$ python3 -c "from privleap.privleap import PrivleapCommon as P; \
print(P.normalize_user_id('1000'), P.normalize_user_id('Example_User'))"
Example_User None
The UID path works, so leapctl --create 1000 resolves the name and sends it
to the daemon. The daemon then normalizes the same name a second time
(privleapd.py:316), this time hitting the regex, logs "Account does not
exist" and returns a control error. leapctl exits 1.
Two things worth noting:
RestartPreventExitStatus=2 in leapctl@.service is meant to stop the loop
when an account is legitimately not permitted to have a socket (exit 2).
This failure exits 1, so the loop never stops.
- The regex is redundant as an existence check: inside the same branch,
normalize_user_id() already verifies membership in pwd.getpwall().
Reproduction
- Create an account whose name contains an uppercase letter.
- Log in.
ls /run/privleapd/comm/ — no socket for that account.
systemctl status leapctl@$(id -u).service — restart loop, exit status 1.
The name is valid by Debian's own rules
Debian's adduser accepts uppercase in non-system usernames. From
man 5 adduser.conf, NAME_REGEX "defaults to the most conservative
^[a-zA-Z][a-zA-Z0-9_-]*\$?$", and SYS_NAME_REGEX to
^[a-zA-Z_][a-zA-Z0-9_-]*\$?$. Both allow A-Z.
$ python3 -c "
import re
print(bool(re.match(r'^[a-zA-Z][a-zA-Z0-9_-]*\$?\$', 'Example_User'))) # adduser
print(bool(re.match(r'[a-z_][-a-z0-9_]*\$?\Z', 'Example_User'))) # privleap
"
True
False
So an account created within Debian's most conservative default is rejected by
privleap.
Knock-on effect: systemcheck reports Tor as down
systemcheck queries Tor through privleap. In
helper-scripts/tor_bootstrap_check.bsh, check_tor_circuit_established()
runs the tor-circuit-established-check privleap action, and on any non-zero
exit code falls through to:
tor_circuit_established="0"
tor_circuit_established_word="not established"
So a privleap failure is reported to the user as "Tor Circuit: not
established", repeated every 2 seconds for the full timeout. On the affected
machine Tor was working the whole time:
$ curl -sS --socks5-hostname 127.0.0.1:9050 https://check.torproject.org/api/ip
{"IsTor":true, ...}
This is the part that worries users: the check reports a broken Tor connection
when the only thing broken is the socket it uses to ask. Distinguishing
"could not query" from "not established" would help regardless of how the
username issue is resolved.
Suggested fix
Allow uppercase in user_name_regex, or drop the regex gate in
normalize_user_id() and rely on the pwd lookup that already follows it.
Still present in master as of 2026-09-15.
Thanks for your work on Kicksecure.
Investigated with Claude Code. Every command above was run on the affected
machine and the outputs are pasted as produced, with two edits: the account
name is replaced by Example_User throughout, and the exit relay address is
trimmed from the check.torproject.org response.
Hello,
I ran into a bug in privleap that makes it unusable for any account whose
username contains an uppercase letter. Details and reproduction below.
Summary
normalize_user_id()rejects any username containing uppercase letters, soaccounts like
Example_Usercan never be granted a comm socket. privleap iseffectively unusable for those users, and
leapctl@<uid>.servicerestartsforever.
Environment
Example_User, UID 1000What happens
privleapd.serviceis active and creates comm sockets normally:Every one of those names is lowercase. The socket for UID 1000 is missing, and
the unit that should create it never succeeds:
That count was taken after 10 hours of uptime: one attempt every 20 seconds
(
RestartSec=20), each spawning a Python process, for as long as the machinestays on.
Root cause
user_name_regexinprivleap.py:1230accepts lowercase only:normalize_user_id()(privleap.py:1628) gates on it, so an existing accountis reported as nonexistent:
The UID path works, so
leapctl --create 1000resolves the name and sends itto the daemon. The daemon then normalizes the same name a second time
(
privleapd.py:316), this time hitting the regex, logs "Account does notexist" and returns a control error.
leapctlexits 1.Two things worth noting:
RestartPreventExitStatus=2inleapctl@.serviceis meant to stop the loopwhen an account is legitimately not permitted to have a socket (exit 2).
This failure exits 1, so the loop never stops.
normalize_user_id()already verifies membership inpwd.getpwall().Reproduction
ls /run/privleapd/comm/— no socket for that account.systemctl status leapctl@$(id -u).service— restart loop, exit status 1.The name is valid by Debian's own rules
Debian's
adduseraccepts uppercase in non-system usernames. Fromman 5 adduser.conf,NAME_REGEX"defaults to the most conservative^[a-zA-Z][a-zA-Z0-9_-]*\$?$", andSYS_NAME_REGEXto^[a-zA-Z_][a-zA-Z0-9_-]*\$?$. Both allowA-Z.So an account created within Debian's most conservative default is rejected by
privleap.
Knock-on effect: systemcheck reports Tor as down
systemcheckqueries Tor through privleap. Inhelper-scripts/tor_bootstrap_check.bsh,check_tor_circuit_established()runs the
tor-circuit-established-checkprivleap action, and on any non-zeroexit code falls through to:
So a privleap failure is reported to the user as "Tor Circuit: not
established", repeated every 2 seconds for the full timeout. On the affected
machine Tor was working the whole time:
This is the part that worries users: the check reports a broken Tor connection
when the only thing broken is the socket it uses to ask. Distinguishing
"could not query" from "not established" would help regardless of how the
username issue is resolved.
Suggested fix
Allow uppercase in
user_name_regex, or drop the regex gate innormalize_user_id()and rely on thepwdlookup that already follows it.Still present in
masteras of 2026-09-15.Thanks for your work on Kicksecure.
Investigated with Claude Code. Every command above was run on the affected
machine and the outputs are pasted as produced, with two edits: the account
name is replaced by
Example_Userthroughout, and the exit relay address istrimmed from the check.torproject.org response.