Skip to content

Keep server secrets out of the webapp and admin-dashboard containers #403

Closed

Description

@HMarzban

Summary

The production compose file gives the webapp and the admin dashboard the whole .env.production through env_file. Neither app uses the server secrets in it. So both containers hold SUPABASE_SERVICE_ROLE_KEY (bypasses all RLS), DATABASE_URL and the storage secret for no reason. Any env leak in a Next app then becomes a full database compromise.

  • Severity: Medium
  • Area: infra (docker-compose.prod.yml)
  • Source: security review of 2026-10-06, finding M5. Confirmed by reading every process.env use in both apps.

Where

  • docker-compose.prod.yml:484 (webapp) — env_file: .env.production
  • docker-compose.prod.yml:582 (admin-dashboard) — env_file: .env.production

What each app really reads at runtime

  • webapp: NEXT_PUBLIC_*, NODE_ENV, NEXT_PHASE, SERVER_RESTAPI_URL, SUPABASE_URL. SENTRY_* is read only at build time in apps/webapp/next.config.js. The only server route is apps/webapp/src/pages/api/health.ts.
  • admin-dashboard: its NEXT_PUBLIC_* keys and NODE_ENV.

Re-check with grep -rn "process.env\." apps/webapp/src apps/webapp/next.config.js apps/admin-dashboard/src before you change anything.

Fix plan

  1. Remove env_file: .env.production from the webapp and admin-dashboard services.
  2. List the needed keys under each service's environment: block.
  3. Add SUPABASE_URL to the webapp block. Today it arrives only through env_file (apps/webapp/src/utils/supabase/url.ts:3-4). Without it, server-side rendering falls back to the public URL.
  4. Rename NEXT_PUBLIC_SPACES_SECRET. The NEXT_PUBLIC_ prefix makes Next inline it into the client bundle the day any client code reads it. Update every reader in the same change.
  5. Keep build-time args (NEXT_PUBLIC_* used at build) as they are in the Dockerfile.

Acceptance criteria

  • docker exec <webapp> env shows no SUPABASE_SERVICE_ROLE_KEY, DATABASE_URL or storage secret.
  • Same for the admin-dashboard container.
  • Webapp SSR, sign-in, documents and chat work in staging.
  • Admin dashboard sign-in and pages work in staging.

Note

Deploy to staging first (.github/workflows/stage.docs.plus.yml). A missing key shows only at runtime.


Generated by Claude Code

Activity

  1. added theissue type on Oct 6, 2026
  2. added a commit that references this issue on Oct 6, 2026
    1db1bd3
  3. HMarzban commented on Oct 6, 2026

    @HMarzban
    CollaboratorAuthor

    Fixed in 1db1bd3. Both services already listed every runtime key they read, so the change is only: remove env_file, and add SUPABASE_URL to the webapp. NEXT_PUBLIC_SPACES_SECRET has no reader anywhere, so its line was removed from .env.example instead of being renamed.

    Before closing: after the staging deploy, check webapp SSR, sign-in, documents and chat, and the admin pages. A missing key shows only at runtime.

    Related, not changed here: NEXT_PUBLIC_ELASTIC_APM_SECRET_TOKEN in .env.example carries the same NEXT_PUBLIC_ prefix risk.


    Generated by Claude Code

  4. changed the title [-][Security] Webapp and admin-dashboard containers receive the full production env, including the service-role key[/-] [+]Keep server secrets out of the webapp and admin-dashboard containers[/+] on Oct 6, 2026
  5. added
    SecuritySecurity, access control, and data exposure
    and removed
    bugSomething isn't working
    on Oct 6, 2026
  6. HMarzban commented on Oct 9, 2026

    @HMarzban
    CollaboratorAuthor

    Reopened: the board automation closed this issue before the push. The code is on main through merge 083f37d84. It stays open until the staging check in the acceptance criteria passes: docker exec env, SSR, sign-in, documents, chat and the admin pages.

  7. HMarzban commented on Oct 12, 2026

    @HMarzban
    CollaboratorAuthor

    Verified on production, 2026-10-12

    The fix (1db1bd3, merged as 083f37d84) has been live since the 2026-10-09 deploy. The latest green deploy is run 38107083947.

    • docker-compose.prod.yml: neither webapp nor admin-dashboard has an env_file, and neither environment: block names a secret.
    • A read-only check printed only set or unset, never a value. In docsplus-webapp-57, docsplus-webapp-58 and docsplus-admin-dashboard-29, SUPABASE_SERVICE_ROLE_KEY and DATABASE_URL are unset. The webapp has SUPABASE_URL.
    • Prod has served SSR, sign-in, documents, chat and the admin pages for three days on this config.

    The staging step was replaced by prod running cleanly for three days. Closing.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    DevOpsSecuritySecurity, access control, and data exposure

    Type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions