sec(sudoers): collapse root wildcards behind agor-user-admin wrapper#1040
Open
mistercrunch wants to merge 2 commits into
Open
sec(sudoers): collapse root wildcards behind agor-user-admin wrapper#1040mistercrunch wants to merge 2 commits into
mistercrunch wants to merge 2 commits into
Conversation
rusackas
pushed a commit
that referenced
this pull request
Apr 19, 2026
…tighten validators Addresses findings from Codex review of PR #1040: 1. BLOCKER: home-base regression - Add optional `--home <dir>` flag to wrapper's add-user verb, with a shape-only validator (`validate_home_dir`) that requires exact /home/<user> or /var/agor/<user> form (no traversal, no control chars, ≤256 bytes). readlink -f is skipped here because the dir does not exist at useradd time. - Thread `homeBase` through `UnixUserCommands.createUser(username, homeDir?)` and both callers (unix-integration-service, ensure-user CLI) so the existing `homeBase` config (defaults to /home but can be overridden to /var/agor) is preserved. 2. shq() duplication → shared escapeShellArg - Extract AGOR_USER_ADMIN constant to new packages/core/src/unix/wrapper-constants.ts so every module that shells out to the wrapper imports from one place. - Drop the local shq() copies in user-manager.ts, group-manager.ts, and symlink-manager.ts; import canonical escapeShellArg from run-as-user.ts. 3. Node-side password validator weaker than wrapper - Strengthen assertChpasswdInputSafe to reject ":" (chpasswd field separator), >256-byte inputs, and non-printable bytes — matching the wrapper's assert_safe_password so defense-in-depth holds at both layers. 4. Wrapper test harness misses adversarial classes - Add Unicode/homoglyph coverage (Cyrillic 'а', full-width 'a', zero-width joiner, RTL-override, BOM, combining marks, NBSP) for usernames, groupnames, and --home paths. - Add argv control-char coverage (newline, CR, tab, leading/ trailing newline) for usernames, groupnames, --home paths, and filesystem-verb paths. - Add NUL-bearing argv case as an observable-outcome test. Tests: user-manager unit tests extended for new createUser signature (58 passing). All 200 unix tests pass. pnpm check green. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
rusackas
pushed a commit
that referenced
this pull request
Apr 19, 2026
…tighten validators Addresses findings from Codex review of PR #1040: 1. BLOCKER: home-base regression - Add optional `--home <dir>` flag to wrapper's add-user verb, with a shape-only validator (`validate_home_dir`) that requires exact /home/<user> or /var/agor/<user> form (no traversal, no control chars, ≤256 bytes). readlink -f is skipped here because the dir does not exist at useradd time. - Thread `homeBase` through `UnixUserCommands.createUser(username, homeDir?)` and both callers (unix-integration-service, ensure-user CLI) so the existing `homeBase` config (defaults to /home but can be overridden to /var/agor) is preserved. 2. shq() duplication → shared escapeShellArg - Extract AGOR_USER_ADMIN constant to new packages/core/src/unix/wrapper-constants.ts so every module that shells out to the wrapper imports from one place. - Drop the local shq() copies in user-manager.ts, group-manager.ts, and symlink-manager.ts; import canonical escapeShellArg from run-as-user.ts. 3. Node-side password validator weaker than wrapper - Strengthen assertChpasswdInputSafe to reject ":" (chpasswd field separator), >256-byte inputs, and non-printable bytes — matching the wrapper's assert_safe_password so defense-in-depth holds at both layers. 4. Wrapper test harness misses adversarial classes - Add Unicode/homoglyph coverage (Cyrillic 'а', full-width 'a', zero-width joiner, RTL-override, BOM, combining marks, NBSP) for usernames, groupnames, and --home paths. - Add argv control-char coverage (newline, CR, tab, leading/ trailing newline) for usernames, groupnames, --home paths, and filesystem-verb paths. - Add NUL-bearing argv case as an observable-outcome test. Tests: user-manager unit tests extended for new createUser signature (58 passing). All 200 unix tests pass. pnpm check green. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
db829f1 to
20a19de
Compare
Replaces five wildcard NOPASSWD rules (useradd *, userdel *, usermod *,
chpasswd, groupadd *, groupdel *, gpasswd *, find *) with a single
audited wrapper at /usr/local/sbin/agor-user-admin and one sudoers line:
agor ALL=(root) NOPASSWD: /usr/local/sbin/agor-user-admin
The wrapper exposes a fixed verb set (add-user, delete-user, lock-user,
unlock-user, add-group, delete-group, add-to-group, remove-from-group,
set-password, setgid-tree, list-symlinks, prune-all-symlinks,
prune-broken-symlinks) and re-validates every argument:
- usernames/groupnames: ^[a-z_][a-z0-9_-]{0,31}$ + system-account deny-list
- paths: readlink -f canonicalization + allowlist (/home/*/agor/*,
/home/*/.agor/*, /var/agor/*) — symlink escapes resolve and fail
- passwords (chpasswd stdin): no NUL/CR/LF/":" injection, printable
ASCII only, ≤256 bytes
- end-of-options (--) on every shell-out to defeat flag-smuggling
Every successful invocation is audited to syslog (logger -t
agor-user-admin). Node-side validators in user-manager.ts remain as
defense-in-depth.
Node callers (user-manager, group-manager, symlink-manager,
unix-integration-service, executor/commands/unix, ensure-user CLI) and
the postgres entrypoint route through the wrapper. Dockerfile installs
it root-owned 0755. Tests updated for new command shapes; bash test
harness exercises validators against adversarial inputs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tighten validators Addresses findings from Codex review of PR #1040: 1. BLOCKER: home-base regression - Add optional `--home <dir>` flag to wrapper's add-user verb, with a shape-only validator (`validate_home_dir`) that requires exact /home/<user> or /var/agor/<user> form (no traversal, no control chars, ≤256 bytes). readlink -f is skipped here because the dir does not exist at useradd time. - Thread `homeBase` through `UnixUserCommands.createUser(username, homeDir?)` and both callers (unix-integration-service, ensure-user CLI) so the existing `homeBase` config (defaults to /home but can be overridden to /var/agor) is preserved. 2. shq() duplication → shared escapeShellArg - Extract AGOR_USER_ADMIN constant to new packages/core/src/unix/wrapper-constants.ts so every module that shells out to the wrapper imports from one place. - Drop the local shq() copies in user-manager.ts, group-manager.ts, and symlink-manager.ts; import canonical escapeShellArg from run-as-user.ts. 3. Node-side password validator weaker than wrapper - Strengthen assertChpasswdInputSafe to reject ":" (chpasswd field separator), >256-byte inputs, and non-printable bytes — matching the wrapper's assert_safe_password so defense-in-depth holds at both layers. 4. Wrapper test harness misses adversarial classes - Add Unicode/homoglyph coverage (Cyrillic 'а', full-width 'a', zero-width joiner, RTL-override, BOM, combining marks, NBSP) for usernames, groupnames, and --home paths. - Add argv control-char coverage (newline, CR, tab, leading/ trailing newline) for usernames, groupnames, --home paths, and filesystem-verb paths. - Add NUL-bearing argv case as an observable-outcome test. Tests: user-manager unit tests extended for new createUser signature (58 passing). All 200 unix tests pass. pnpm check green. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
20a19de to
e279a24
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces five wildcard
NOPASSWDsudoers rules (useradd *,userdel *,usermod *,chpasswd,groupadd */groupdel */gpasswd *,find *) with a single audited wrapper at/usr/local/sbin/agor-user-adminand one sudoers line:Each wildcard was a well-known root-shell path under sudo (flag-smuggling, hashed-password injection,
find -exec). Funneling through one validated entry point closes those avenues while keeping the legitimate functionality the daemon needs.What's in the wrapper
Fixed verb set:
add-user,delete-user [--remove-home],lock-user,unlock-user,add-group,delete-group,add-to-group,remove-from-group,set-password,setgid-tree,list-symlinks,prune-all-symlinks,prune-broken-symlinks.Validators (re-run inside the wrapper, even though Node-side validators stay as defense-in-depth):
^[a-z_][a-z0-9_-]{0,31}$+ system-account deny-list (root, daemon, sudo, wheel, agor, agor_executor, …)readlink -fcanonicalization + allowlist (/home/*/agor/*,/home/*/.agor/*,/var/agor/*); symlink escapes resolve and failNUL/CR/LF/":"(chpasswd field separator), printable ASCII only, ≤256 bytes--end-of-optionslogger -t agor-user-admin(auth.info)Node-side wiring
packages/core/src/unix/{user,group,symlink}-manager.tsroute throughAGOR_USER_ADMINpackages/executor/src/commands/unix.ts(syncUnixPassword) replaces directsudo -n /usr/sbin/chpasswdspawn withsudo -n agor-user-admin set-password <user>apps/agor-cli/src/commands/admin/ensure-user.tsanddocker/docker-entrypoint-postgres.shupdateddocker/Dockerfileinstalls the wrapper root-owned, mode 0755Tests
docker/sudoers/tests/agor-user-admin.test.shexercises validators against adversarial inputs (flag-shaped names, path escapes, CRLF/:/non-printable passwords, system-account deny-list)Test plan
pnpm checkpasses (typecheck + lint + build) — verified locallypnpm --filter @agor/core test src/unix/— 198/198 passing locallybash docker/sudoers/tests/agor-user-admin.test.shinside a root container (e.g.docker run --rm -v "$PWD:/src" -w /src debian:bookworm-slim bash docker/sudoers/tests/agor-user-admin.test.sh)sudo -n /usr/local/sbin/agor-user-adminshould exit 64 with usage;sudo -n /usr/local/sbin/agor-user-admin add-user rootshould exit 65🤖 Generated with Claude Code