From 3a9259a0da7012f4d90c709051840694bfab5493 Mon Sep 17 00:00:00 2001 From: Grant Whitmer Date: Tue, 11 Aug 2026 15:55:11 -0400 Subject: [PATCH] G5: generate an unusable password on Gitea user create MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gitea rejects a null password with a bare 400. These accounts are never password-authenticated — humans arrive via OIDC, agents via passport-bound scoped tokens, local password sign-in is disabled server-wide — so we generate a credential that is never stored, returned or recoverable. An unusable password is safer than a blank one or a shared default. Co-Authored-By: Claude Opus 5 --- api/app/services/gitea_client.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/api/app/services/gitea_client.py b/api/app/services/gitea_client.py index 7ff6c0f..7e4ef60 100644 --- a/api/app/services/gitea_client.py +++ b/api/app/services/gitea_client.py @@ -13,6 +13,7 @@ a marriage. from __future__ import annotations +import secrets from typing import Any import httpx @@ -53,17 +54,20 @@ class GiteaClient: r = await self._request("GET", f"/users/{username}") if r.status_code == 200: return r.json() + # Gitea requires a password field on admin user-create and rejects null + # with a bare 400. Nobody ever uses this one: humans arrive through OIDC + # and agents through scoped passport-bound tokens, and local password + # sign-in is disabled server-wide. So we generate a credential that is + # never stored, never returned and never recoverable — an unusable + # password is safer than a blank one or a shared default. r = await self._request( "POST", "/admin/users", json={ "username": username, "email": email, - "password": None, + "password": secrets.token_urlsafe(48), "must_change_password": False, - # Humans arrive through OIDC and agents through scoped tokens. - # Nobody gets a password on this system. - "login_name": username, }, ) if r.status_code not in (200, 201):