Files
self/packages/native-shell-ios/Sources/SelfNativeShell/WebView/RemoteNavigationPolicy.swift
Justin Hernandez f29130587b Harden WebView bridge and asset serving across native shells (#1924)
* security fix

* more security fixes

* fixes

* pr feedback

* Restore remote URL loading in native-shell-ios and native-shell-android

Remove bundled-asset-only loading and SHA-256 integrity checks from both
native shell packages. WebViews now load directly from the remote URL
(default: https://self-app-alpha.vercel.app) over HTTPS, matching the
pattern already implemented in kmp-sdk and self-sdk-swift.

Also fixes ObjC selector mismatch in self-sdk-swift WebViewProviderImpl
for configureRemoteLoading.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Restore remote URL loading in kmp-sdk and self-sdk-swift

Remove bundled-asset-only loading from kmp-sdk AndroidWebViewHost and
self-sdk-swift WebViewProviderImpl. Both now load directly from the
remote URL (default: https://self-app-alpha.vercel.app) over HTTPS.

Adds remoteWebAppBaseUrl to SelfSdkConfig and pipes it through
IosWebViewHost via the new configureRemoteLoading protocol method.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* coderabbit comments

* lint

* coderabbit comments

---------

Co-authored-by: seshanthS <seshanth@protonmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 22:39:27 +05:30

71 lines
2.2 KiB
Swift

// SPDX-License-Identifier: BUSL-1.1
import Foundation
enum RemoteNavigationPolicy {
private static let allowedSubframeHosts: Set<String> = ["verify.didit.me"]
static func makeEntryURL(baseURL: URL?, queryParams: String) -> URL? {
guard let baseURL else { return nil }
guard var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false) else {
return nil
}
let basePath = components.path.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
components.path = "/" + [basePath, "tunnel", "tour", "1"].filter { !$0.isEmpty }.joined(separator: "/")
components.percentEncodedQuery = queryParams.isEmpty ? nil : queryParams
return components.url
}
static func isAllowedMainFrameNavigation(
url: URL,
remoteWebAppBaseURL: URL?,
isDebugMode: Bool
) -> Bool {
if isDebugMode {
return url.scheme == "http" &&
url.host == "localhost" &&
resolvedPort(for: url) == 5173
}
guard let remoteWebAppBaseURL,
remoteWebAppBaseURL.scheme == "https",
remoteWebAppBaseURL.host != nil else {
return false
}
return url.scheme == remoteWebAppBaseURL.scheme &&
url.host == remoteWebAppBaseURL.host &&
resolvedPort(for: url) == resolvedPort(for: remoteWebAppBaseURL)
}
static func isAllowedSubframeNavigation(
url: URL,
remoteWebAppBaseURL: URL?,
isDebugMode: Bool
) -> Bool {
if isAllowedMainFrameNavigation(url: url, remoteWebAppBaseURL: remoteWebAppBaseURL, isDebugMode: isDebugMode) {
return true
}
guard url.scheme == "https", let host = url.host else {
return false
}
let port = resolvedPort(for: url)
return allowedSubframeHosts.contains(host) && port == 443
}
static func resolvedPort(for url: URL) -> Int {
if let port = url.port {
return port
}
switch url.scheme {
case "https":
return 443
case "http":
return 80
default:
return -1
}
}
}