Skip to content

fix(security): remove hardcoded JWT secret from docker-compose#68

Merged
Kingvic300 merged 1 commit into
mainfrom
fix/25-compose-secrets
Jun 7, 2026
Merged

fix(security): remove hardcoded JWT secret from docker-compose#68
Kingvic300 merged 1 commit into
mainfrom
fix/25-compose-secrets

Conversation

@Kingvic300

@Kingvic300 Kingvic300 commented Jun 7, 2026

Copy link
Copy Markdown
Owner

Summary

docker-compose.yml committed a real, fixed JWT_SECRET (dev-secret-key-at-least-32-chars-long-12345) and a BREVO_API_KEY. Since the API signs/verifies JWTs with that value, anyone who has seen the repo could forge a valid token for any user.

Changes

  • Remove the inline JWT_SECRET and BREVO_API_KEY (and the unused SUI_CLI_EMAIL) from the api service.
  • Source secrets from the untracked env_file (backend/api/.env, copied from .env.example).
  • Add a comment; the API already fails fast when JWT_SECRET is missing/<32 chars (config.rs).

Follow-up

  • The previously committed secret should be treated as compromised and rotated.

Closes #25

Summary by CodeRabbit

  • Chores
    • Updated deployment configuration to improve how environment variables and secrets are managed

docker-compose.yml shipped a real, fixed JWT_SECRET and a BREVO_API_KEY
in version control. Anyone with repo access could forge a valid token for
any user. Drop the inline secrets and source them from the untracked
env_file (backend/api/.env); the API already fails fast when JWT_SECRET
is unset or <32 chars.

Note: the previously committed secret should be considered burned and
rotated.

Closes #25
@vercel

vercel Bot commented Jun 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
txio Ready Ready Preview, Comment Jun 7, 2026 11:48am

@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR moves hardcoded secrets from inline docker-compose.yml environment variables to an external .env file, mitigating accidental secret exposure. Non-secret configuration remains inline, with comments documenting the new structure.

Changes

Secrets externalization from docker-compose

Layer / File(s) Summary
API service env_file configuration
docker-compose.yml
Secret environment variables (JWT_SECRET, BREVO_API_KEY, SUI_CLI_EMAIL) are removed from inline environment: and loaded via env_file: ./backend/api/.env; non-secret runtime variables (MONGO_URI, RUST_LOG, PORT) remain explicit, with comments clarifying the new secrets sourcing pattern and JWT validation behavior.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~5 minutes

Poem

🐰 The secrets hop away to files unseen,
No hardcoded keys in docker-compose keen!
From inline to env, they find their home,
Safe in the .env where they'll freely roam.
The rabbit cheers—no more tokens exposed! 🔐

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR addresses most key requirements from issue #25: removes hardcoded JWT/BREVO secrets from docker-compose, configures env_file loading, and documents fast-fail validation. However, it does not implement rejection of known/default values as requested in the requirements. Add validation logic to reject known/default JWT_SECRET values in addition to the existing presence and length checks, or create a follow-up issue if deferred.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and concisely describes the main security change: removing a hardcoded JWT secret from docker-compose configuration.
Out of Scope Changes check ✅ Passed All changes are directly related to addressing the hardcoded secrets issue in docker-compose.yml with no unrelated modifications.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/25-compose-secrets

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
docker-compose.yml (1)

15-19: Operational reminder: Rotate the previously committed JWT_SECRET.

As noted in the PR objectives, the JWT_SECRET dev-secret-key-at-least-32-chars-long-12345 was previously committed to version control and should be treated as compromised. Ensure it is rotated and never used in any deployment environment (dev, staging, production).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docker-compose.yml` around lines 15 - 19, The committed JWT_SECRET must be
rotated and removed from any repo and deployments: generate a new
cryptographically secure secret (>=32 chars), replace the compromised value in
backend/api/.env and any deployment/CI environment variables (not in source),
update .env.example to not contain real secrets, ensure env_file in
docker-compose.yml still points to ./backend/api/.env, add backend/api/.env to
.gitignore if not already, revoke/replace the old secret wherever it was used
(deployments, tokens, API keys), and verify config.rs (JWT_SECRET consumer) and
deployment manifests use the new secret from the env file rather than any
hardcoded value.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docker-compose.yml`:
- Around line 15-19: The JWT_SECRET validation currently only checks presence
and minimum length in backend/api/src/utils/config.rs; add an explicit denylist
check (or an entropy/format check) inside the same validation path (e.g., in the
function that reads/validates JWT_SECRET such as the config load/validate
function) so that known defaults from .env.example (and other common
placeholders) are rejected even if length >= 32; implement by creating a small
denylist set of strings (include documented defaults), perform the denylist
check immediately after the len() check and return an error/exit if matched, and
update any error message to instruct rotation of previously committed/burned
secrets so the team rotates them.

---

Nitpick comments:
In `@docker-compose.yml`:
- Around line 15-19: The committed JWT_SECRET must be rotated and removed from
any repo and deployments: generate a new cryptographically secure secret (>=32
chars), replace the compromised value in backend/api/.env and any deployment/CI
environment variables (not in source), update .env.example to not contain real
secrets, ensure env_file in docker-compose.yml still points to
./backend/api/.env, add backend/api/.env to .gitignore if not already,
revoke/replace the old secret wherever it was used (deployments, tokens, API
keys), and verify config.rs (JWT_SECRET consumer) and deployment manifests use
the new secret from the env file rather than any hardcoded value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4b01528d-8347-42b5-b21f-de40f4baf3e7

📥 Commits

Reviewing files that changed from the base of the PR and between d73d242 and a4ce52a.

📒 Files selected for processing (1)
  • docker-compose.yml

Comment thread docker-compose.yml
Comment on lines +15 to 19
# Secrets (JWT_SECRET, BREVO_API_KEY, GROQ_API_KEYS, ...) come from this
# untracked file. Copy .env.example to backend/api/.env and fill it in.
# The API fails fast if JWT_SECRET is missing or <32 chars (see config.rs).
env_file:
- ./backend/api/.env

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check JWT_SECRET validation in config.rs

# Look for JWT_SECRET validation logic
ast-grep --pattern 'if jwt_secret.$$$'

Repository: Kingvic300/txio

Length of output: 41


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Locate config.rs =="
fd -t f -e rs "config.rs" . | head -n 20

echo
echo "== Search for jwt_secret / JWT_SECRET =="
rg -n --hidden --no-ignore -S "jwt_secret|JWT_SECRET" backend/api . | head -n 200

echo
echo "== Show likely validation area =="
CONFIG_FILE="$(fd -t f -e rs "config.rs" backend/api . | head -n 1 || true)"
echo "Using config file: ${CONFIG_FILE:-<none found>}"

if [ -n "${CONFIG_FILE:-}" ]; then
  # Print sections likely containing validation / JWT_SECRET
  rg -n -S "jwt_secret|JWT_SECRET|len\\(\\)|length|<\\s*32|\\b32\\b" "$CONFIG_FILE" | head -n 200
  echo
  # Show around first match for context
  MATCH_LINE="$(rg -n -S "jwt_secret|JWT_SECRET" "$CONFIG_FILE" | head -n 1 | cut -d: -f1 || true)"
  if [ -n "${MATCH_LINE:-}" ]; then
    sed -n "$((MATCH_LINE-60)),$((MATCH_LINE+120))p" "$CONFIG_FILE" | nl -ba | head -n 250
  fi
fi

Repository: Kingvic300/txio

Length of output: 4884


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Inspect backend/api/src/utils/config.rs (JWT_SECRET validation) =="
nl -ba backend/api/src/utils/config.rs | sed -n '1,140p'

echo
echo "== Search for known/default weak secret patterns =="
rg -n -S --hidden --no-ignore "dev-secret|test-secret|known_weak|weak_secrets|default secret|blocklist|blacklist" backend/api . || true

echo
echo "== Search for JWT_SECRET length / other validation beyond config.rs =="
rg -n -S --hidden --no-ignore "JWT_SECRET|jwt_secret" backend/api/src | head -n 200

Repository: Kingvic300/txio

Length of output: 190


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Show backend/api/src/utils/config.rs (first ~120 lines with line numbers) =="
python3 - <<'PY'
from pathlib import Path
p = Path("backend/api/src/utils/config.rs")
lines = p.read_text().splitlines()
for i, line in enumerate(lines[:140], 1):
    print(f"{i:4d}\t{line}")
PY

echo
echo "== Search for weak/default secret blocklist logic in backend/api =="
rg -n -S --hidden --no-ignore "dev-secret|test-secret|known_weak|weak_secrets|weaksecret|blocklist|blacklist|default secret|known.*secret|REJECT.*secret|jwt.*secret.*(block|reject|deny)" backend/api . || true

echo
echo "== Search for JWT_SECRET validation beyond length check =="
rg -n -S --hidden --no-ignore "(JWT_SECRET|jwt_secret).*len\\(\\)|len\\(.*32|<\\s*32|at least 32|must be at least 32|must be set" backend/api/src . || true

echo
echo "== Search for any mention of “dev-secret” / sample secrets across repo =="
rg -n -S --hidden --no-ignore "dev-secret|test-secret|known default|weak secret|rotate|burned" . || true

Repository: Kingvic300/txio

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Search for blocklist/weaksecret logic in config/auth area =="
rg -n -S --hidden --no-ignore "known_weak|weak_secrets|blocklist|blacklist|dev-secret|test-secret|default.*JWT_SECRET|JWT_SECRET.*default|reject.*JWT_SECRET|deny.*JWT_SECRET" backend/api/src || true

echo
echo "== Search for the specific example weak secret strings (if present) =="
rg -n -S --hidden --no-ignore "dev-secret-key-at-least-32-chars-long-12345|test-secret-key-at-least-32-chars-long" . || true

echo
echo "== Show JWT_SECRET validation snippet context in config.rs =="
python3 - <<'PY'
from pathlib import Path
p=Path("backend/api/src/utils/config.rs")
lines=p.read_text().splitlines()
for i in range(15,50):
    if i<=len(lines):
        print(f"{i:4d}\t{lines[i-1]}")
PY

Repository: Kingvic300/txio

Length of output: 1211


Reject known/default JWT_SECRET values (not just minimum length).

backend/api/src/utils/config.rs only checks that JWT_SECRET is set and len() >= 32; it does not reject any known/default/weak values. This leaves the PR objective from issue #25 unmet (“additionally reject known/default values”), since well-known placeholder secrets that meet the length threshold would still deploy.

Add an explicit denylist (at least for the documented defaults from backend/api/.env.example / other known dev secrets) or an entropy/format policy that makes weak defaults fail fast, and ensure the team rotates any previously committed/burned secret.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docker-compose.yml` around lines 15 - 19, The JWT_SECRET validation currently
only checks presence and minimum length in backend/api/src/utils/config.rs; add
an explicit denylist check (or an entropy/format check) inside the same
validation path (e.g., in the function that reads/validates JWT_SECRET such as
the config load/validate function) so that known defaults from .env.example (and
other common placeholders) are rejected even if length >= 32; implement by
creating a small denylist set of strings (include documented defaults), perform
the denylist check immediately after the len() check and return an error/exit if
matched, and update any error message to instruct rotation of previously
committed/burned secrets so the team rotates them.

@Kingvic300 Kingvic300 merged commit 0051d69 into main Jun 7, 2026
4 of 5 checks passed
Kingvic300 added a commit that referenced this pull request Jun 18, 2026
fix(security): remove hardcoded JWT secret from docker-compose
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.

[High] Hardcoded JWT secret committed in docker-compose.yml

1 participant