Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 14 additions & 11 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
MONGO_URL_DEV=mongodb://localhost/nodejs-api-boilerplate-dev
JWT_SECRET_DEV=ewtijwebgiuweg9w98u9283982t!!u1h28h1t1h89u9h@$$

MONGO_URL_TEST=mongodb://localhost/nodejs-api-boilerplate-test
JWT_SECRET_TEST=ewtijwebgiuweg9w98u9283982t!!u1h28h1t1h89u9h@$$

MONGO_URL_PROD=mongodb://localhost/nodejs-api-boilerplate-prod
JWT_SECRET_PROD=ewtijwebgiuweg9w98u9283982t!!u1h28h1t1h89u9h@$$

RAVEN_ID=yourapikey
DOCS_WEBSITE=yourwebsiteurl
MONGO_URL_DEV=mongodb://localhost/nodejs-api-boilerplate-dev
# Generate: node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
JWT_SECRET_DEV=

MONGO_URL_TEST=mongodb://localhost/nodejs-api-boilerplate-test
# Generate: node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
JWT_SECRET_TEST=

MONGO_URL_PROD=mongodb://localhost/nodejs-api-boilerplate-prod
# Generate: node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
JWT_SECRET_PROD=

RAVEN_ID=yourapikey
DOCS_WEBSITE=yourwebsiteurl
116 changes: 68 additions & 48 deletions src/config/constants.js
Original file line number Diff line number Diff line change
@@ -1,48 +1,68 @@
require('dotenv').config();

const WHITELIST = {
posts: {
create: ['title', 'text'],
update: ['title', 'text'],
},
users: {
create: ['email', 'username', 'password'],
},
};

const devConfig = {
JWT_SECRET: process.env.JWT_SECRET_DEV,
MONGO_URL: process.env.MONGO_URL_DEV,
};

const testConfig = {
JWT_SECRET: 'ewtijwebgiuweg9w98u9283982t!!u1h28h1t1h89u9h@$$',
MONGO_URL: 'mongodb://localhost/nodejs-api-boilerplate-test',
};

const prodConfig = {
JWT_SECRET: process.env.JWT_SECRET_PROD,
MONGO_URL: process.env.MONGO_URL_PROD,
};

const defaultConfig = {
PORT: process.env.PORT || 3000,
RAVEN_ID: process.env.RAVEN_ID,
WHITELIST,
};

function envConfig(env) {
switch (env) {
case 'development':
return devConfig;
case 'test':
return testConfig;
default:
return prodConfig;
}
}

export default {
...defaultConfig,
...envConfig(process.env.NODE_ENV),
};
require('dotenv').config();

const WHITELIST = {
posts: {
create: ['title', 'text'],
update: ['title', 'text'],
},
users: {
create: ['email', 'username', 'password'],
},
};

const devConfig = {
JWT_SECRET: process.env.JWT_SECRET_DEV,
MONGO_URL: process.env.MONGO_URL_DEV,
};

const testConfig = {
JWT_SECRET: process.env.JWT_SECRET_TEST,
MONGO_URL: process.env.MONGO_URL_TEST || 'mongodb://localhost/nodejs-api-boilerplate-test',
};

const prodConfig = {
JWT_SECRET: process.env.JWT_SECRET_PROD,
MONGO_URL: process.env.MONGO_URL_PROD,
};

const defaultConfig = {
PORT: process.env.PORT || 3000,
RAVEN_ID: process.env.RAVEN_ID,
WHITELIST,
};

function envConfig(env) {
switch (env) {
case 'development':
return devConfig;
case 'test':
return testConfig;
default:
return prodConfig;
}
}

const config = {
...defaultConfig,
...envConfig(process.env.NODE_ENV),
};

// Validate JWT_SECRET at startup — reject empty/weak defaults
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(
Comment on lines +51 to +56

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).

'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}.`
);
}
Comment on lines +51 to +66

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 + '.'
);
}


export default config;