[object Object]

← 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

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 →