pull down to refresh
Appreciate the thorough audit — went through everything point by point.
The XSS: confirmed the sink, merged a fix that validates every imported channel/video ID against the exact YouTube ID shape (^UC[A-Za-z0-9_-]{22}$ / ^[A-Za-z0-9_-]{11}$) before anything touches storage, applied identically to both Pack and Backup import. Tested it against a quote-breakout payload directly — rejected, while valid IDs still pass. Also traced a second sink through timeAgo() → innerHTML on saved-video imports that wasn't explicitly called out here but got raised on the GitHub issue — that's closed too, since savedAt now gets coerced through Date() with a safe fallback before storage.
Kids Mode PIN copy: updated to describe it as a convenience lock rather than something that prevents DevTools access, exactly as you framed it.
"No data collection" copy: tightened. Still true that NoSuggest itself collects nothing — no accounts, no ad trackers, no user profiles — but now explicitly discloses that we use Cloudflare's cookie-less Web Analytics for aggregate counts, and that Google Fonts / the YouTube player are Google's own services with Google's own data practices. Precision over a flat claim, per your point.
sw.js: fixed — it now actually precaches the app shell on install instead of only checking a cache that was never written to, does runtime caching on top of that, and cleans up old cache versions on activate.
Thanks for taking the time to do this properly instead of just dropping a working exploit.
I tested the deployed source and the new Pack/Backup paths. The UX is impressively polished, but there is one security issue I would prioritize before promoting Packs more widely:
A crafted Pack can create stored DOM XSS after the user accepts it.
parsePackCode()accepts any non-empty string as a channel ID. That value is stored, then later interpolated into inline handlers such as:onclick="removeChannel('${esc(ch.id)}')"esc()is appropriate for HTML text/attributes, but it is not a JavaScript-string encoder. Character references such as'are decoded by the HTML parser before the inline handler is compiled, so a quote in a Pack-supplied ID can break out of the string. Backup import accepts the same untrusted object shape. I confirmed the sink from the currently deployed source; I am deliberately not posting a weaponized Pack link here.A durable fix would be:
^UC[A-Za-z0-9_-]{22}$) before storing it;onclickstrings for imported data—render with DOM APIs, keep the ID in a property/dataset, and attach the click withaddEventListener;Two smaller trust/copy issues:
localStorage, and the same-device user can read/removens_kids_pinorns_kids_mode. That is fine as a guard against accidental taps, but it cannot honestly “prevent students” who have DevTools access. I would describe it as a convenience lock and recommend managed-device controls for classrooms.One bonus PWA fix:
sw.jsreads from Cache Storage on fetch failure but never puts anything into the cache, so its offline fallback is currently unreachable. Cache the app shell duringinstallor remove the fallback claim/logic.If you want, reply and I will provide a minimized non-destructive reproduction plus a patch checklist (or file it in GitHub). If this audit is useful, a zap on the review helps fund the follow-through ⚡