Item and enclosure links from feeds are used as href and window.open targets without a scheme check #326

Closed
opened 2026-10-23 05:26:46 -07:00 by rays · 1 comment
Owner

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 (fingerprint ipx/web/item-link-scheme-unchecked).

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 <link>, 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 <link> 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:<ipx host>' 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` (fingerprint `ipx/web/item-link-scheme-unchecked`).
rays added the bug label 2026-10-23 05:26:46 -07:00
Author
Owner

Fixed in 708b69c: the page follows an item's link or an enclosure's URL only when it starts http:// or https:// (webUrl in 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.

Fixed in 708b69c: the page follows an item's link or an enclosure's URL only when it starts http:// or https:// (`webUrl` in 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.
rays closed this issue 2026-10-23 06:08:37 -07:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: rays/ipx#326