← back to Marketing Command Center
MCC officer-review fixes: fail-closed auth (remove hardcoded password default), HttpOnly/Secure cookie, gitignore runtime-state files, fix Wallpaper->Wallcovering leak
da76726aa2a1b3f974ed7954f0952f1cdd2b750d · 2026-06-14 19:31:00 -0700 · Steve Abrams
Files touched
M .env.exampleM .gitignoreD data/channels-outbox.jsonD data/linkedin-drafts.jsonM modules/profiles/index.jsM server.js
Diff
commit da76726aa2a1b3f974ed7954f0952f1cdd2b750d
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sun Jun 14 19:31:00 2026 -0700
MCC officer-review fixes: fail-closed auth (remove hardcoded password default), HttpOnly/Secure cookie, gitignore runtime-state files, fix Wallpaper->Wallcovering leak
---
.env.example | 4 +++-
.gitignore | 4 ++++
data/channels-outbox.json | 26 --------------------------
data/linkedin-drafts.json | 32 --------------------------------
modules/profiles/index.js | 2 +-
server.js | 14 +++++++++++---
6 files changed, 19 insertions(+), 63 deletions(-)
diff --git a/.env.example b/.env.example
index 740d369..b7a153f 100644
--- a/.env.example
+++ b/.env.example
@@ -1,6 +1,8 @@
PORT=9661
MCC_USER=admin
-MCC_PASS=DWMarketing2026!
+# REQUIRED — set a strong unique password. No default; the app denies all
+# requests until this is set. Never commit the real value.
+MCC_PASS=change-me-set-a-strong-password
# Constant Contact (v3 API, OAuth2). Provide when ready; module mock-runs without it.
CTCT_API_KEY=
CTCT_ACCESS_TOKEN=
diff --git a/.gitignore b/.gitignore
index fe01fdf..97cb27f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,10 @@ data/assets.json
data/assets/
data/channels-tokens.json
data/social-publish-log.json
+data/channels-outbox.json
+data/linkedin-drafts.json
+data/ctct-store.json
+data/reposts.json
# Meta go-live creds — real Page token, never commit
scripts/meta-creds.env
diff --git a/data/channels-outbox.json b/data/channels-outbox.json
deleted file mode 100644
index 65c133a..0000000
--- a/data/channels-outbox.json
+++ /dev/null
@@ -1,26 +0,0 @@
-[
- {
- "id": "stg-facebook-0",
- "channel": "facebook",
- "caption": "New grasscloth collection just landed.",
- "mediaUrl": "https://x/y.jpg",
- "status": "pending-connection",
- "at": "2026-06-10T15:55:28.807Z"
- },
- {
- "id": "stg-instagram-1",
- "channel": "instagram",
- "caption": "New grasscloth collection just landed.",
- "mediaUrl": "https://x/y.jpg",
- "status": "pending-connection",
- "at": "2026-06-10T15:55:28.807Z"
- },
- {
- "id": "stg-tiktok-2",
- "channel": "tiktok",
- "caption": "New grasscloth collection just landed.",
- "mediaUrl": "https://x/y.jpg",
- "status": "pending-connection",
- "at": "2026-06-10T15:55:28.807Z"
- }
-]
\ No newline at end of file
diff --git a/data/linkedin-drafts.json b/data/linkedin-drafts.json
deleted file mode 100644
index c0b9360..0000000
--- a/data/linkedin-drafts.json
+++ /dev/null
@@ -1,32 +0,0 @@
-[
- {
- "id": "li_mqeaivf3dv0",
- "text": "New from Designer Wallcoverings: Bold Blush.\n\nMost of us treat blush as a soft default. This collection treats it as a structural choice — whisper-soft rose through saturated, confident depth, across florals with real movement, geometrics with backbone, and textures that catch the light.\n\nA wallcovering range built to anchor a room, not soften it. Available to the trade; samples on request.\n\nHow are you specifying pink on commercial projects right now?",
- "hashtags": [
- "#InteriorDesign",
- "#ToTheTrade",
- "#DesignerWallcoverings",
- "#Wallcovering",
- "#Hospitality"
- ],
- "chars": 456,
- "status": "ready",
- "created_at": "2026-06-14T21:22:33.998Z",
- "updatedAt": "2026-06-15T00:37:21.388Z"
- },
- {
- "id": "li_mqeaivf3o6u",
- "text": "A statement wall should be a statement — not a stock number.\n\nOur new Custom Mural program is made-to-order: scenic and artistic murals scaled, colored, and printed for a single room. Painterly to graphic, produced on demand — no repeats.\n\nFor designers and specifiers, that means a true bespoke wall without bespoke lead-time pain. To the trade.\n\nWhat’s the most ambitious feature wall you’ve specified this year?",
- "hashtags": [
- "#InteriorDesign",
- "#ToTheTrade",
- "#DesignerWallcoverings",
- "#FeatureWall",
- "#Bespoke"
- ],
- "chars": 414,
- "status": "ready",
- "created_at": "2026-06-14T21:22:33.998Z",
- "updatedAt": "2026-06-15T00:37:21.389Z"
- }
-]
\ No newline at end of file
diff --git a/modules/profiles/index.js b/modules/profiles/index.js
index 318f8d0..fb143de 100644
--- a/modules/profiles/index.js
+++ b/modules/profiles/index.js
@@ -38,7 +38,7 @@ const SITE_PAGES = [
{ slug: 'grasscloth-wallcoverings', title: 'Grasscloth Wallcoverings' },
{ slug: 'silk-wallcoverings', title: 'Silk Wallcoverings' },
{ slug: 'cork-wallcoverings', title: 'Cork Wallcoverings' },
- { slug: 'flocked-wallpaper', title: 'Flocked Wallpaper' },
+ { slug: 'flocked-wallcoverings', title: 'Flocked Wallcoverings' },
{ slug: 'glass-bead-wallcoverings', title: 'Glass-Bead Wallcoverings' },
{ slug: 'trade-program', title: 'Trade Program' },
{ slug: 'memo-samples', title: 'Request Memo Samples' },
diff --git a/server.js b/server.js
index 222329b..bdce7df 100644
--- a/server.js
+++ b/server.js
@@ -26,7 +26,12 @@ app.use(express.json({ limit: '28mb' }));
// ── Basic auth (matches DW fleet pattern; cred overridable via env) ──────────
const USER = process.env.MCC_USER || 'admin';
-const PASS = process.env.MCC_PASS || 'DWMarketing2026!';
+// No fallback: fail closed if MCC_PASS is unset so a misconfigured box denies
+// all access rather than shipping a known default. (A hardcoded default was
+// previously here AND in the committed .env.example — treat that value as burned
+// and rotate it; this box must set MCC_PASS in its own .env.)
+const PASS = process.env.MCC_PASS || '';
+if (!PASS) console.warn('[mcc] MCC_PASS is unset — all requests will be denied until it is configured.');
// Auth accepts the standard Basic header OR an `mcc` cookie we set on first
// success. The cookie is what makes XHR/fetch work after the page is opened
// with embedded URL credentials (http://user:pass@host) — Chrome won't re-attach
@@ -40,8 +45,11 @@ app.use((req, res, next) => {
if (m) b64 = decodeURIComponent(m[1]);
}
const [u, p] = Buffer.from(b64 || '', 'base64').toString().split(':');
- if (u === USER && p === PASS) {
- res.setHeader('Set-Cookie', `mcc=${encodeURIComponent(b64)}; Path=/; Max-Age=604800; SameSite=Lax`);
+ if (PASS && u === USER && p === PASS) {
+ // HttpOnly so XSS can't read the credential-equivalent cookie; Secure only
+ // when the request arrived over HTTPS so local plain-http :9661 still works.
+ const secure = (req.headers['x-forwarded-proto'] === 'https' || req.secure) ? ' Secure;' : '';
+ res.setHeader('Set-Cookie', `mcc=${encodeURIComponent(b64)}; Path=/; Max-Age=604800; HttpOnly;${secure} SameSite=Lax`);
return next();
}
res.set('WWW-Authenticate', 'Basic realm="Marketing Command Center"').status(401).end('Auth required');
← 4715bb6 MCC QA fixes: re-render #3 (Anemone Blush, was off-theme) +
·
back to Marketing Command Center
·
MCC should-fix sweep: unify Kravet block (lib/kravet.js, ser 9968bde →