← back to Norma Sdcc Pitch
harden(auth): refuse to boot pitch deck in NODE_ENV=production when env passwords missing
6be4a5a31c5a97689bf3548466c2b682fa518c41 · 2026-05-21 19:02:47 -0700 · SteveStudio2
Was: prod boot with unset SDCC_PASSWORD/STEVE_PASSWORD silently fell back
to hard-coded defaults (Pulse-2026 / DWSecure2024!) and logged a warning.
That's the worst kind of credential drift — nobody notices until it leaks.
Now: in NODE_ENV=production, ALL configured passwords must be set as env
vars. If any are missing, process.exit(2) with a stderr explanation of
exactly which ones to set. Dev (NODE_ENV != production) is unchanged —
defaults still allowed for convenience, single warning per default used.
Verified: prod (with env vars set) still boots clean + good-pw login
works. Negative test (env -i NODE_ENV=production node server.js) exits
with the refusal message + code 2.
Files touched
Diff
commit 6be4a5a31c5a97689bf3548466c2b682fa518c41
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Thu May 21 19:02:47 2026 -0700
harden(auth): refuse to boot pitch deck in NODE_ENV=production when env passwords missing
Was: prod boot with unset SDCC_PASSWORD/STEVE_PASSWORD silently fell back
to hard-coded defaults (Pulse-2026 / DWSecure2024!) and logged a warning.
That's the worst kind of credential drift — nobody notices until it leaks.
Now: in NODE_ENV=production, ALL configured passwords must be set as env
vars. If any are missing, process.exit(2) with a stderr explanation of
exactly which ones to set. Dev (NODE_ENV != production) is unchanged —
defaults still allowed for convenience, single warning per default used.
Verified: prod (with env vars set) still boots clean + good-pw login
works. Negative test (env -i NODE_ENV=production node server.js) exits
with the refusal message + code 2.
---
server.js | 28 ++++++++++++++++++++++++----
1 file changed, 24 insertions(+), 4 deletions(-)
diff --git a/server.js b/server.js
index 1efd3b5..5980095 100644
--- a/server.js
+++ b/server.js
@@ -39,8 +39,13 @@ const NORMA_BASE = process.env.NORMA_BASE || 'http://localhost:7400';
// cleared from process.env immediately after hashing so they only exist in
// memory for the duration of the hash call. Login comparison uses scrypt +
// crypto.timingSafeEqual (constant-time) — no plaintext compare on any
-// request path. Defaults are kept for dev convenience but trigger a stderr
-// warning in production. `crypto` is already required at the top of this file.
+// request path.
+//
+// In production, BOTH env vars must be set. Booting with hard-coded defaults
+// under NODE_ENV=production is refused — silent fallback to known passwords
+// is the worst kind of credential drift (the kind nobody notices until it's
+// in a leak). In dev (NODE_ENV != production), defaults are allowed for
+// convenience.
const SCRYPT_N = 16384; // 2^14 — fast enough for login, slow enough vs brute force
const SCRYPT_KEYLEN = 64;
function deriveLoginHash(plaintext, salt) {
@@ -53,10 +58,25 @@ const LOGIN_RECORDS = (() => {
sdcc: { env: 'SDCC_PASSWORD', default: 'Pulse-2026' },
steve: { env: 'STEVE_PASSWORD', default: 'DWSecure2024!' },
};
+ const isProd = process.env.NODE_ENV === 'production';
+ const missing = [];
+ for (const [user, { env }] of Object.entries(seed)) {
+ if (!process.env[env]) missing.push({ user, env });
+ }
+ if (isProd && missing.length > 0) {
+ const lines = missing.map(({ user, env }) => ` - ${env} (for "${user}" account)`);
+ console.error(
+ '\n[auth] REFUSING TO BOOT in NODE_ENV=production with missing login passwords:\n' +
+ lines.join('\n') +
+ '\n\nSet them in /root/Projects/norma-sdcc-pitch/.env (or whatever you use)\n' +
+ 'and restart. Falling back to hard-coded defaults in prod is not allowed.\n',
+ );
+ process.exit(2);
+ }
for (const [user, { env, default: dflt }] of Object.entries(seed)) {
const pt = process.env[env] || dflt;
- if (!process.env[env] && process.env.NODE_ENV === 'production') {
- console.warn(`[auth] WARNING: ${env} not set in env; using hard-coded default for "${user}"`);
+ if (!process.env[env]) {
+ console.warn(`[auth] dev fallback: ${env} not set, using default for "${user}"`);
}
const salt = crypto.randomBytes(16);
out[user] = { salt, hash: deriveLoginHash(pt, salt) };
← 88191f5 fix(auth): replace plain-text password compare with scrypt +
·
back to Norma Sdcc Pitch
·
Add per-site favicon (kills /favicon.ico 404) e7cc6ea →