Skip to content

Fix hardcoded JWT secrets in .env.example and constants.js (CWE-798)#178

Open
saaa99999999 wants to merge 1 commit into
EQuimper:masterfrom
saaa99999999:fix/hardcoded-jwt-secret
Open

Fix hardcoded JWT secrets in .env.example and constants.js (CWE-798)#178
saaa99999999 wants to merge 1 commit into
EQuimper:masterfrom
saaa99999999:fix/hardcoded-jwt-secret

Conversation

@saaa99999999
Copy link
Copy Markdown

@saaa99999999 saaa99999999 commented May 17, 2026

Summary

Replaced hardcoded JWT signing keys in .env.example and src/config/constants.js with environment variable references. Added startup validation that rejects empty, known-default, and short JWT secrets.

Details

  • .env.example: All three environments (DEV, TEST, PROD) contained the same hardcoded key ewtijwebgiuweg9w98u9283982t!!u1h28h1t1h89u9h@$. Replaced with empty values and generation instructions using crypto.randomBytes().
  • src/config/constants.js: estConfig.JWT_SECRET was a hardcoded string literal instead of reading from process.env.JWT_SECRET_TEST. Changed to use environment variable.
  • Added startup validation in constants.js that rejects: empty secrets, known-weak values (the old hardcoded key, "secret", "changeme", "jwt_secret", "your-secret-key"), and keys shorter than 32 characters.

Security Impact

This project uses symmetric JWT (HS256) where the same key signs and verifies tokens. The hardcoded key is visible in the public repository history. Anyone with this key can forge valid JWT tokens for any user ??including admin ??bypassing authentication completely (CWE-798).

Before/After

.env.example

`

  • JWT_SECRET_DEV=ewtijwebgiuweg9w98u9283982t!!u1h28h1t1h89u9h@$
  • JWT_SECRET_TEST=ewtijwebgiuweg9w98u9283982t!!u1h28h1t1h89u9h@$
  • JWT_SECRET_PROD=ewtijwebgiuweg9w98u9283982t!!u1h28h1t1h89u9h@$
  • Generate: node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"

  • JWT_SECRET_DEV=
  • JWT_SECRET_TEST=
  • JWT_SECRET_PROD=
    `

src/config/constants.js

`

  • JWT_SECRET: 'ewtijwebgiuweg9w98u9283982t!!u1h28h1t1h89u9h@$',
  • JWT_SECRET: process.env.JWT_SECRET_TEST,
    `
    Plus startup validation block (lines 50-66).

Summary by CodeRabbit

  • Chores
    • Updated configuration template with empty JWT secret placeholders and instructions for secure generation.
    • Added startup validation to enforce strong JWT secrets, preventing weak or missing values from being used.

Review Change Stack

Replaced hardcoded JWT_SECRET values in .env.example with empty placeholders
and generation instructions. Moved testConfig JWT_SECRET from hardcoded string
to process.env.JWT_SECRET_TEST. Added startup validation that rejects empty,
known-default, and short JWT secrets.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 17, 2026

📝 Walkthrough

Walkthrough

This PR strengthens JWT secret configuration by replacing hardcoded examples in .env.example with empty placeholders and generation instructions, while adding runtime validation in the configuration module to enforce that secrets are non-empty, non-weak, and at least 32 characters long.

Changes

JWT Secret Configuration and Validation

Layer / File(s) Summary
Environment template with JWT secret instructions
.env.example
JWT secret placeholders for DEV, TEST, and PROD replace hardcoded values, each with a preceding comment showing how to generate a cryptographically secure secret using Node's crypto.randomBytes(64). Mongo connection URLs and other environment variables remain unchanged.
Configuration validation and startup checks
src/config/constants.js
Configuration module now sources testConfig.JWT_SECRET from the environment instead of a hardcoded weak default, builds the exported config constant by merging environment-specific overrides with defaultConfig, and validates at module load that JWT_SECRET is non-empty, not a known weak value, and at least 32 characters long, throwing errors on violation before export.

Sequence Diagram

sequenceDiagram
    participant Developer
    participant EnvTemplate as .env.example
    participant ConfigModule as constants.js
    Developer->>EnvTemplate: Read setup instructions
    EnvTemplate->>Developer: Generate secret via crypto.randomBytes(64)
    Developer->>ConfigModule: Module loads with JWT_SECRET from env
    ConfigModule->>ConfigModule: Validate secret non-empty
    ConfigModule->>ConfigModule: Validate secret not weak/placeholder
    ConfigModule->>ConfigModule: Validate secret length >= 32 chars
    ConfigModule->>Developer: Export validated config or throw error
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A secret stronger than before,
No hardcoded weak keys anymore!
Crypto bytes now guard the way,
Validated crisp at startup's day.
Security leaps, one hop closer to safe! 🔐

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main security fix: removing hardcoded JWT secrets from .env.example and constants.js, with explicit CWE-798 reference.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request removes hardcoded JWT secrets from the environment example and configuration files, transitioning to environment-variable-based configuration across all stages. It also introduces a startup validation check to ensure the JWT_SECRET is present, not a known weak value, and meets a minimum length requirement. Feedback suggests refining the validation logic to handle whitespace and case-insensitivity for weak secrets to prevent accidental bypasses.

Comment thread src/config/constants.js
Comment on lines +51 to +66
const KNOWN_WEAK_SECRETS = [
'', 'ewtijwebgiuweg9w98u9283982t!!u1h28h1t1h89u9h@$$',
'secret', 'changeme', 'jwt_secret', 'your-secret-key',
];
if (!config.JWT_SECRET || KNOWN_WEAK_SECRETS.includes(config.JWT_SECRET)) {
throw new Error(
'JWT_SECRET is not set or uses a known default value. ' +
'Set JWT_SECRET_DEV, JWT_SECRET_TEST, or JWT_SECRET_PROD in your .env file. ' +
'Generate a secure key: node -e "console.log(require(\'crypto\').randomBytes(64).toString(\'hex\'))"'
);
}
if (config.JWT_SECRET.length < 32) {
throw new Error(
`JWT_SECRET must be at least 32 characters. Current length: ${config.JWT_SECRET.length}.`
);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The JWT secret validation can be improved by handling potential whitespace and performing case-insensitive checks for known weak secrets. This prevents accidental bypasses with leading/trailing spaces or variations like 'Secret'. Also, the empty string in the KNOWN_WEAK_SECRETS array is redundant as the !config.JWT_SECRET check already handles empty strings, null, and undefined values. The length check is also updated to use the trimmed secret for accuracy.

Suggested change
const KNOWN_WEAK_SECRETS = [
'', 'ewtijwebgiuweg9w98u9283982t!!u1h28h1t1h89u9h@$$',
'secret', 'changeme', 'jwt_secret', 'your-secret-key',
];
if (!config.JWT_SECRET || KNOWN_WEAK_SECRETS.includes(config.JWT_SECRET)) {
throw new Error(
'JWT_SECRET is not set or uses a known default value. ' +
'Set JWT_SECRET_DEV, JWT_SECRET_TEST, or JWT_SECRET_PROD in your .env file. ' +
'Generate a secure key: node -e "console.log(require(\'crypto\').randomBytes(64).toString(\'hex\'))"'
);
}
if (config.JWT_SECRET.length < 32) {
throw new Error(
`JWT_SECRET must be at least 32 characters. Current length: ${config.JWT_SECRET.length}.`
);
}
const KNOWN_WEAK_SECRETS = [
'ewtijwebgiuweg9w98u9283982t!!u1h28h1t1h89u9h@$$',
'secret', 'changeme', 'jwt_secret', 'your-secret-key',
];
const secret = (config.JWT_SECRET || '').trim();
if (!secret || KNOWN_WEAK_SECRETS.some(weak => weak.toLowerCase() === secret.toLowerCase())) {
throw new Error(
'JWT_SECRET is not set or uses a known default value. ' +
'Set JWT_SECRET_DEV, JWT_SECRET_TEST, or JWT_SECRET_PROD in your .env file. ' +
'Generate a secure key: node -e "console.log(require(\'crypto\').randomBytes(64).toString(\'hex\'))"'
);
}
if (secret.length < 32) {
throw new Error(
'JWT_SECRET must be at least 32 characters (excluding whitespace). Current length: ' + secret.length + '.'
);
}

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@src/config/constants.js`:
- Around line 51-56: The denylist check for weak JWT secrets is brittle and
contains a typo in the KNOWN_WEAK_SECRETS entry and uses a direct includes which
can be bypassed; fix by correcting the compromised string in KNOWN_WEAK_SECRETS
(replace the incorrect "...h@$$" with the intended "...h@$") and change the
validation around config.JWT_SECRET to normalize the value (trim whitespace and
lower-case) before checking membership—e.g., compute a normalizedSecret =
config.JWT_SECRET.trim().toLowerCase() and compare against a pre-normalized
set/array of KNOWN_WEAK_SECRETS; apply the same normalization-based check
wherever config.JWT_SECRET is validated (the other check referenced around the
62-64 area).
🪄 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

Run ID: b1fbd801-2bc6-455c-beb2-df015e632ae5

📥 Commits

Reviewing files that changed from the base of the PR and between cc702d7 and 8ef5b28.

📒 Files selected for processing (2)
  • .env.example
  • src/config/constants.js

Comment thread src/config/constants.js
Comment on lines +51 to +56
const KNOWN_WEAK_SECRETS = [
'', 'ewtijwebgiuweg9w98u9283982t!!u1h28h1t1h89u9h@$$',
'secret', 'changeme', 'jwt_secret', 'your-secret-key',
];
if (!config.JWT_SECRET || KNOWN_WEAK_SECRETS.includes(config.JWT_SECRET)) {
throw new Error(
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 | ⚡ Quick win

Fix denylist mismatch and normalize JWT secret before validation.

Line 52 appears to have a typo (...h@$$) versus the compromised value described in this PR (...h@$), so the intended block may not trigger. Also, raw includes checks can be bypassed with whitespace/case variants (for example, " Secret ").

Suggested patch
-const KNOWN_WEAK_SECRETS = [
-  '', 'ewtijwebgiuweg9w98u9283982t!!u1h28h1t1h89u9h@$$',
-  'secret', 'changeme', 'jwt_secret', 'your-secret-key',
-];
-if (!config.JWT_SECRET || KNOWN_WEAK_SECRETS.includes(config.JWT_SECRET)) {
+const KNOWN_WEAK_SECRETS = [
+  '',
+  'ewtijwebgiuweg9w98u9283982t!!u1h28h1t1h89u9h@$',
+  'secret',
+  'changeme',
+  'jwt_secret',
+  'your-secret-key',
+];
+const normalizedSecret = String(config.JWT_SECRET || '').trim();
+const weakSecrets = new Set(KNOWN_WEAK_SECRETS.map((s) => s.trim().toLowerCase()));
+if (!normalizedSecret || weakSecrets.has(normalizedSecret.toLowerCase())) {
   throw new Error(
     'JWT_SECRET is not set or uses a known default value. ' +
     'Set JWT_SECRET_DEV, JWT_SECRET_TEST, or JWT_SECRET_PROD in your .env file. ' +
     'Generate a secure key: node -e "console.log(require(\'crypto\').randomBytes(64).toString(\'hex\'))"'
   );
 }
-if (config.JWT_SECRET.length < 32) {
+if (normalizedSecret.length < 32) {
   throw new Error(
-    `JWT_SECRET must be at least 32 characters. Current length: ${config.JWT_SECRET.length}.`
+    `JWT_SECRET must be at least 32 characters. Current length: ${normalizedSecret.length}.`
   );
 }

Also applies to: 62-64

🤖 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 `@src/config/constants.js` around lines 51 - 56, The denylist check for weak
JWT secrets is brittle and contains a typo in the KNOWN_WEAK_SECRETS entry and
uses a direct includes which can be bypassed; fix by correcting the compromised
string in KNOWN_WEAK_SECRETS (replace the incorrect "...h@$$" with the intended
"...h@$") and change the validation around config.JWT_SECRET to normalize the
value (trim whitespace and lower-case) before checking membership—e.g., compute
a normalizedSecret = config.JWT_SECRET.trim().toLowerCase() and compare against
a pre-normalized set/array of KNOWN_WEAK_SECRETS; apply the same
normalization-based check wherever config.JWT_SECRET is validated (the other
check referenced around the 62-64 area).

@saaa99999999
Copy link
Copy Markdown
Author

CVE Request — Action Needed from Maintainer

This PR fixes security vulnerabilities. To assign a CVE number:

GitHub only issues CVEs from the official upstream repository, not from forks.

Please:

  1. Go to this repo → SecurityAdvisoriesNew draft security advisory
  2. Add @saaa99999999 as a collaborator
  3. I will populate the full vulnerability details (CVSS, CWE, data flow, PoC) and submit the CVE request

If you prefer, I can submit the CVE via MITRE (cveform.mitre.org) instead — just let me know.

Thank you for reviewing this PR!

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