[object Object]

← back to Designer Wallcoverings

TK-11786 #4: HMAC-sign DW Central SSO session (close base64 forge bypass), room-setting-app + ImportNewSku

733bbfd27e92422d3f3fe24fd56654ce0d0045f4 · 2026-09-22 13:36:25 -0700 · Steve

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G3ChReG53fwpNgUESv4SY7

Files touched

Diff

commit 733bbfd27e92422d3f3fe24fd56654ce0d0045f4
Author: Steve <steve@designerwallcoverings.com>
Date:   Tue Sep 22 13:36:25 2026 -0700

    TK-11786 #4: HMAC-sign DW Central SSO session (close base64 forge bypass), room-setting-app + ImportNewSku
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01G3ChReG53fwpNgUESv4SY7
---
 .../__tests__/dw-central-session.test.ts           |  74 ++++++++++++
 .../ImportNewSkufromURL/lib/dw-central-session.ts  | 131 +++++++++++++++++++++
 .../__tests__/dw-central-session.test.mjs          |  64 ++++++++++
 .../room-setting-app/lib/dw-central-session.ts     | 131 +++++++++++++++++++++
 DW-Programming/room-setting-app/middleware.ts      |  24 +---
 5 files changed, 406 insertions(+), 18 deletions(-)

diff --git a/DW-Programming/ImportNewSkufromURL/__tests__/dw-central-session.test.ts b/DW-Programming/ImportNewSkufromURL/__tests__/dw-central-session.test.ts
new file mode 100644
index 00000000..9bca5a6c
--- /dev/null
+++ b/DW-Programming/ImportNewSkufromURL/__tests__/dw-central-session.test.ts
@@ -0,0 +1,74 @@
+/**
+ * @jest-environment node
+ *
+ * Node env: this exercises Web Crypto (globalThis.crypto.subtle), which Node 18+ exposes
+ * as a global — same primitive the Next.js Edge middleware uses at runtime.
+ */
+/**
+ * dw-central-session.test.ts — the PROOF for TK-11776 finding #4.
+ *
+ * Before this fix, verifyDWCentralSession accepted ANY base64('user:'+Date.now()) cookie
+ * with no signature — a live auth bypass. These are NEGATIVE tests (CLAUDE.md TK-11431
+ * amendment 3): they inject the exact forged cookie the exploit used and prove it is now
+ * REJECTED, prove a validly-signed cookie is ACCEPTED, and prove an expired one is
+ * REJECTED — so the fix demonstrably goes RED on the attack and GREEN on real sessions.
+ */
+import { signDWCentralSession, verifyDWCentralSession } from '@/lib/dw-central-session';
+
+const SECRET = 'test-only-secret-do-not-use-in-prod-0000'; // gitleaks:allow — test-only HMAC fixture, not a real credential
+
+// The literal exploit from the memo: base64('admin:'+Date.now()), NO signature.
+function forgeLegacyCookie(username = 'admin', now = Date.now()): string {
+  return Buffer.from(`${username}:${now}`).toString('base64');
+}
+
+describe('TK-11776 #4 — forgeable DW Central session is closed', () => {
+  test('REJECTS the forged unsigned cookie (base64 user:timestamp, the live exploit)', async () => {
+    const forged = forgeLegacyCookie('admin');
+    expect(await verifyDWCentralSession(forged, SECRET)).toBe(false);
+  });
+
+  test('REJECTS a fresh unsigned cookie for any username', async () => {
+    expect(await verifyDWCentralSession(forgeLegacyCookie('root'), SECRET)).toBe(false);
+    expect(await verifyDWCentralSession(forgeLegacyCookie('anybody@x.com'), SECRET)).toBe(false);
+  });
+
+  test('ACCEPTS a validly-signed, in-window cookie', async () => {
+    const good = await signDWCentralSession('steve', SECRET);
+    expect(await verifyDWCentralSession(good, SECRET)).toBe(true);
+  });
+
+  test('REJECTS a validly-signed cookie that is EXPIRED (>24h old)', async () => {
+    const oldTs = Date.now() - (86_400_000 + 60_000); // 24h + 1min ago
+    const expired = await signDWCentralSession('steve', SECRET, oldTs);
+    expect(await verifyDWCentralSession(expired, SECRET)).toBe(false);
+  });
+
+  test('REJECTS a valid token verified under the WRONG secret', async () => {
+    const good = await signDWCentralSession('steve', SECRET);
+    expect(await verifyDWCentralSession(good, 'a-different-secret')).toBe(false);
+  });
+
+  test('REJECTS a tampered payload (forger changes user but keeps a stolen signature)', async () => {
+    const good = await signDWCentralSession('lowpriv', SECRET);
+    const sig = good.slice(good.lastIndexOf('.'));
+    const evilPayload = Buffer.from(`admin:${Date.now()}`).toString('base64');
+    const tampered = `${evilPayload}${sig}`;
+    expect(await verifyDWCentralSession(tampered, SECRET)).toBe(false);
+  });
+
+  test('FAILS CLOSED when DW_SESSION_SECRET is unset (never accept-all)', async () => {
+    const good = await signDWCentralSession('steve', SECRET);
+    // Even a genuinely-signed token cannot pass without a configured secret,
+    // and the forged one certainly cannot.
+    expect(await verifyDWCentralSession(good, undefined)).toBe(false);
+    expect(await verifyDWCentralSession(good, '')).toBe(false);
+    expect(await verifyDWCentralSession(forgeLegacyCookie('admin'), undefined)).toBe(false);
+  });
+
+  test('REJECTS junk / empty / signature-less tokens', async () => {
+    for (const t of ['', 'not-a-token', 'no.dot.here.invalid', '.', 'abc.', '.abc']) {
+      expect(await verifyDWCentralSession(t, SECRET)).toBe(false);
+    }
+  });
+});
diff --git a/DW-Programming/ImportNewSkufromURL/lib/dw-central-session.ts b/DW-Programming/ImportNewSkufromURL/lib/dw-central-session.ts
new file mode 100644
index 00000000..a9bf7616
--- /dev/null
+++ b/DW-Programming/ImportNewSkufromURL/lib/dw-central-session.ts
@@ -0,0 +1,131 @@
+/**
+ * dw-central-session.ts — the ONE shared, signed DW Central SSO cookie contract.
+ *
+ * SECURITY (TK-11776 finding #4): the previous `verifyDWCentralSession` accepted ANY
+ * cookie that base64-decoded to `username:timestamp` within 24h, with NO signature.
+ * So `base64('admin:'+Date.now())` was a valid session on every gated route — a live,
+ * confirmed auth bypass on an endpoint that mints Shopify products. This module closes
+ * it by requiring an HMAC-SHA256 signature that only a holder of DW_SESSION_SECRET can
+ * produce.
+ *
+ * LOCKSTEP: the ISSUER (DW Central @ dw.greendomainbrokers.com) and EVERY verifier
+ * (this app, Letsbegin, room-setting-app) MUST use THIS exact logic with the SAME
+ * DW_SESSION_SECRET, or real logins break. The signer + verifier live together here on
+ * purpose so both sides are provably identical — the issuer copies/imports this file.
+ *
+ * SIGNED TOKEN FORMAT (a strict superset of the old payload, minimal issuer diff):
+ *   token   = <payloadB64> + "." + <sigB64>
+ *   payload = utf8("<username>:<timestampMillis>")   // unchanged from the old format
+ *   sig     = HMAC-SHA256( ascii(payloadB64), DW_SESSION_SECRET )
+ * base64 is standard (its alphabet has no ".", so lastIndexOf('.') splits cleanly).
+ *
+ * There is NO legacy-unsigned acceptance path. A token without a valid signature —
+ * including every old forgeable cookie — is REJECTED. Fails CLOSED when the secret is
+ * unset (rejects; never accept-all).
+ *
+ * Runtime-agnostic on purpose: uses Web Crypto (globalThis.crypto.subtle) + btoa/atob,
+ * which exist in the Next.js Edge runtime (where middleware runs), in Node 18+, and in
+ * jest's node test env. `crypto.subtle.verify('HMAC', ...)` is itself constant-time, so
+ * we do NOT hand-roll a compare (and node:crypto.timingSafeEqual is unavailable on Edge).
+ */
+
+const MAX_AGE_MS = 86_400_000; // 24 hours (unchanged)
+
+// --- base64 helpers (standard alphabet), runtime-agnostic --------------------------
+function bytesToBase64(bytes: Uint8Array): string {
+  let bin = '';
+  for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
+  return btoa(bin);
+}
+function base64ToBytes(b64: string): Uint8Array {
+  const bin = atob(b64);
+  const out = new Uint8Array(bin.length);
+  for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
+  return out;
+}
+function utf8ToBase64(str: string): string {
+  return bytesToBase64(new TextEncoder().encode(str));
+}
+function base64ToUtf8(b64: string): string {
+  return new TextDecoder().decode(base64ToBytes(b64));
+}
+
+// Uint8Array -> BufferSource: a runtime no-op cast. TS 5.7+ types encode()/atob bytes as
+// Uint8Array<ArrayBufferLike>, which some app tsconfigs reject where crypto.subtle wants
+// BufferSource. This keeps the module byte-identical + compiling across all consumers.
+function bs(u: Uint8Array): BufferSource {
+  return u as unknown as BufferSource;
+}
+
+async function hmacKey(secret: string): Promise<CryptoKey> {
+  return crypto.subtle.importKey(
+    'raw',
+    bs(new TextEncoder().encode(secret)),
+    { name: 'HMAC', hash: 'SHA-256' },
+    false,
+    ['sign', 'verify'],
+  );
+}
+
+/**
+ * Issue a signed DW Central session token. Used by DW Central at login (and by tests).
+ * Throws if the secret is missing — an unsigned token must never be minted.
+ */
+export async function signDWCentralSession(
+  username: string,
+  secret: string | undefined,
+  now: number = Date.now(),
+): Promise<string> {
+  if (!secret) throw new Error('DW_SESSION_SECRET is required to sign a DW Central session');
+  if (!username) throw new Error('username is required to sign a DW Central session');
+  const payloadB64 = utf8ToBase64(`${username}:${now}`);
+  const key = await hmacKey(secret);
+  const sig = new Uint8Array(
+    await crypto.subtle.sign('HMAC', key, bs(new TextEncoder().encode(payloadB64))),
+  );
+  return `${payloadB64}.${bytesToBase64(sig)}`;
+}
+
+/**
+ * Verify a DW Central session token. Returns true ONLY when the signature is valid for
+ * the configured secret AND the payload is within the 24h window. Fails CLOSED on a
+ * missing secret, a missing/short signature, a bad signature, or any decode error.
+ */
+export async function verifyDWCentralSession(
+  token: string | undefined | null,
+  secret: string | undefined,
+): Promise<boolean> {
+  try {
+    if (!secret) return false;                     // fail closed — never accept-all
+    if (!token || typeof token !== 'string') return false;
+
+    const dot = token.lastIndexOf('.');
+    if (dot <= 0 || dot >= token.length - 1) return false; // rejects ALL legacy unsigned cookies
+    const payloadB64 = token.slice(0, dot);
+    const sigB64 = token.slice(dot + 1);
+    if (!payloadB64 || !sigB64) return false;
+
+    const key = await hmacKey(secret);
+    const sigBytes = base64ToBytes(sigB64);
+    const ok = await crypto.subtle.verify(     // constant-time HMAC verification
+      'HMAC',
+      key,
+      bs(sigBytes),
+      bs(new TextEncoder().encode(payloadB64)),
+    );
+    if (!ok) return false;
+
+    // Signature is valid → the payload is authentic. Now enforce the 24h age.
+    const decoded = base64ToUtf8(payloadB64);
+    const idx = decoded.indexOf(':');
+    if (idx <= 0) return false;
+    const username = decoded.slice(0, idx);
+    const timestamp = decoded.slice(idx + 1);
+    if (!username || !timestamp) return false;
+    const sessionTime = parseInt(timestamp, 10);
+    if (!Number.isFinite(sessionTime)) return false;
+    return Date.now() - sessionTime < MAX_AGE_MS;
+  } catch {
+    return false;
+  }
+}
diff --git a/DW-Programming/room-setting-app/__tests__/dw-central-session.test.mjs b/DW-Programming/room-setting-app/__tests__/dw-central-session.test.mjs
new file mode 100644
index 00000000..182d047c
--- /dev/null
+++ b/DW-Programming/room-setting-app/__tests__/dw-central-session.test.mjs
@@ -0,0 +1,64 @@
+/**
+ * dw-central-session.test.mjs — PROOF for TK-11776 finding #4 (room-setting-app).
+ *
+ * This app has no jest runner, so this is a self-contained negative test runnable with
+ * plain `node __tests__/dw-central-session.test.mjs` (Node 18+; Node 23.6+/26 imports the
+ * .ts lib directly via type-stripping). It injects the exact forged cookie the live
+ * exploit used and proves it is now REJECTED, a validly-signed cookie ACCEPTED, an
+ * expired one REJECTED — plus wrong-secret / tampered / no-secret rejections. It also
+ * proves the test is a real detector by showing the OLD verifier logic ACCEPTS the forgery.
+ */
+import assert from 'node:assert/strict';
+import { signDWCentralSession, verifyDWCentralSession } from '../lib/dw-central-session.ts';
+
+const SECRET = 'test-only-secret-do-not-use-in-prod-0000'; // gitleaks:allow — test-only HMAC fixture, not a real credential
+const forge = (u = 'admin', now = Date.now()) => Buffer.from(`${u}:${now}`).toString('base64');
+
+// the vulnerable OLD verifier, verbatim — used only to prove this test is a real detector
+function oldVerify(token) {
+  try {
+    const d = Buffer.from(token, 'base64').toString('utf-8');
+    const [u, t] = d.split(':');
+    if (u && t) return Date.now() - parseInt(t, 10) < 86_400_000;
+    return false;
+  } catch { return false; }
+}
+
+let pass = 0;
+const check = async (name, fn) => { await fn(); pass++; console.log(`  ✓ ${name}`); };
+
+console.log('TK-11776 #4 — forgeable DW Central session is closed (room-setting-app)');
+await check('detector sanity: OLD verifier ACCEPTS the forged admin cookie', async () => {
+  assert.equal(oldVerify(forge('admin')), true);
+});
+await check('REJECTS the forged unsigned cookie (the live exploit)', async () => {
+  assert.equal(await verifyDWCentralSession(forge('admin'), SECRET), false);
+});
+await check('ACCEPTS a validly-signed, in-window cookie', async () => {
+  assert.equal(await verifyDWCentralSession(await signDWCentralSession('steve', SECRET), SECRET), true);
+});
+await check('REJECTS a validly-signed cookie that is EXPIRED (>24h)', async () => {
+  const expired = await signDWCentralSession('steve', SECRET, Date.now() - (86_400_000 + 60_000));
+  assert.equal(await verifyDWCentralSession(expired, SECRET), false);
+});
+await check('REJECTS a valid token under the WRONG secret', async () => {
+  const good = await signDWCentralSession('steve', SECRET);
+  assert.equal(await verifyDWCentralSession(good, 'a-different-secret'), false);
+});
+await check('REJECTS a tampered payload with a stolen signature', async () => {
+  const good = await signDWCentralSession('lowpriv', SECRET);
+  const sig = good.slice(good.lastIndexOf('.'));
+  const tampered = `${Buffer.from(`admin:${Date.now()}`).toString('base64')}${sig}`;
+  assert.equal(await verifyDWCentralSession(tampered, SECRET), false);
+});
+await check('FAILS CLOSED when DW_SESSION_SECRET is unset', async () => {
+  const good = await signDWCentralSession('steve', SECRET);
+  assert.equal(await verifyDWCentralSession(good, undefined), false);
+  assert.equal(await verifyDWCentralSession(forge('admin'), undefined), false);
+});
+await check('REJECTS junk / signature-less tokens', async () => {
+  for (const t of ['', 'not-a-token', '.', 'abc.', '.abc']) {
+    assert.equal(await verifyDWCentralSession(t, SECRET), false);
+  }
+});
+console.log(`\n${pass}/8 passed`);
diff --git a/DW-Programming/room-setting-app/lib/dw-central-session.ts b/DW-Programming/room-setting-app/lib/dw-central-session.ts
new file mode 100644
index 00000000..a9bf7616
--- /dev/null
+++ b/DW-Programming/room-setting-app/lib/dw-central-session.ts
@@ -0,0 +1,131 @@
+/**
+ * dw-central-session.ts — the ONE shared, signed DW Central SSO cookie contract.
+ *
+ * SECURITY (TK-11776 finding #4): the previous `verifyDWCentralSession` accepted ANY
+ * cookie that base64-decoded to `username:timestamp` within 24h, with NO signature.
+ * So `base64('admin:'+Date.now())` was a valid session on every gated route — a live,
+ * confirmed auth bypass on an endpoint that mints Shopify products. This module closes
+ * it by requiring an HMAC-SHA256 signature that only a holder of DW_SESSION_SECRET can
+ * produce.
+ *
+ * LOCKSTEP: the ISSUER (DW Central @ dw.greendomainbrokers.com) and EVERY verifier
+ * (this app, Letsbegin, room-setting-app) MUST use THIS exact logic with the SAME
+ * DW_SESSION_SECRET, or real logins break. The signer + verifier live together here on
+ * purpose so both sides are provably identical — the issuer copies/imports this file.
+ *
+ * SIGNED TOKEN FORMAT (a strict superset of the old payload, minimal issuer diff):
+ *   token   = <payloadB64> + "." + <sigB64>
+ *   payload = utf8("<username>:<timestampMillis>")   // unchanged from the old format
+ *   sig     = HMAC-SHA256( ascii(payloadB64), DW_SESSION_SECRET )
+ * base64 is standard (its alphabet has no ".", so lastIndexOf('.') splits cleanly).
+ *
+ * There is NO legacy-unsigned acceptance path. A token without a valid signature —
+ * including every old forgeable cookie — is REJECTED. Fails CLOSED when the secret is
+ * unset (rejects; never accept-all).
+ *
+ * Runtime-agnostic on purpose: uses Web Crypto (globalThis.crypto.subtle) + btoa/atob,
+ * which exist in the Next.js Edge runtime (where middleware runs), in Node 18+, and in
+ * jest's node test env. `crypto.subtle.verify('HMAC', ...)` is itself constant-time, so
+ * we do NOT hand-roll a compare (and node:crypto.timingSafeEqual is unavailable on Edge).
+ */
+
+const MAX_AGE_MS = 86_400_000; // 24 hours (unchanged)
+
+// --- base64 helpers (standard alphabet), runtime-agnostic --------------------------
+function bytesToBase64(bytes: Uint8Array): string {
+  let bin = '';
+  for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
+  return btoa(bin);
+}
+function base64ToBytes(b64: string): Uint8Array {
+  const bin = atob(b64);
+  const out = new Uint8Array(bin.length);
+  for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
+  return out;
+}
+function utf8ToBase64(str: string): string {
+  return bytesToBase64(new TextEncoder().encode(str));
+}
+function base64ToUtf8(b64: string): string {
+  return new TextDecoder().decode(base64ToBytes(b64));
+}
+
+// Uint8Array -> BufferSource: a runtime no-op cast. TS 5.7+ types encode()/atob bytes as
+// Uint8Array<ArrayBufferLike>, which some app tsconfigs reject where crypto.subtle wants
+// BufferSource. This keeps the module byte-identical + compiling across all consumers.
+function bs(u: Uint8Array): BufferSource {
+  return u as unknown as BufferSource;
+}
+
+async function hmacKey(secret: string): Promise<CryptoKey> {
+  return crypto.subtle.importKey(
+    'raw',
+    bs(new TextEncoder().encode(secret)),
+    { name: 'HMAC', hash: 'SHA-256' },
+    false,
+    ['sign', 'verify'],
+  );
+}
+
+/**
+ * Issue a signed DW Central session token. Used by DW Central at login (and by tests).
+ * Throws if the secret is missing — an unsigned token must never be minted.
+ */
+export async function signDWCentralSession(
+  username: string,
+  secret: string | undefined,
+  now: number = Date.now(),
+): Promise<string> {
+  if (!secret) throw new Error('DW_SESSION_SECRET is required to sign a DW Central session');
+  if (!username) throw new Error('username is required to sign a DW Central session');
+  const payloadB64 = utf8ToBase64(`${username}:${now}`);
+  const key = await hmacKey(secret);
+  const sig = new Uint8Array(
+    await crypto.subtle.sign('HMAC', key, bs(new TextEncoder().encode(payloadB64))),
+  );
+  return `${payloadB64}.${bytesToBase64(sig)}`;
+}
+
+/**
+ * Verify a DW Central session token. Returns true ONLY when the signature is valid for
+ * the configured secret AND the payload is within the 24h window. Fails CLOSED on a
+ * missing secret, a missing/short signature, a bad signature, or any decode error.
+ */
+export async function verifyDWCentralSession(
+  token: string | undefined | null,
+  secret: string | undefined,
+): Promise<boolean> {
+  try {
+    if (!secret) return false;                     // fail closed — never accept-all
+    if (!token || typeof token !== 'string') return false;
+
+    const dot = token.lastIndexOf('.');
+    if (dot <= 0 || dot >= token.length - 1) return false; // rejects ALL legacy unsigned cookies
+    const payloadB64 = token.slice(0, dot);
+    const sigB64 = token.slice(dot + 1);
+    if (!payloadB64 || !sigB64) return false;
+
+    const key = await hmacKey(secret);
+    const sigBytes = base64ToBytes(sigB64);
+    const ok = await crypto.subtle.verify(     // constant-time HMAC verification
+      'HMAC',
+      key,
+      bs(sigBytes),
+      bs(new TextEncoder().encode(payloadB64)),
+    );
+    if (!ok) return false;
+
+    // Signature is valid → the payload is authentic. Now enforce the 24h age.
+    const decoded = base64ToUtf8(payloadB64);
+    const idx = decoded.indexOf(':');
+    if (idx <= 0) return false;
+    const username = decoded.slice(0, idx);
+    const timestamp = decoded.slice(idx + 1);
+    if (!username || !timestamp) return false;
+    const sessionTime = parseInt(timestamp, 10);
+    if (!Number.isFinite(sessionTime)) return false;
+    return Date.now() - sessionTime < MAX_AGE_MS;
+  } catch {
+    return false;
+  }
+}
diff --git a/DW-Programming/room-setting-app/middleware.ts b/DW-Programming/room-setting-app/middleware.ts
index a4781668..c7790251 100644
--- a/DW-Programming/room-setting-app/middleware.ts
+++ b/DW-Programming/room-setting-app/middleware.ts
@@ -1,23 +1,11 @@
 import { NextRequest, NextResponse } from 'next/server';
 import { verifySession } from '@/lib/simple-auth';
+import { verifyDWCentralSession } from '@/lib/dw-central-session';
 
-// DW Central SSO: Check if user has a valid DW Central session
-function verifyDWCentralSession(token: string): boolean {
-  try {
-    // DW Central uses base64 encoded "username:timestamp" format
-    const decoded = Buffer.from(token, 'base64').toString('utf-8');
-    const [username, timestamp] = decoded.split(':');
-    if (username && timestamp) {
-      const sessionTime = parseInt(timestamp, 10);
-      const now = Date.now();
-      const maxAge = 86400000; // 24 hours
-      return (now - sessionTime) < maxAge;
-    }
-    return false;
-  } catch {
-    return false;
-  }
-}
+// DW Central SSO cookies are now HMAC-signed (TK-11776 finding #4). The verifier in
+// @/lib/dw-central-session REQUIRES DW_SESSION_SECRET and rejects any unsigned/forged
+// cookie (incl. the legacy base64('user:'+Date.now()) bypass). The DW Central issuer
+// must sign with the SAME secret via that module's signDWCentralSession. Fails closed.
 
 export async function middleware(request: NextRequest) {
   // Allow access to login page, API auth routes, public API routes, and root (for 150dpi integration)
@@ -39,7 +27,7 @@ export async function middleware(request: NextRequest) {
 
   // Check for DW Central SSO session first (from dw.greendomainbrokers.com)
   const dwCentralToken = request.cookies.get('dw_central_session')?.value;
-  if (dwCentralToken && verifyDWCentralSession(dwCentralToken)) {
+  if (dwCentralToken && (await verifyDWCentralSession(dwCentralToken, process.env.DW_SESSION_SECRET))) {
     return NextResponse.next();
   }
 

← 83214c10 TK-11786: remove superseded one-off scripts (romo-drilldown,  ·  back to Designer Wallcoverings  ·  Fix room-setting-app pre-existing build errors (unblock depl 5ad05ef7 →