[object Object]

← back to Handbag Authentication

security: add rel="noopener noreferrer" to all target=_blank links

9f17a3dc9d2946aba6eb210df71a53fb743b466e · 2026-05-30 23:57:18 -0700 · Steve Abrams

Fixes reverse tabnapping vulnerability in public/js/product.js (5 links),
public/js/app.js (1 link), and views/product.ejs (3 links).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Files touched

Diff

commit 9f17a3dc9d2946aba6eb210df71a53fb743b466e
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sat May 30 23:57:18 2026 -0700

    security: add rel="noopener noreferrer" to all target=_blank links
    
    Fixes reverse tabnapping vulnerability in public/js/product.js (5 links),
    public/js/app.js (1 link), and views/product.ejs (3 links).
    
    Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---
 public/js/app.js     |  2 +-
 public/js/product.js | 12 ++++++------
 views/product.ejs    |  6 +++---
 3 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/public/js/app.js b/public/js/app.js
index 05f9071..2a1b249 100644
--- a/public/js/app.js
+++ b/public/js/app.js
@@ -430,7 +430,7 @@ function createListingCard(listing, index) {
       </div>
       <div class="pricing-logic">
         <strong>💡 Pricing Logic:</strong> ${translatedBrand || 'These'} bags resell at <strong>${multiplier}x</strong> Japan price (${Math.round((multiplier - 1) * 100)}% markup).
-        <a href="https://www.google.com/search?q=${encodeURIComponent((translatedBrand || 'luxury') + ' bag sold prices')}" target="_blank" class="research-link">See market data →</a>
+        <a href="https://www.google.com/search?q=${encodeURIComponent((translatedBrand || 'luxury') + ' bag sold prices')}" target="_blank" rel="noopener noreferrer" class="research-link">See market data →</a>
       </div>
     </div>
   ` : '';
diff --git a/public/js/product.js b/public/js/product.js
index 52dbad0..3b2e49a 100644
--- a/public/js/product.js
+++ b/public/js/product.js
@@ -261,7 +261,7 @@ function buildPlatformScenarios(totalCosts, basePrice) {
         <td>${p.speed}</td>
         <td class="comps-cell">
           ${compsHtml}
-          <a href="${p.searchUrl}" target="_blank" class="btn-view-comps">View All Sold →</a>
+          <a href="${p.searchUrl}" target="_blank" rel="noopener noreferrer" class="btn-view-comps">View All Sold →</a>
         </td>
       </tr>
     `;
@@ -331,7 +331,7 @@ async function fetchRealSoldData(brand, model) {
 
       // Add search link
       const searchUrl = getSearchUrl(platform, brand, model);
-      compsHtml += `<a href="${searchUrl}" target="_blank" class="btn-view-comps">View All Sold →</a>`;
+      compsHtml += `<a href="${searchUrl}" target="_blank" rel="noopener noreferrer" class="btn-view-comps">View All Sold →</a>`;
 
       cell.innerHTML = compsHtml;
     });
@@ -559,8 +559,8 @@ function loadRetailerLinks() {
       <img src="${retailer.logo}" alt="${retailer.name}" onerror="this.src='data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%2260%22 height=%2260%22><rect width=%2260%22 height=%2260%22 fill=%22%23667eea%22/><text x=%2250%%22 y=%2250%%22 fill=%22white%22 font-size=%2224%22 text-anchor=%22middle%22 dy=%22.3em%22>🏬</text></svg>'">
       <h4>${retailer.name}</h4>
       <p>Compare to NEW retail version</p>
-      <a href="${retailer.searchUrl(model)}" target="_blank">Search for ${model}</a>
-      <a href="${retailer.url}" target="_blank" style="margin-top: 0.5rem; background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">Browse All Bags</a>
+      <a href="${retailer.searchUrl(model)}" target="_blank" rel="noopener noreferrer">Search for ${model}</a>
+      <a href="${retailer.url}" target="_blank" rel="noopener noreferrer" style="margin-top: 0.5rem; background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">Browse All Bags</a>
     </div>
   `;
 
@@ -577,7 +577,7 @@ function loadRetailerLinks() {
         <img src="${r.logo}" alt="${r.name}" onerror="this.src='data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%2260%22 height=%2260%22><rect width=%2260%22 height=%2260%22 fill=%22%23c3cfe2%22/><text x=%2250%%22 y=%2250%%22 fill=%22%23667eea%22 font-size=%2224%22 text-anchor=%22middle%22 dy=%22.3em%22>🛍️</text></svg>'">
         <h4>${r.name}</h4>
         <p>Authorized retailer</p>
-        <a href="${r.url}" target="_blank">Search ${r.name}</a>
+        <a href="${r.url}" target="_blank" rel="noopener noreferrer">Search ${r.name}</a>
       </div>
     `;
   });
@@ -772,7 +772,7 @@ function loadPlatformComparison() {
         <td>$${Math.round(p.salePrice).toLocaleString()}</td>
         <td class="${profitClass}">$${Math.round(profit).toLocaleString()}</td>
         <td>
-          <a href="${p.url}" target="_blank" class="btn-primary" style="padding: 0.5rem 1rem; font-size: 0.9rem;">List Now</a>
+          <a href="${p.url}" target="_blank" rel="noopener noreferrer" class="btn-primary" style="padding: 0.5rem 1rem; font-size: 0.9rem;">List Now</a>
         </td>
       </tr>
     `;
diff --git a/views/product.ejs b/views/product.ejs
index 7db6b4c..2dad525 100644
--- a/views/product.ejs
+++ b/views/product.ejs
@@ -66,7 +66,7 @@
 
         <!-- Action Buttons -->
         <div class="action-buttons">
-          <a href="<%= product.product_url %>" target="_blank" class="btn-primary">
+          <a href="<%= product.product_url %>" target="_blank" rel="noopener noreferrer" class="btn-primary">
             🛒 View on <%= product.source %>
           </a>
           <button class="btn-presell" onclick="presellItem()">
@@ -108,7 +108,7 @@
           <div class="platform-quick-card" style="background: white; padding: 1.5rem; border-radius: 12px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);">
             <h4 style="margin: 0 0 0.5rem 0; font-size: 1.3rem;"><%= data.url.includes('ebay') ? 'eBay' : data.url.includes('posh') ? 'Poshmark' : data.url.includes('mercari') ? 'Mercari' : data.url.includes('vestiaire') ? 'Vestiaire' : data.url.includes('realreal') ? 'The RealReal' : 'Fashionphile' %></h4>
             <p style="font-size: 0.9rem; color: #666; margin-bottom: 1rem;"><%= data.quickStart %></p>
-            <a href="<%= data.url %>" target="_blank" class="btn-primary" style="display: block; text-align: center; padding: 0.75rem; text-decoration: none;">
+            <a href="<%= data.url %>" target="_blank" rel="noopener noreferrer" class="btn-primary" style="display: block; text-align: center; padding: 0.75rem; text-decoration: none;">
               🚀 List on <%= data.url.includes('ebay') ? 'eBay' : data.url.includes('posh') ? 'Poshmark' : data.url.includes('mercari') ? 'Mercari' : data.url.includes('vestiaire') ? 'Vestiaire' : data.url.includes('realreal') ? 'RealReal' : 'Fashionphile' %>
             </a>
           </div>
@@ -357,7 +357,7 @@
               <h6 style="margin: 0 0 0.5rem 0; font-size: 1.2rem;"><%= service.name %></h6>
               <p><strong>Cost:</strong> <%= service.cost %></p>
               <p><strong>Method:</strong> <%= service.method %></p>
-              <p><strong>URL:</strong> <a href="<%= service.url %>" target="_blank"><%= service.url %></a></p>
+              <p><strong>URL:</strong> <a href="<%= service.url %>" target="_blank" rel="noopener noreferrer"><%= service.url %></a></p>
               <p style="color: #666; font-size: 0.9rem;"><%= service.notes %></p>
             </div>
           <% }); %>

← 48074cf add rel=noopener noreferrer to target=_blank links  ·  back to Handbag Authentication  ·  Add per-site favicon (kills /favicon.ico 404) 934a23a →