Native Content-Feed Link Hardening
Filed 2026-07-15 from the range-bag-suggested-items PR-B review (flagged by the worker lane, verified and ruled by the architect). Content-feed surface — belongs to whoever next works that code, not the range-bag lane.
Summary
The native content-feed detail screens open the feed-supplied actionUrl without the
scheme allowlist (and, on Android, without the defensive open) that the PWA and the
range-bag store links already use. Low severity today, but the pattern should be
consistent everywhere a server-supplied URL is opened.
Requirements
- Android and iOS content-feed detail screens open
actionUrlonly when it passes an http/https scheme allowlist (mirrorsafeStoreLink/ the PWA messages.rs check). - The Android open is defensive: an unhandlable or malformed URL must not crash the
app (try/catch around
startActivity). - Unit tests pin the allowlist on both platforms (same five-case shape as the
range-bag tests: https, http,
javascript:,ftp:, null).
Context
PR-B (#238) established the client-side pattern for opening server-supplied URLs: an
http/https allowlist plus a safe external open (safe_store_link /
safeStoreLink on all three platforms). The PWA’s content-feed counterpart already
follows it (apps/pwa/src/pages/messages.rs:283-288 checks the scheme before
rendering the anchor). The native content-feed screens predate the pattern.
Details
- Android — two defects (
apps/android/.../contentfeed/ContentFeedDetailScreen.kt:94):Intent(Intent.ACTION_VIEW, Uri.parse(entry.actionUrl))+startActivitywith no scheme allowlist and no try/catch.- A malicious/unexpected URL deep-links users into arbitrary scheme handlers.
- An unhandlable or malformed URL throws
ActivityNotFoundExceptionand crashes the app on tap — no attacker needed; a content typo is enough.
- iOS — same family, milder (
apps/ios/RangeDayPro/ContentFeed/ContentFeedDetailView.swift:29):Link(destination:)hands any scheme to the OS. No crash path, but no allowlist. - Fix precedent: the PWA content-feed check (
messages.rs:283-288) and PR-B’ssafeStoreLinkpattern — roughly 10 lines per platform plus unit tests: allowlist the scheme, open defensively. range-bag-packing-checklist adds the try/catch half to the Android range-bag open (scope addition recorded there), so by the time this item is picked up the range-bag code models both halves.
Severity is honestly low: feed entries are server-authored — there is no end-user
injection path, and the dashboard has no actionUrl authoring UI today. Filed so the
inconsistency is tracked, not because anything is exploitable right now.
Open Questions
None — scope and precedent are settled; this is a mechanical hardening pass for whoever next touches the content-feed surface.