Skip to content

Hardcoded admin demo credentials are reseeded on every startup #4

Description

@shunfeng8421

Summary

app/database.py hardcodes three demo accounts, including an admin account with a plaintext password, and initialize() unconditionally replaces password_hash for those accounts on every startup (ON CONFLICT(id) DO UPDATE SET ... password_hash = excluded.password_hash). Combined with the published 0.0.0.0:8000 bind, a network-reachable deployment can be logged into with a password that is public in the source tree, and the login endpoint implements no rate limiting or lockout.

Affected behavior

  • DEMO_USERS in app/database.py contains ("john", ..., "Northstar-John-2026!", "admin") (passwords literal in source).
  • On every boot the seed block upserts the demo rows and overwrites password_hash and role, so even an operator who changed an account's password has it reset at next restart.
  • compose.yaml runs uvicorn app.main:app --host 0.0.0.0 --port 8000 and publishes 8000:8000.
  • POST /api/auth/login has no visible rate limiting / account lockout / IP throttle.
  • The admin role grants admin_configuration, which manages the team capability layer: HTTP function tools (with optional stored headers via RELAY_TOOL_HEADER_ENV_ALLOWLIST), remote MCP server configuration, prompt templates and documents.

Verification (local, network-isolated: bound to 127.0.0.1:18099, no real service contacted)

  1. POST /api/auth/login with john@northstar.consulting / Northstar-John-2026! → 200, "role":"admin", capabilities.admin_configuration=true.
  2. POST /api/admin/configuration/function-tools:
    • as seeded member mary → 403 (confirming the endpoint is role-protected; this is a known-default-credential issue, not an unauthenticated one),
    • as seeded admin john with a harmless HTTPS URL → 201 (tool row created; deleted immediately afterwards, no execution).
  3. Password reseed proof: changed john's password_hash in the local SQLite file to RESET_PROOF_DISABLED, restarted the service, logged in again with the documented password → 200, admin identity restored.

Full reproduction steps are in the local verification log referenced at the bottom of this report.

Impact

Any deployment reachable over the network is loggable with a source-documented admin credential, and that credential can manage the capability layer (outbound HTTP tools, remote MCP servers, stored secrets). This is a known/mineable credential exposure plus a guaranteed-persistence mechanism (restart restores the password), not an unauthenticated RCE: host code execution was not established, and the project labels the credentials as intentionally demo-only.

Suggested fixes

  • Make demo-account seeding opt-in (e.g. only when RELAY_SEED_DEMO=1) and never overwrite an existing user's password_hash/role.
  • Use per-deployment random admin credentials, or require the operator to set them explicitly and fail startup otherwise.
  • Add rate limiting / lockout to /api/auth/login.
  • Document deployment warnings for the 0.0.0.0 bind and published port.

References

  • app/database.py lines ~17-26 (DEMO_USERS), ~400-412 (startup upsert)
  • app/main.py /api/auth/login, admin configuration routes with require_admin
  • compose.yaml

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

bugSomething isn't working

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions