[object Object]

← back to Rentv

chore: harden sublease (listing_url XSS guard, DELETE id validation), refactor, v0.5.0 (session close)

6e2fe1310d32dbbd8d19cfd16a17ecad2f016edf · 2026-07-22 20:24:42 -0700 · Steve

Files touched

Diff

commit 6e2fe1310d32dbbd8d19cfd16a17ecad2f016edf
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed Jul 22 20:24:42 2026 -0700

    chore: harden sublease (listing_url XSS guard, DELETE id validation), refactor, v0.5.0 (session close)
---
 package-lock.json | 4 ++--
 package.json      | 2 +-
 public/desk.html  | 3 +--
 server.js         | 6 +++++-
 4 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 738bb014..7cb8addd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "rentv-v1",
-  "version": "0.4.0",
+  "version": "0.5.0",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "rentv-v1",
-      "version": "0.4.0",
+      "version": "0.5.0",
       "dependencies": {
         "express": "^4.21.2"
       }
diff --git a/package.json b/package.json
index 49c9fe27..d6d27833 100644
--- a/package.json
+++ b/package.json
@@ -1 +1 @@
-{"name":"rentv-v1","version":"0.4.0","private":true,"main":"server.js","scripts":{"start":"node server.js","pull":"node scripts/pull-news.mjs"},"dependencies":{"express":"^4.21.2"}}
\ No newline at end of file
+{"name":"rentv-v1","version":"0.5.0","private":true,"main":"server.js","scripts":{"start":"node server.js","pull":"node scripts/pull-news.mjs"},"dependencies":{"express":"^4.21.2"}}
\ No newline at end of file
diff --git a/public/desk.html b/public/desk.html
index 1703f7a9..5a969d03 100644
--- a/public/desk.html
+++ b/public/desk.html
@@ -99,7 +99,7 @@ function actions(k,x){
   if(k==='firms')return [A(LI_C(x.name),'🏢','LinkedIn'),A(WEB(x.name),'🌐','Website'),A(MAPS(x.name+' '+loc(x)),'📍','HQ / Map'),x.license_no?A(DRE(x.license_no),'🪪','DRE'):'',A(GOOG(x.name+' brokers '+loc(x)),'🔎','Google')].filter(Boolean).join('');
   if(k==='owners'){const ad=[x.address,x.city].filter(Boolean).join(', ');return [A(MAPS(ad),'📍','Map'),A(GOOG(ad+' '+(x.use_desc||'')+' owner'),'🔎','Owner / deal'),A('https://www.google.com/maps/@?api=1&map_action=pano&viewpoint='+enc(ad),'📸','Street View'),A(ASSR(x.ain),'🏛️','Assessor')].join('');}
   if(k==='sublease'){const ad=[x.address,x.city].filter(Boolean).join(', ');return [
-    x.listing_url?A(x.listing_url,'🔗','Listing'):'',
+    (x.listing_url&&/^https?:\/\//i.test(x.listing_url))?A(x.listing_url,'🔗','Listing'):'',
     A(MAPS(ad),'📍','Map'),
     A('https://www.google.com/maps/@?api=1&map_action=pano&viewpoint='+enc(ad),'📸','Street View'),
     x.broker_name?A(LI_P(x.broker_name,x.firm_name),'👤','Broker'):'',
@@ -164,7 +164,6 @@ function renderAll(){directory();analytics();applyScopeViews();}
 function directory(){
   const rows=scopeData();
   $('v-directory').innerHTML=`<div class="md"><div class="list"><div class="search"><input id="dq" placeholder="Search ${D.scope}…"><div class="fresh" id="dcount" style="padding:7px 2px 0"></div></div><div class="rows" id="drows"></div></div><div class="detail" id="ddetail"></div></div>`;
-  const q='';
   function list(){let r=rows.slice();const v=($('dq').value||'').toLowerCase();if(v)r=r.filter(x=>JSON.stringify(x).toLowerCase().includes(v));return r;}
   function detail(x){if(!x){$('ddetail').innerHTML='';return;}
     const grid=D.scope==='sublease'?[['SqFt',x.sqft?Number(x.sqft).toLocaleString():'—'],['Asking',subRate(x)],['Term',x.term_months?x.term_months+' mo':'—'],['Available',x.available_date?dstr(x.available_date):'Now'],['Sublandlord',x.sublandlord||'—'],['Listing broker',x.firm_name||'—']]:D.scope==='owners'?[['Assessed',shortUsd(x.assessed_total)],['SqFt',(x.sqft||0).toLocaleString()],['Built',x.year_built||'—'],['AIN',x.ain]]:D.scope==='firms'?[['Agents',(x.agent_count||0).toLocaleString()],['HQ',loc(x)||'—'],['License',x.license_no||'—'],['Source',x.source||'—']]:[['License',x.license_no||'—'],['Type',x.license_type||'—'],['Firm',x.firm_name||'—'],['City',x.city||'—']];
diff --git a/server.js b/server.js
index 083f8b48..1507d81f 100644
--- a/server.js
+++ b/server.js
@@ -266,7 +266,11 @@ function adminGate(req, res, next) {
 }
 app.get('/desk-admin', adminGate, (_req, res) => res.sendFile(path.join(__dirname, 'admin', 'desk-admin.html')));
 app.post('/api/admin/sublease', adminGate, (req, res) => usreWrite('POST', '/api/admin/sublease', req, res));
-app.delete('/api/admin/sublease/:id', adminGate, (req, res) => usreWrite('DELETE', '/api/admin/sublease/' + encodeURIComponent(req.params.id), req, res));
+app.delete('/api/admin/sublease/:id', adminGate, (req, res) => {
+  const id = String(req.params.id).replace(/[^\d]/g, '');
+  if (!id) return res.status(400).json({ ok: false, error: 'bad id' });
+  return usreWrite('DELETE', '/api/admin/sublease/' + id, req, res);
+});
 
 app.use(express.static(PUB, { extensions: ['html'] }));
 app.listen(PORT, () => console.log('rentv-v1 (full site, gated, live data) on ' + PORT));

← 85dbfc62 auto-save: 2026-07-22T20:16:30 (3 files) — data/markets.json  ·  back to Rentv  ·  auto-save: 2026-07-22T20:46:37 (3 files) — data/markets.json 4a206b7a →