fix(security): remove hardcoded JWT secret from docker-compose#68
Conversation
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
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThe PR moves hardcoded secrets from inline ChangesSecrets externalization from docker-compose
Estimated code review effort🎯 2 (Simple) | ⏱️ ~5 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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-12345was 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
| # 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 |
There was a problem hiding this comment.
🧩 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
fiRepository: 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 200Repository: 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" . || trueRepository: 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]}")
PYRepository: 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.
fix(security): remove hardcoded JWT secret from docker-compose
Summary
docker-compose.ymlcommitted a real, fixedJWT_SECRET(dev-secret-key-at-least-32-chars-long-12345) and aBREVO_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
JWT_SECRETandBREVO_API_KEY(and the unusedSUI_CLI_EMAIL) from theapiservice.env_file(backend/api/.env, copied from .env.example).JWT_SECRETis missing/<32 chars (config.rs).Follow-up
Closes #25
Summary by CodeRabbit