6be4a5a2026-05-21harden(auth): refuse to boot pitch deck in NODE_ENV=production when env passwords missing
88191f52026-05-21fix(auth): replace plain-text password compare with scrypt + timingSafeEqual
418a7c62026-05-21feat(auth): rate-limit POST /login on norma-sdcc-pitch (10/15min per IP, 30min lockout)
c6eb87e2026-05-20add: dashboard modals — click priority cards / loop cards / cron cards opens detail modal with extended sections; +3 XP per modal opened; Vault section deployed with full token form
948a9242026-05-20add: section 11 Token Vault — 6 categories (AI / Email / Advocacy / Productivity / Data / Norma internals), 21 token slots, password-typed inputs with show/hide, last4 masking on read, delete + bulk save
51ce9682026-05-20add: browser-playable explainer video banner at top of pitch viewer — Steve's cloned voice, HTML5 <video>, served from /explainer.mp4 (17 MB)
76506bb2026-05-20fix: demo mode short-circuit — when NORMA_BASE=demo, /api/run-example serves canned mocks instantly (no live-fetch attempt). always returns status+ms+source so frontend never shows undefined
c03fe2c2026-05-20fix: live demo mode — canned realistic mock responses for /api/run-example when Norma backend unreachable; UI shows ◆ DEMO MODE vs ◆ LIVE tags + explanatory note
93836032026-05-20add: sdcc + steve login gate, ElevenLabs voice cloning script for explainer video, hardened DEPLOY-NOW.sh
502c2822026-05-20hero: lead with 'A CRM that fits SDCC's work. Not the other way around.'
16362c02026-05-20add: 'Why custom CRM?' section — head-to-head vs Salesforce/EveryAction/HubSpot across 8 axes + hybrid play + honest risks + 'the real question'
29071142026-05-20add: top-5 priorities, reluctant-adopter, companies-are-databases, wix integration + UX/UI principles, glossary (80 terms across 10 categories), 2 new vocab badges
43cd0612026-05-20initial scaffold — lecture-style SDCC pitch viewer with quizzes + XP + agent catalog + Q&A cheat-sheets
Authors
Steve17
Agents used
none detected
Skills used
/login2
/run-example2
/after2
/favicon1
/proc1
/environ1
/pitch1
/rate-limit1
/hide1
/explainer1
Creative ideas + design notes
Commits with substantial prose (≥120 chars) — the rationale behind each move.
6be4a5a · 2026-05-21 · 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.
Was:
if (LOGINS[u] && password === LOGINS[u]) ← plaintext compare, timing-leak
Now:
scrypt-hash env-var plaintexts at module load (N=16384, keylen=64,
random per-user salt). Compare submitted password via scrypt + the same
salt, then crypto.timingSafeEqual against the stored hash. Plaintexts
are scrubbed from process.env immediately after hashing so they don't
sit in /proc/<pid>/environ for the life of the process.
No new deps — uses node built-in crypto.scryptSync. Defaults
(Pulse-2026 / DWSecure2024!) preserved for dev convenience; a stderr
warning fires when NODE_ENV=production and the env var is unset.
Verified live: 3 wrong-pw → 302 each, right-pw → 302 + Set-Cookie,
process.env no longer contains SDCC_PASSWORD/STEVE_PASSWORD.
418a7c6 · 2026-05-21 · feat(auth): rate-limit POST /login on norma-sdcc-pitch (10/15min per IP, 30min lockout)
Public pitch deck at https://pitch.sdcc.agentabrams.com/login had zero
rate-limit + plain-text password compare. The plain-text compare is a
separate concern flagged for follow-up; this commit only addresses the
brute-force vector.
Pattern matches Norma's rate-limit (lib/rate-limit.ts) but kept inline
since this project is single-file Express with no util layer:
- in-memory Map keyed by IP (X-Forwarded-For aware — nginx in front)
- 10 fails per 15-min window → 30-min lockout
- success clears counter
- 429 with Retry-After + a small HTML page when locked
- Map auto-cleans stale entries every 5 min via setInterval().unref()