fix(security): client href scheme check, outbound fetch timeouts, origin-scoped CORS (closes task/resolver-security-hardening)
Source: task/resolver-security-hardening (plan/steward-linker-security) — defense-in-depth: client trusted resolver href, outbound fetches had no timeout, CORS was blanket-*. - F1: extension/content.js makeLink re-validates primary.url scheme via a new pure Extract.isSafeHttpUrl helper (in extract.js, unit-tested); a non-http(s) url (javascript:/data:/garbage) is dropped and the plain text is kept. - F2: each of the three resolver outbound fetches (Spotify token, Spotify search, ollama /api/chat) now carries signal: AbortSignal.timeout(5000) so a hung upstream fails fast (caught per-candidate -> null), zero-dep. - F3: resolver CORS reflects the request Origin only for moz-extension://* or null (the extension's background-script origin), with Vary: Origin, instead of blanket access-control-allow-origin: *. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -56,6 +56,10 @@
|
||||
const platform = primary.platform;
|
||||
const kind = links.kind === "album" ? "album" : "artist";
|
||||
const noun = kind === "album" ? "album" : "artist";
|
||||
// Re-validate the href scheme client-side: only http(s) becomes a clickable link.
|
||||
// A swapped/compromised/buggy resolver could send a javascript:/data: url; never
|
||||
// inject that into reddit's DOM. Returning null leaves the original text in place.
|
||||
if (!Extract.isSafeHttpUrl(primary.url)) return null;
|
||||
const a = document.createElement("a");
|
||||
a.className = `rsl-link rsl-${platform} rsl-${kind}`;
|
||||
a.textContent = label;
|
||||
@@ -80,7 +84,10 @@
|
||||
for (const m of ms) {
|
||||
if (m.index < last) continue; // skip overlaps
|
||||
if (m.index > last) frag.appendChild(document.createTextNode(text.slice(last, m.index)));
|
||||
frag.appendChild(makeLink(text.substr(m.index, m.length), resolved.get(m.name)));
|
||||
const matchText = text.substr(m.index, m.length);
|
||||
const link = makeLink(matchText, resolved.get(m.name));
|
||||
// makeLink returns null for an unsafe (non-http(s)) href: keep the plain text.
|
||||
frag.appendChild(link || document.createTextNode(matchText));
|
||||
last = m.index + m.length;
|
||||
}
|
||||
if (last < text.length) frag.appendChild(document.createTextNode(text.slice(last)));
|
||||
|
||||
Reference in New Issue
Block a user