GitHub
10/27/2026, 9:31 AMadmin or service to false did nothing. The API only demotes when not_admin / not_service is sent, and the shell store never sent them. DB mode and osctrl-cli user edit --non-admin did demote.
• CLI DB-mode env add only refused UUID-shaped names. It did not lowercase or check characters, unlike the API create path, so it could create environments the API would refuse.
Change
• Shell (API mode). EditUserField now sends the request through EditUserReq with NotAdmin: !admin and NotService: !service, the same client path as user edit --non-admin. The shell starts from the user's current flags, so editing an email, full name or password does not demote anyone. The server's self-demotion and last-super-admin refusals apply.
• env add (DB mode). The name is trimmed and lowercased, then checked with environments.NewEnvNameFilter, exactly as the API create handler does. This also refuses UUID-shaped names. The Exists check and the rest of the command use the canonical name. EnvNameFilter is unchanged. The shell store has no create-environment path.
Operator-visible changes
• Shell in API mode: admin=false and service=false now take effect. Demoting yourself or the last super-admin is refused with 403.
• service=false had the same bug and is fixed too, beyond what was first asked.
• `env add --db`: Dev now creates dev, and the output shows the stored name. Names with spaces, special characters or a UUID shape are refused with invalid environment name "...": use lowercase letters, numbers, dashes and underscores, and not a UUID.
Security / operational impact
• Closes a DB/API behaviour gap that let the CLI create environments the API would reject.
• Makes shell demotion work in API mode, with the same guards as everywhere else.
Validation
• go test ./cmd/cli/... ./cmd/api/... ./pkg/... -count=1, go vet ./..., gofmt, golangci-lint, make openapi-check.
• New tests, each seen failing first:
• `TestAPIStoreEditUserFieldDemotes`: demote and re-promote admin, service true and false, self-demote refused, last-admin demote refused.
• `TestEnvAddNameValidationParity`: invalid names refused in both DB and API mode with nothing created; " MixedDB " becomes mixeddb in DB mode; MIXEDDB is then refused as existing.
• No existing tests changed.
Not in this PR
• The API also validates the host name, icon and type on environment create. DB-mode --host still has no character check.
jmpsec/osctrlGitHub
10/27/2026, 9:40 AM