Skip to content

Commit acec906

Browse files
committed
seo-shot: fix XSS via innerHTML, fix @type label as Graph
1 parent 16d0ee8 commit acec906

1 file changed

Lines changed: 16 additions & 12 deletions

File tree

seo-shot/popup/popup.js

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ function runScanner() {
8888
results.push({ name, status: "fail", detail, points });
8989
}
9090

91+
function esc(str) {
92+
return String(str).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
93+
}
94+
9195
try {
9296
const title = doc.querySelector("title");
9397
const titleText = title?.textContent?.trim() || "";
@@ -98,17 +102,17 @@ function runScanner() {
98102
} else if (titleLen < 30) {
99103
warn(
100104
"Title tag",
101-
`Too short (${titleLen} chars). Aim for 50–60. Current: "${titleText}"`,
105+
`Too short (${titleLen} chars). Aim for 50–60. Current: "${esc(titleText)}"`,
102106
5
103107
);
104108
} else if (titleLen > 70) {
105109
warn(
106110
"Title tag",
107-
`Too long (${titleLen} chars). Aim for 50–60. Current: "${titleText.substring(0, 60)}"`,
111+
`Too long (${titleLen} chars). Aim for 50–60. Current: "${esc(titleText.substring(0, 60))}\u2026"`,
108112
5
109113
);
110114
} else {
111-
pass("Title tag", `${titleLen} chars — good length. "${titleText}"`, 10);
115+
pass("Title tag", `${titleLen} chars — good length. "${esc(titleText)}"`, 10);
112116
}
113117

114118
const metaDesc = doc.querySelector('meta[name="description"]');
@@ -141,7 +145,7 @@ function runScanner() {
141145
} else {
142146
pass(
143147
"H1 heading",
144-
`One H1 found: "${h1s[0].textContent.trim().substring(0, 60)}"`,
148+
`One H1 found: "${esc(h1s[0].textContent.trim().substring(0, 60))}"`,
145149
10
146150
);
147151
}
@@ -250,7 +254,7 @@ function runScanner() {
250254
} else {
251255
pass(
252256
"Canonical URL",
253-
`Present: <code>${canonicalHref.substring(0, 50)}</code>`,
257+
`Present: <code>${esc(canonicalHref.substring(0, 50))}</code>`,
254258
10
255259
);
256260
}
@@ -266,7 +270,7 @@ function runScanner() {
266270
5
267271
);
268272
} else {
269-
pass("Robots meta", `Set to: <code>${robotsContent}</code>`, 10);
273+
pass("Robots meta", `Set to: <code>${esc(robotsContent)}</code>`, 10);
270274
}
271275

272276
const jsonLd = doc.querySelectorAll('script[type="application/ld+json"]');
@@ -282,11 +286,11 @@ function runScanner() {
282286
types = Array.from(jsonLd)
283287
.map((el) => JSON.parse(el.textContent))
284288
.filter(Boolean)
285-
.map((d) => d["@type"] || d["@graph"] ? "Graph" : "Unknown");
289+
.map((d) => d["@type"] || (d["@graph"] ? "Graph" : "Unknown"));
286290
} catch (_) {}
287291
pass(
288292
"Structured data",
289-
`${jsonLd.length} JSON-LD block(s) found${types.length ? ": " + types.slice(0, 3).join(", ") : ""}.`,
293+
`${jsonLd.length} JSON-LD block(s) found${types.length ? ": " + types.slice(0, 3).map(esc).join(", ") : ""}.`,
290294
10
291295
);
292296
}
@@ -303,7 +307,7 @@ function runScanner() {
303307
if (vpContent.includes("width=device-width")) {
304308
pass("Mobile viewport", "Present with width=device-width — mobile-friendly.", 10);
305309
} else {
306-
warn("Mobile viewport", "Found but missing width=device-width.", 5);
310+
warn("Mobile viewport", `Found but missing width=device-width. Content: <code>${esc(vpContent)}</code>`, 5);
307311
}
308312
}
309313

@@ -315,7 +319,7 @@ function runScanner() {
315319
5
316320
);
317321
} else {
318-
pass("Language", `Set to <code>${htmlLang}</code>.`, 10);
322+
pass("Language", `Set to <code>${esc(htmlLang)}</code>.`, 10);
319323
}
320324

321325
const totalPoints = results.reduce((sum, r) => sum + r.points, 0);
@@ -331,7 +335,7 @@ function runScanner() {
331335
{
332336
name: "Scanner error",
333337
status: "fail",
334-
detail: `Could not complete scan: ${e.message}. The page may have restricted access to its DOM.`,
338+
detail: `Could not complete scan: ${esc(e.message)}. The page may have restricted access to its DOM.`,
335339
points: 0,
336340
},
337341
],
@@ -358,7 +362,7 @@ function renderResults(data) {
358362
<div class="check-item">
359363
<div class="check-header">
360364
<span class="check-name">${r.name}</span>
361-
<span class="check-status ${r.status}">${r.status === "pass" ? " Pass" : r.status === "warn" ? " Warn" : " Fail"}</span>
365+
<span class="check-status ${r.status}">${r.status === "pass" ? "\u2713 Pass" : r.status === "warn" ? "\u26a0 Warn" : "\u2717 Fail"}</span>
362366
</div>
363367
<div class="check-detail">${r.detail}</div>
364368
</div>`

0 commit comments

Comments
 (0)