From 6f8af2cfa465d0fb1c6bbc402fb3984224fa3733 Mon Sep 17 00:00:00 2001 From: Maksim Shulga Date: Mon, 3 Aug 2026 17:59:38 +0000 Subject: [PATCH] Reject UID or GID zero unless a documented use case requires it (#32) * Reject UID or GID zero unless a documented use case requires it setup_uid() would happily remap the seafile user to UID/GID 0, which after the previous non-root fix would mean drop_privileges() drops straight back into root. main() now rejects --uid/SEAFILE_UID and --gid/SEAFILE_GID being 0 with exit code 2, before setup_uid() runs. Verified against the real image via tests/smoke.sh. * Clarify the GID=0 rejection message Copilot review: 'group root' isn't a commonly used phrase. Name GID 0 as the root group explicitly instead. --------- Co-authored-by: Maxim Shulga --- IMPROVEMENTS.md | 4 +++- start.py | 6 ++++++ tests/smoke.sh | 16 ++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/IMPROVEMENTS.md b/IMPROVEMENTS.md index a0766e8..1b28d3f 100644 --- a/IMPROVEMENTS.md +++ b/IMPROVEMENTS.md @@ -199,7 +199,9 @@ Acceptance criteria: since it can't be invoked from an already non-root process anyway. Verified against the real image (not just mocked unit tests): a running container's PID 1 shows `Uid: 1000 1000 1000 1000` in `/proc/1/status`. -- [ ] Reject UID or GID zero unless a documented use case requires it. +- [x] Reject UID or GID zero unless a documented use case requires it. + `main()` now exits with code 2 and a clear message if `--uid`/`SEAFILE_UID` + or `--gid`/`SEAFILE_GID` resolve to 0, before `setup_uid()` ever runs. - [ ] Add a health check for the daemon and RPC endpoint. - [ ] Handle temporary RPC failures and daemon restarts without corrupting state. diff --git a/start.py b/start.py index d630758..45dc498 100755 --- a/start.py +++ b/start.py @@ -131,6 +131,12 @@ def main(): parser.error("username is not specified") if not args.libraries: parser.error("library is not specified") + if args.uid == 0: + parser.error("uid must not be 0; running the daemon as root defeats " + "the point of --uid/SEAFILE_UID") + if args.gid == 0: + parser.error("gid must not be 0 (the root group); running the daemon " + "with it defeats the point of --gid/SEAFILE_GID") try: password = resolve_secret("PASSWORD", args.password, args.password_file) diff --git a/tests/smoke.sh b/tests/smoke.sh index 7def2f6..906c95d 100755 --- a/tests/smoke.sh +++ b/tests/smoke.sh @@ -139,6 +139,22 @@ else fail "negative limit not reported: $(echo "$out" | tail -1)" fi +out=$(docker run --rm $PLATFORM_ARG -e SERVER_HOST=h -e USERNAME=u -e PASSWORD=p \ + -e LIBRARY_ID=x -e SEAFILE_UID=0 "$IMAGE" 2>&1; echo "rc=$?") +if echo "$out" | grep -q "uid must not be 0" && [ "$(echo "$out" | tail -1)" = "rc=2" ]; then + pass "SEAFILE_UID=0 is rejected" +else + fail "SEAFILE_UID=0 not rejected: $out" +fi + +out=$(docker run --rm $PLATFORM_ARG -e SERVER_HOST=h -e USERNAME=u -e PASSWORD=p \ + -e LIBRARY_ID=x -e SEAFILE_GID=0 "$IMAGE" 2>&1; echo "rc=$?") +if echo "$out" | grep -q "gid must not be 0" && [ "$(echo "$out" | tail -1)" = "rc=2" ]; then + pass "SEAFILE_GID=0 is rejected" +else + fail "SEAFILE_GID=0 not rejected: $out" +fi + echo "== a mounted secret is read and stays out of the environment" printf 's3cret-from-file\n' > "$WORK/pw" out=$(docker run --rm $PLATFORM_ARG -v "$WORK/pw:/run/secrets/pw:ro" \