Skip to content

fix(server): harden self-hosted server — pgvector upgrade, admin auth, endpoint security#5360

Merged
kartik-mem0 merged 10 commits into
mainfrom
fix/upgrade-pgvector-docker-image
Jun 5, 2026
Merged

fix(server): harden self-hosted server — pgvector upgrade, admin auth, endpoint security#5360
kartik-mem0 merged 10 commits into
mainfrom
fix/upgrade-pgvector-docker-image

Conversation

@kartik-mem0
Copy link
Copy Markdown
Contributor

@kartik-mem0 kartik-mem0 commented Jun 3, 2026

Linked Issue

Closes #5127

Summary

Security hardening for the self-hosted Mem0 server across three areas:

1. Replace archived pgvector Docker image

  • ankane/pgvector:v0.5.1pgvector/pgvector:pg17 (PostgreSQL 17.10, pgvector 0.8.2)
  • Require POSTGRES_PASSWORD via ${POSTGRES_PASSWORD:?...} — compose fails fast if unset
  • Fix healthcheck to use ${POSTGRES_USER:-postgres} instead of hardcoded -U postgres
  • Migration guide added in both server/README.md and docs/migration/server-pgvector-upgrade.mdx

2. Require admin role on config and reset endpoints (#5127)

  • POST /configure and POST /reset now use require_admin — non-admin callers get 403
  • Added require_admin FastAPI dependency in server/auth.py that composes on verify_auth and enforces user.role == "admin"
  • ADMIN_API_KEY bootstrap works on fresh empty DB via a _BOOTSTRAP_ADMIN sentinel (stable UUID, never persisted)
  • Read-only endpoints (GET /configure, GET /configure/providers) remain open to all authenticated users

3. Harden destructive and sensitive endpoints

  • DELETE /memories (bulk delete): require_admin — prevents non-admin from wiping memories for any user_id/agent_id/run_id
  • DELETE /entities/{type}/{id}: require_admin — cascade-deletes all memories for an entity
  • GET /requests (audit log): require_admin — full request log is admin-only information
  • GET /memories (no filters): inline admin check — unfiltered listing returns all memories across all users; filtered reads remain open to any authenticated caller

Testing

Admin auth (10/10 edge cases pass):

  • Non-admin API key → POST /configure = 403
  • Non-admin API key → POST /reset = 403
  • Admin JWT → POST /configure = 200
  • Admin JWT → POST /reset = 200
  • Admin-owned API key → POST /configure = 200
  • Non-admin → GET /configure (read-only) = 200
  • No auth → POST /configure = 401
  • Invalid key → POST /configure = 401
  • ADMIN_API_KEY on empty DB → POST /configure = 200 (bootstrap)
  • pgvector vector operations = working

pgvector migration (tested end-to-end):

  1. Built old stack from main (ankane/pgvector:v0.5.1, PG 15.4, pgvector 0.5.1)
  2. Added 9 real memories with embeddings across 3 users + 1 agent via OpenRouter
  3. Ran pg_dumpall backup (185KB)
  4. Switched to PR branch (pgvector/pgvector:pg17, PG 17.10, pgvector 0.8.2)
  5. Restored backup following migration guide (start only postgres → restore → start mem0)
  6. Verified: admin login, API key auth, all 9 memories present, vector search works, config preserved, new memories addable

Breaking Changes

  • POST /configure and POST /reset now require admin role: API keys from non-admin users get 403. Admin JWT and ADMIN_API_KEY still work.
  • DELETE /memories (bulk) and DELETE /entities/{type}/{id} now require admin role: Same as above.
  • GET /requests (audit log) now requires admin role: Non-admin users can no longer read the request log.
  • GET /memories (unfiltered) now requires admin role: Listing all memories across all users requires admin. Filtered reads by user_id/agent_id/run_id remain open.
  • POSTGRES_PASSWORD now required: docker compose up fails if unset in .env.
  • Existing dev volumes: Users with ankane/pgvector volumes need to follow the migration guide (pg_dumpall → restore).

Type of Change

  • Bug fix (non-breaking change that fixes an issue)

Test Coverage

  • I tested manually (10-case admin auth suite + end-to-end pgvector migration)
  • New and existing tests pass locally

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have added tests that prove my fix/feature works
  • New and existing tests pass locally
  • I have updated documentation if needed

…es config

- Replace ankane/pgvector:v0.5.1 (archived, unpatched for 2+ years) with
  pgvector/pgvector:pg17 (actively maintained, pgvector 0.8.2)
- Remove exposed postgres port (8432:5432) — postgres only needs to be
  reachable by the mem0 service on the docker network, not the host
- Require POSTGRES_PASSWORD via .env instead of hardcoding postgres/postgres

The archived ankane image with default credentials and an exposed port made
self-hosted deployments vulnerable to the JINX-0126 cryptomining campaign
that has compromised 1,500+ PostgreSQL servers via COPY FROM PROGRAM.
Add sensible defaults to .env.example matching docker-compose config and
document that POSTGRES_PASSWORD is required (docker-compose will refuse
to start without it).
Restore ports 8432:5432 — developers need host access to postgres for
debugging with psql/pgAdmin/DBeaver. The security risk was the hardcoded
default credentials, not the port exposure itself.
Any authenticated API key holder could modify the global LLM/embedder
configuration or wipe all memories. Add a require_admin dependency that
composes on top of require_auth and enforces user.role == "admin",
returning 403 for non-admin callers.

Closes #5127
- require_admin: handle ADMIN_API_KEY and AUTH_DISABLED on fresh empty DB
  by falling through to allow bootstrap when no users exist yet, instead
  of raising 401 (Finding #1)
- docker-compose healthcheck: use ${POSTGRES_USER:-postgres} instead of
  hardcoded -U postgres so custom POSTGRES_USER values work (Finding #2)
- require_admin: restore request: Request param (needed for auth_type
  check) but now used in function body (Finding #7 resolved)
- docs: sync migration guide password placeholder with README (Finding #9)
The migration guide previously said to start the full stack, then
restore, then restart. This fails because the mem0 API runs alembic
on startup, creating empty tables that conflict with the pg_dumpall
restore (duplicate-key errors silently drop API keys and settings).

Correct procedure: start only postgres → restore → then start mem0.
Tested end-to-end: PG15/pgvector 0.5.1 → PG17/pgvector 0.8.2 with
9 memories, 1 user, 1 API key, 1 config — all preserved.
…n path

When ADMIN_API_KEY authenticates on a fresh empty database, require_admin
previously returned None which broke the User return-type contract. Replace
with a module-level _BOOTSTRAP_ADMIN User instance (not persisted) so
callers always get a real User with role="admin".
SQLAlchemy column defaults (default=_new_uuid, default=_utcnow) only
fire on INSERT, not on bare __init__. The sentinel had id=None and
created_at=None, which would crash any future admin endpoint that
reads user.id. Use a zero-UUID and datetime.min as stable sentinels.
- DELETE /memories (bulk): require_admin — prevents non-admin from
  wiping memories for arbitrary user_id/agent_id/run_id
- DELETE /entities/{type}/{id}: require_admin — cascade-deletes all
  memories for an entity
- GET /requests (audit log): require_admin — exposes request metadata
  for all API calls
- GET /memories (no filters): inline admin check — unfiltered listing
  returns all memories across all users; filtered reads remain open
@kartik-mem0 kartik-mem0 changed the title fix(server): replace archived ankane/pgvector image and harden postgres config fix(server): harden self-hosted server — pgvector upgrade, admin auth, endpoint security Jun 5, 2026
@kartik-mem0 kartik-mem0 merged commit ae7f406 into main Jun 5, 2026
10 checks passed
@kartik-mem0 kartik-mem0 deleted the fix/upgrade-pgvector-docker-image branch June 5, 2026 11:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Missing Authorization on POST /configure Allows Any API Key Holder to Hijack Global LLM Configuration

2 participants