diff --git a/dashboard/inject.mjs b/dashboard/inject.mjs
index 331171c..3f7bd4f 100644
--- a/dashboard/inject.mjs
+++ b/dashboard/inject.mjs
@@ -58,6 +58,16 @@ function geoTagText(text) {
return null;
}
+function sanitizeExternalUrl(raw) {
+ if (!raw) return undefined;
+ try {
+ const url = new URL(raw);
+ return url.protocol === 'http:' || url.protocol === 'https:' ? url.toString() : undefined;
+ } catch {
+ return undefined;
+ }
+}
+
// === RSS Fetching ===
async function fetchRSS(url, source) {
try {
@@ -69,7 +79,7 @@ async function fetchRSS(url, source) {
while ((match = itemRegex.exec(xml)) !== null) {
const block = match[1];
const title = (block.match(/
(?:)?<\/title>/)?.[1] || '').trim();
- const link = (block.match(/(?:)?<\/link>/)?.[1] || '').trim();
+ const link = sanitizeExternalUrl((block.match(/(?:)?<\/link>/)?.[1] || '').trim());
const pubDate = block.match(/(.*?)<\/pubDate>/)?.[1] || '';
if (title && title !== source) items.push({ title, date: pubDate, source, url: link || undefined });
}
@@ -421,7 +431,7 @@ function buildNewsFeed(rssNews, gdeltData, tgUrgent, tgTop) {
const geo = geoTagText(a.title);
feed.push({
headline: a.title.substring(0, 100), source: 'GDELT', type: 'gdelt',
- timestamp: new Date().toISOString(), region: geo?.region || 'Global', urgent: false, url: a.url
+ timestamp: new Date().toISOString(), region: geo?.region || 'Global', urgent: false, url: sanitizeExternalUrl(a.url)
});
}
}
diff --git a/dashboard/public/jarvis.html b/dashboard/public/jarvis.html
index ff2bf47..6af58f0 100644
--- a/dashboard/public/jarvis.html
+++ b/dashboard/public/jarvis.html
@@ -958,6 +958,7 @@ function renderRight(){
// === HELPERS ===
function getAge(d){const ms=Date.now()-new Date(d).getTime();const h=Math.floor(ms/3600000);if(h<1)return 'just now';if(h<24)return h+'h ago';return Math.floor(h/24)+'d ago'}
function cleanText(t){return t.replace(/'/g,"'").replace(/!/g,"!").replace(/&/g,"&").replace(/<[^>]+>/g,'')}
+function safeExternalUrl(raw){try{const u=new URL(raw,location.href);return u.protocol==='http:'||u.protocol==='https:'?u.toString():null}catch{return null}}
// === BOOT SEQUENCE ===
function runBoot(){
@@ -1055,7 +1056,10 @@ function init(){
// Open article links from ticker cards
document.addEventListener('click',e=>{
const card=e.target.closest('.tk-card[data-url]');
- if(card) window.open(card.dataset.url,'_blank','noopener');
+ if(card){
+ const url=safeExternalUrl(card.dataset.url);
+ if(url) window.open(url,'_blank','noopener');
+ }
});
}