[object Object]

← back to NationalPaperHangers

Constrain profile hero_url to the installer's own uploads, not an arbitrary URL

258f66afb1eaee516ae787c3d257c734c6d207a9 · 2026-05-19 08:00:18 -0700 · Steve

Files touched

Diff

commit 258f66afb1eaee516ae787c3d257c734c6d207a9
Author: Steve <steve@designerwallcoverings.com>
Date:   Tue May 19 08:00:18 2026 -0700

    Constrain profile hero_url to the installer's own uploads, not an arbitrary URL
---
 routes/admin.js | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/routes/admin.js b/routes/admin.js
index b8f4024..2182cd7 100644
--- a/routes/admin.js
+++ b/routes/admin.js
@@ -850,9 +850,19 @@ router.post('/template', async (req, res, next) => {
     }
     // template_settings: keep what's there, overlay our editable fields.
     const existing = req.installer.template_settings || {};
+    // hero_url must be one of THIS installer's own content-addressed uploads
+    // (/uploads/<their-id>/hero-<hash>.<ext>) — the hidden field is normally
+    // filled by the upload JS, but a hand-crafted POST could otherwise plant
+    // an arbitrary off-site URL that renders into the public profile's
+    // <img src>. Anything else is dropped (hero falls back to portfolio[0]).
+    const heroRaw = String(req.body.hero_url || '').slice(0, 500);
+    const heroOwnPathRe = new RegExp(
+      '^/uploads/' + req.installer.id + '/hero-[0-9a-f]{16}\\.(jpg|png|webp|avif)$'
+    );
+    const heroUrl = heroOwnPathRe.test(heroRaw) ? heroRaw : undefined;
     const settings = {
       ...existing,
-      hero_url: String(req.body.hero_url || '').slice(0, 500) || undefined,
+      hero_url: heroUrl,
       accent_color: /^#[0-9a-f]{6}$/i.test(String(req.body.accent_color || '')) ? req.body.accent_color : undefined
     };
     // Strip undefineds.

← 50ae349 Validate the plan tier on billing checkout so installers can  ·  back to NationalPaperHangers  ·  JSON-serialise booking-page analytics values so a stray quot 098e20c →