Item and enclosure links from feeds are used as href and window.open targets without a scheme check #326
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found by the security-audit skill (run-1, 2026-10-20). Source-read only: nothing was run, so this is a lead that still needs its local check, not a confirmed finding. Two independent verifiers did not disprove it.
A feed's item link (RSS , line 788; Atom alternate href, line 861) and enclosure URL are stored as the publisher wrote them. clean_row (web.rs:1564) sanitises only description and markdown_html and returns link unchanged; the directory listing (web.rs:1097) passes another server's item links through the same way. The page renders them as 'Open the original' (items.ts:418, href with target=_blank rel=noopener noreferrer), as directory item links (dialogs.ts:284, target=_blank rel=noopener), as an enclosure's 'View in a new tab' when the file is not on disk (items.ts:484), and opens e.link with window.open(...,'_blank','noopener') on the v key (player.ts:264). esc() (util.ts:19-20) escapes & < > " ' only, so a javascript: or data: URL survives as the href. The server already enforces http(s) elsewhere (web.rs:1212, 2157, 2488) and ammonia strips the same schemes in show notes, so these link paths are the inconsistent exception. Every sink opens a new, noopener browsing context and the app page has no CSP; whether any current browser then runs the script with the iPX origin (as the signed-in user) is the decisive, unobserved fact. Requires a user click or the v key on an item from a malicious feed the user subscribed to (or a directory listing).
Claimed root cause. Neither the feed parser (crates/feed/src/lib.rs:788, 861, enclosure parsing) nor the web layer (clean_row, directory listing mapping) restricts Entry.link or Enclosure.url to http(s); the page relies on esc(), which escapes characters but not URL schemes.
Local check. {'local': "In a sandboxed checkout, add a Playwright fixture feed (tests fixtures served by the browser suite's own daemon under /tmp/ipx-ui-test) with one item whose is javascript:opener===null&&(document.title='xss:'+document.domain) and an image enclosure (not downloaded) whose url is the same javascript: URL. Open the item, click Open the original, the enclosure's View in a new tab, and press v; in each of chromium, firefox and webkit collect every page in the context (context.pages()) and assert whether any has the title 'xss:' or can read document.cookie / fetch('/api/me'). Repeat for a directory item link via a stub directory. Regardless of the result, add a tests/page-unit.js check (and a cargo test in crates/feed) that a non-http(s) link or enclosure url is not rendered as href, with the fix restricting link and enclosure url to http/https in the parser or in clean_row and the directory mapping.", 'deployment': "Owner-observed, non-destructive: on a scratch iPX (not production), subscribe a test account to a local feed with the javascript: link above and repeat the clicks in the household's real browsers (desktop Chrome/Firefox, iOS Safari home-screen app with the Glass theme); record whether any script runs on the iPX origin."}
Owner check (no probing). Owner-observed, non-destructive: on a scratch iPX (not production), subscribe a test account to a local feed with the javascript: link above and repeat the clicks in the household's real browsers (desktop Chrome/Firefox, iOS Safari home-screen app with the Glass theme); record whether any script runs on the iPX origin.
Full detail:
/config/security-audit-skill/ipx/run-1/NEEDS-VALIDATION.md(fingerprintipx/web/item-link-scheme-unchecked).Fixed in
708b69c: the page follows an item's link or an enclosure's URL only when it starts http:// or https:// (webUrlin web/src/util.ts), in all four places (Open the original, View in a new tab, the Directory's item links, the v key). Deployed in ipx-web.