Skip to content

sec: externalize hardcoded secrets in docker-compose.yml - #330

Open
raaidrushdy wants to merge 2 commits into
mainfrom
sec/externalize-compose-secrets
Open

sec: externalize hardcoded secrets in docker-compose.yml#330
raaidrushdy wants to merge 2 commits into
mainfrom
sec/externalize-compose-secrets

Conversation

@raaidrushdy

Copy link
Copy Markdown
Contributor

Summary

Removes three hardcoded credentials from docker-compose.yml (POSTGRES_PASSWORD, SECRET_KEY, ENCRYPTION_KEY) and replaces them with ${VAR:?} references sourced from a local, gitignored .env file. The previously-exposed Fernet ENCRYPTION_KEY has been rotated, the new value only exists in local .env files, never in version control. env.example is updated with the three new required variables and generation instructions for each.

Type of Change

  • Security

Affected Components

  • /infrastructure (docker-compose.yml is root-level dev infra config)

Motivation

Covers Planner task 26T2-DEV-RR-002 (Workstream 4). Follows directly from the baseline audit (26T2-SEC-RR-001), which documented three real credentials sitting in plaintext in docker-compose.yml, including a live Fernet key actively used to encrypt stored M365/AWS/Azure/GCP credentials at rest. This closes that finding by moving all three to environment variables that are never committed.

Testing Done

  • Tested manually — describe how: Generated three new values locally (Fernet key, SECRET_KEY, Postgres password), populated a local .env, and ran docker compose --profile all up --build -d with a fresh volume. Confirmed Postgres authentication succeeded with the new .env-sourced password (proving the ${VAR} substitution works correctly) before hitting an unrelated, pre-existing Alembic "multiple head revisions" migration error. Verified this error is not caused by this change by reproducing it against unmodified main as well.

Security Considerations

This is the core fix for the credential-exposure finding in the Week 4/5 baseline audit. The old ENCRYPTION_KEY value is treated as compromised (it was exposed in git history) and has been rotated, not just relocated; the new key only exists in local .env files. .env is already covered by .gitignore. env.example keeps blank placeholders only, with generation commands documented as comments.

Breaking Changes

  • Yes — describe below:
    Anyone running the stack locally now needs a .env file (cp env.example .env) populated with POSTGRES_PASSWORD, SECRET_KEY, and ENCRYPTION_KEY before docker compose up will work; previously, these had working defaults baked into docker-compose.yml. Generation commands for each are documented in env.example.

Rollback Plan

  • Revert commit is sufficient

Checklist

  • Code follows project conventions
  • No secrets, credentials, or tokens committed
  • Relevant documentation updated (if applicable)
  • CI/CD workflows pass on this branch
  • PR is focused on one thing

Screenshots

N/A — infra config change, no UI impact.

@raaidrushdy
raaidrushdy requested a review from a team as a code owner August 16, 2026 14:51
@github-actions

Copy link
Copy Markdown
Contributor

Preview Environment

A preview environment can be spun up on demand for this PR.

Action Label Includes
Spin up preview deploy-preview Frontend, backend, database, Redis, OPA, worker
Spin up preview with M365 deploy-preview-m365 Everything above + PowerShell service for Exchange/Teams scan testing
Tear down preview teardown-preview Stops the environment early

The environment will also be torn down automatically when the PR is closed or merged.
Preview URLs will appear in a follow-up comment once the deploy completes (~5–8 min).
M365 scans require real tenant credentials added through the frontend UI.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f798130222

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread docker-compose.yml

# Required: Database connection (PostgreSQL with asyncpg driver)
- DATABASE_URL=postgresql+asyncpg://autoaudit:autoaudit_dev_password@db:5432/autoaudit
- DATABASE_URL=postgresql+asyncpg://autoaudit:${POSTGRES_PASSWORD}@db:5432/autoaudit

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Migrate passwords for existing PostgreSQL volumes

For developers upgrading with an existing postgres_data volume, the Postgres image does not reapply POSTGRES_PASSWORD after the data directory has been initialized, but this URL immediately makes the backend authenticate with the newly generated .env value. Unless users retain the old exposed password, both application services will fail authentication after the upgrade; provide a password migration/reset step or explicitly recreate the development volume before switching the connection URLs.

Useful? React with 👍 / 👎.

Comment thread docker-compose.yml
# Generate with: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
# This will be passed in with a secret provider in production. This is for local use only.
- ENCRYPTION_KEY=Ps-HiS3ww5QzQPc_Mdu5-JyA_jCNbdFHMdiwWSlAfgM=
- ENCRYPTION_KEY=${ENCRYPTION_KEY:?ENCRYPTION_KEY not set — see env.example}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve access to credentials encrypted with the old key

When an existing installation replaces the committed Fernet key with a newly generated ENCRYPTION_KEY, credentials already stored in its persistent PostgreSQL volume remain encrypted with the old key. Calls through backend-api/app/services/encryption.py::decrypt and engine/worker/db.py::decrypt will then raise InvalidToken, preventing saved connections and scans from working; migrate/re-encrypt those values or document a required credential reset before rotating the key.

Useful? React with 👍 / 👎.

Comment thread docker-compose.yml

# Required: Database connection (PostgreSQL with asyncpg driver)
- DATABASE_URL=postgresql+asyncpg://autoaudit:autoaudit_dev_password@db:5432/autoaudit
- DATABASE_URL=postgresql+asyncpg://autoaudit:${POSTGRES_PASSWORD}@db:5432/autoaudit

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep reserved characters out of the database URL

If POSTGRES_PASSWORD contains URI-reserved characters such as @, /, : or %, interpolating it verbatim into this SQLAlchemy URL changes how the authority is parsed even though PostgreSQL receives the literal password from its environment. Since the new template neither encodes nor constrains this value, an otherwise valid generated password can prevent both backend and worker database connections; use a URL-encoded companion value or document a URL-safe generation command.

Useful? React with 👍 / 👎.

Comment thread env.example
GOOGLE_OAUTH_CLIENT_SECRET= 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

# Postgres (used by db, backend-api, worker)
POSTGRES_PASSWORD=

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Make the documented Compose quick start runnable

For a new checkout following docs/GETTING_STARTED.md, the documented sequence is only cp env.example .env followed by docker compose --profile all up, but these newly required entries remain empty and Compose's ${VAR:?…} expressions reject empty values before starting anything. Update the onboarding instructions to require populating all three values—also providing a PostgreSQL password generation command—or supply safe development defaults through a non-secret mechanism.

Useful? React with 👍 / 👎.

@raaidrushdy

Copy link
Copy Markdown
Contributor Author

26T2-DEV-RR-002 - Testing & Evidence

What I did

I removed three hardcoded credentials from docker-compose.yml, POSTGRES_PASSWORD,
SECRET_KEY, and ENCRYPTION_KEY, and replaced them with environment variable
references (${VAR:?message} syntax) that pull from a local .env file, which is
gitignored and never committed. Updated env.example (the safe, blank template that
IS committed) with the three new required variables and generation commands for each.
Since the old ENCRYPTION_KEY had already been exposed in git history, I rotated it
rather than just moving the same value; the new key exists only in my local .env.

Why

This closes the primary finding from my Week 4/5 baseline audit
(26T2-SEC-RR-001): three real credentials sitting in plaintext in a file every repo
collaborator can read, including a live Fernet key actively encrypting stored
M365/AWS/Azure/GCP credentials at rest.

How I tested it

  1. Generated three new values locally (Fernet key via Python's cryptography
    library, a 32-byte hex SECRET_KEY, a 16-byte hex Postgres password) and saved
    them into a local .env, never committed, never pasted anywhere outside my
    own terminal/editor.

  2. Edited docker-compose.yml, replacing all six hardcoded occurrences (the
    password appears twice, standalone and inside DATABASE_URL, and the
    encryption key appears in both the backend-api and worker services since they
    need to share it).

  3. Updated env.example with the three new variables as blank placeholders plus
    generation instructions.

  4. Installed Docker Desktop locally (wasn't previously set up on this machine) to
    run the full stack.

  5. First test run failed with required variable POSTGRES_PASSWORD is missing a value, traced this to .env not actually existing yet (I'd generated the
    values but hadn't written them to a file). Created .env properly, verified
    all three variables were set without printing the actual secret values to the
    terminal (grep -q checks instead of cat).

  6. Second test run: backend-api container crash-looped. Logs showed
    Multiple head revisions are present from Alembic, initially unclear whether
    this was caused by my changes.

  7. Isolated the cause by stashing my changes and re-testing against unmodified
    main, hit a different error there (password authentication failed),
    which on inspection turned out to be because the Postgres data volume had
    already been initialised with my new password from the earlier test run, so
    reverting the compose file created a mismatch. This actually confirmed my
    .env substitution had worked correctly on the first attempt.

  8. Restored my changes (git stash pop), wiped the Postgres volume
    (docker compose down -v) for a clean initialisation, and rebuilt from
    scratch. Postgres authentication succeeded immediately with the new
    .env-sourced password.

  9. The same Multiple head revisions Alembic error reappeared on this clean run.
    Since it now occurred with a fresh database, the correct password, and no
    other variables changed, this confirmed the migration error is a pre-existing
    issue in the repo's Alembic migration history
    , unrelated to this change —
    not something introduced or something I need to fix as part of this task.

Result

  • POSTGRES_PASSWORD, SECRET_KEY, and ENCRYPTION_KEY are no longer hardcoded
    anywhere in version control.

  • Verified via successful Postgres authentication that the .env-based
    substitution works correctly end-to-end.

  • db, redis, opa, frontend, worker, and powershell-service all start
    healthy with the new setup. backend-api reaches the migration step
    successfully (proving my change works) before hitting the separate,
    pre-existing Alembic issue.

Related finding raised separately (not fixed here, out of scope for this task)

The Alembic "multiple head revisions" error blocks a clean first-time
docker compose up for any new contributor, regardless of branch. Flagging this to
the team as a new item since it's a real onboarding blocker, but it's a migration
history problem, not a secrets problem, and shouldn't be bundled into a
security-focused PR.

@raaidrushdy

Copy link
Copy Markdown
Contributor Author

addressed codex review

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.

1 participant