security(auth): write initial admin password to 0600 file instead of logs

CodeQL py/clear-text-logging-sensitive-data flagged 3 call sites that
logged the auto-generated admin password to stdout via logger.info().
Production log aggregators (ELK/Splunk/etc) would have captured those
cleartext secrets. Replace with a shared helper that writes to
.deer-flow/admin_initial_credentials.txt with mode 0600, and log only
the path.

New file
--------
- app/gateway/auth/credential_file.py: write_initial_credentials()
  helper. Takes email, password, and a "initial"/"reset" label.
  Creates .deer-flow/ if missing, writes a header comment plus the
  email+password, chmods 0o600, returns the absolute Path.

Modified
--------
- app/gateway/app.py: both _ensure_admin_user paths (fresh creation
  + needs_setup password reset) now write to file and log the path
- app/gateway/auth/reset_admin.py: rewritten to use the shared ORM
  repo (SQLiteUserRepository with session_factory) and the
  credential_file helper. The previous implementation was broken
  after the earlier ORM refactor — it still imported _get_users_conn
  and constructed SQLiteUserRepository() without a session factory.

No tests changed — the three password-log sites are all exercised
via existing test_ensure_admin.py which checks that startup
succeeds, not that a specific string appears in logs.

CodeQL alerts 272, 283, 284: all resolved.
This commit is contained in:
greatmengqi
2026-04-08 12:58:42 +08:00
parent ea73db6fc1
commit e7a881b577
3 changed files with 113 additions and 47 deletions
+7 -4
View File
@@ -87,6 +87,7 @@ async def _ensure_admin_user(app: FastAPI) -> None:
age = time.time() - admin.created_at.replace(tzinfo=UTC).timestamp()
if age >= 30:
from app.gateway.auth.credential_file import write_initial_credentials
from app.gateway.auth.password import hash_password_async
password = secrets.token_urlsafe(16)
@@ -94,10 +95,10 @@ async def _ensure_admin_user(app: FastAPI) -> None:
admin.token_version += 1
await provider.update_user(admin)
cred_path = write_initial_credentials(admin.email, password, label="reset")
logger.info("=" * 60)
logger.info(" Admin account setup incomplete — password reset")
logger.info(" Email: %s", admin.email)
logger.info(" Password: %s", password)
logger.info(" Credentials written to: %s (mode 0600)", cred_path)
logger.info(" Change it after login: Settings -> Account")
logger.info("=" * 60)
@@ -119,10 +120,12 @@ async def _ensure_admin_user(app: FastAPI) -> None:
logger.exception("LangGraph thread migration failed (non-fatal)")
if fresh_admin_created:
from app.gateway.auth.credential_file import write_initial_credentials
cred_path = write_initial_credentials(admin.email, password, label="initial") # noqa: F821 — defined in the fresh_admin branch
logger.info("=" * 60)
logger.info(" Admin account created on first boot")
logger.info(" Email: %s", admin.email)
logger.info(" Password: %s", password) # noqa: F821 — defined in the fresh_admin branch
logger.info(" Credentials written to: %s (mode 0600)", cred_path)
logger.info(" Change it after login: Settings -> Account")
logger.info("=" * 60)