From 8f8e461499afa48dd9d556babfb0b7eeab8d95ff Mon Sep 17 00:00:00 2001 From: Antonio Aversa Date: Thu, 8 May 2025 17:47:41 +0200 Subject: [PATCH 1/6] DART-246 Modify rule S7409: Add Dart language --- rules/S7409/dart/metadata.json | 2 + rules/S7409/dart/rule.adoc | 136 +++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 rules/S7409/dart/metadata.json create mode 100644 rules/S7409/dart/rule.adoc diff --git a/rules/S7409/dart/metadata.json b/rules/S7409/dart/metadata.json new file mode 100644 index 00000000000..0af704eda69 --- /dev/null +++ b/rules/S7409/dart/metadata.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/rules/S7409/dart/rule.adoc b/rules/S7409/dart/rule.adoc new file mode 100644 index 00000000000..0cc4e569cd4 --- /dev/null +++ b/rules/S7409/dart/rule.adoc @@ -0,0 +1,136 @@ +include::../description.adoc[] + +include::../ask-yourself.adoc[] + +== Recommended Secure Coding Practices + +=== Disable JavaScript + +If it is possible to disable JavaScript in the WebView, this is the most secure option. + +When using the https://pub.dev/packages/webview_flutter[`webview_flutter` package] v4 and above, by default +JavaScript is disabled in every https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewController-class.html[`WebViewController`] +instance. Therefore, https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewController/setJavaScriptMode.html[`setJavaScriptMode`] +does not need to be explicitly called with https://pub.dev/documentation/webview_flutter/latest/webview_flutter/JavaScriptMode.html[`JavaScriptMode.disabled`]. + +The same applies to https://pub.dev/documentation/webview_flutter/3.0.4/webview_flutter/WebView-class.html[`WebView`] +instances in https://pub.dev/packages/webview_flutter[`webview_flutter` package] v3 and below: there is no +need to explicitly set the named constructor parameter `javascriptMode` to https://pub.dev/documentation/webview_flutter/3.0.4/webview_flutter/JavascriptMode.html[`JavascriptMode.disabled`]. + +When using the https://pub.dev/packages/flutter_inappwebview[`flutter_inappwebview` package], on the other hand, +the default behavior is to enable JavaScript. Therefore, it is necessary to set the `enableJavaScript` parameter +to `false` in https://pub.dev/documentation/flutter_inappwebview/latest/flutter_inappwebview/InAppWebViewSettings-class.html[InAppWebViewSettings] +and https://pub.dev/documentation/flutter_inappwebview/latest/flutter_inappwebview/InAppWebViewOptions-class.html[InAppWebViewOptions] +instances. + +Of course, sometimes it is necessary to enable, or keep enabled, JavaScript, in which case the following +recommendations should be considered. + +=== Remove JavaScript interface when loading untrusted content + +JavaScript interfaces can be removed at a later point. It is recommended to remove the JavaScript +interface when it is no longer needed. If it is needed for a longer time, consider removing it before +loading untrusted content. This can be done by calling `webViewController.removeJavaScriptChannel('channelName')`. + +A good place to do this in Flutter is inside navigation callbacks like https://pub.dev/documentation/webview_flutter/latest/webview_flutter/NavigationDelegate/onNavigationRequest.html[`NavigationDelegate.onNavigationRequest`] +for the https://pub.dev/packages/webview_flutter[`webview_flutter` package] or https://pub.dev/documentation/flutter_inappwebview/latest/flutter_inappwebview/PlatformInAppBrowserEvents/shouldOverrideUrlLoading.html[`WebViewClient.shouldOverrideUrlLoading`] +for the https://pub.dev/packages/flutter_inappwebview[`flutter_inappwebview` package] package, where you can check the URL being loaded and remove the JavaScript channel +if the content is untrusted. + +=== Alternative methods to implement native bridges + +If a native channel has to be added to the web view or its controller, and it is impossible to remove it +at a later point, consider using an alternative method that offers more control over the communication flow. + +For the `webview_flutter` package, use a more controlled approach with https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewController/runJavaScript.html[`runJavaScript`] +with origin validation in your message handling code. + +For the `flutter_inappwebview` package, use https://pub.dev/documentation/flutter_inappwebview/latest/flutter_inappwebview/InAppWebViewController/evaluateJavascript.html[`evaluateJavascript`] +combined with https://pub.dev/documentation/flutter_inappwebview/latest/flutter_inappwebview/WebMessageListener-class.html[`WebMessageListener`] +which allows you to restrict the origins that can send messages to your application. + +== Sensitive Code Example + +[source,dart] +---- +final _controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..addJavaScriptChannel("channel", onMessageReceived: (m) {}); // Sensitive +---- + +== Compliant Solution + +The most secure option is to not enable, or disable, JavaScript entirely. S6362 further explains why it should not +be enabled unless absolutely necessary. + +[source,dart] +---- +final _controller = WebViewController(); +---- + +If possible, remove the JavaScript channel after it is no longer needed, or before loading any untrusted content. + +[source,dart] +---- +_controller.removeJavaScriptChannel("channel"); +---- + +If a JavaScript channel must be used, consider using `runJavascript` (in the case of `flutter_webview`, and +`evaluateJavascript` in the case of `flutter_inappwebview`) instead. This allows you to restrict the origins that +can send messages to the JavaScript bridge. + +[source,dart] +---- +import 'package:webview_flutter/webview_flutter.dart'; + +class _SecureWebViewExampleState extends State { + late final WebViewController _controller; + + // List of trusted origins + final Set _trustedOrigins = { + 'https://example.com', + 'https://yourtrustedsite.com', + }; + + void _setupJavaScriptMessageReceiver() { + // Inject JavaScript code that sets up a message handler + _controller.runJavaScript(''' + window.addEventListener('message', function(event) { + // Origin validation happens on the JavaScript side + if (${_trustedOrigins.map((origin) => "event.origin === '$origin'").join(' || ')}) { + // Process message from trusted origin + window.flutter_inappwebview?.callHandler('messageHandler', + JSON.stringify({ + origin: event.origin, + data: event.data + }) + ); + } else { + console.warn('Blocked message from untrusted origin: ' + event.origin); + } + }, false); + '''); + } + + // Method to receive and process messages from WebView with validation + Future _processMessageFromWebView() async { + // Getting the document's origin to validate it's trusted + final String originScript = 'window.location.origin'; + final String origin = await _controller.runJavaScriptReturningResult(originScript) as String; + + if (_trustedOrigins.contains(origin)) { + // Safe to get and process messages as we've validated the origin + final String messageScript = 'window.getLatestMessage()'; // Assuming such a function exists + final String messageJson = await _controller.runJavaScriptReturningResult(messageScript) as String; + + // Process the message + debugPrint('Received valid message from $origin: $messageJson'); + // Process messageJson as needed... + } else { + debugPrint('Blocked processing message from untrusted origin: $origin'); + } + } +} +---- + +include::../see.adoc[] \ No newline at end of file From 9d5f508fe9b696e13621abfccf697fdc2a250bdc Mon Sep 17 00:00:00 2001 From: Antonio Aversa Date: Thu, 8 May 2025 18:33:09 +0200 Subject: [PATCH 2/6] Fix package name --- rules/S7409/dart/rule.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rules/S7409/dart/rule.adoc b/rules/S7409/dart/rule.adoc index 0cc4e569cd4..d9def579b58 100644 --- a/rules/S7409/dart/rule.adoc +++ b/rules/S7409/dart/rule.adoc @@ -75,7 +75,7 @@ If possible, remove the JavaScript channel after it is no longer needed, or befo _controller.removeJavaScriptChannel("channel"); ---- -If a JavaScript channel must be used, consider using `runJavascript` (in the case of `flutter_webview`, and +If a JavaScript channel must be used, consider using `runJavascript` (in the case of `webview_flutter`, and `evaluateJavascript` in the case of `flutter_inappwebview`) instead. This allows you to restrict the origins that can send messages to the JavaScript bridge. @@ -133,4 +133,4 @@ class _SecureWebViewExampleState extends State { } ---- -include::../see.adoc[] \ No newline at end of file +include::../see.adoc[] From 9c5bde6712427dcfec3d0a8759245557375c3c30 Mon Sep 17 00:00:00 2001 From: Pierre-Loup Date: Fri, 16 May 2025 17:33:26 +0200 Subject: [PATCH 3/6] Review and update S7409 for Dart --- rules/S7409/dart/rule.adoc | 96 +++++++++++++++----------------------- 1 file changed, 37 insertions(+), 59 deletions(-) diff --git a/rules/S7409/dart/rule.adoc b/rules/S7409/dart/rule.adoc index d9def579b58..b3fc14a574f 100644 --- a/rules/S7409/dart/rule.adoc +++ b/rules/S7409/dart/rule.adoc @@ -30,7 +30,7 @@ recommendations should be considered. JavaScript interfaces can be removed at a later point. It is recommended to remove the JavaScript interface when it is no longer needed. If it is needed for a longer time, consider removing it before -loading untrusted content. This can be done by calling `webViewController.removeJavaScriptChannel('channelName')`. +loading untrusted content. This can be done by calling `webViewController.removeJavaScriptChannel('channelName')` for the https://pub.dev/packages/webview_flutter[`webview_flutter` package] or `webViewController.removeJavaScriptHandler('channelName')` for the https://pub.dev/packages/flutter_inappwebview[`flutter_inappwebview` package]. A good place to do this in Flutter is inside navigation callbacks like https://pub.dev/documentation/webview_flutter/latest/webview_flutter/NavigationDelegate/onNavigationRequest.html[`NavigationDelegate.onNavigationRequest`] for the https://pub.dev/packages/webview_flutter[`webview_flutter` package] or https://pub.dev/documentation/flutter_inappwebview/latest/flutter_inappwebview/PlatformInAppBrowserEvents/shouldOverrideUrlLoading.html[`WebViewClient.shouldOverrideUrlLoading`] @@ -42,15 +42,13 @@ if the content is untrusted. If a native channel has to be added to the web view or its controller, and it is impossible to remove it at a later point, consider using an alternative method that offers more control over the communication flow. -For the `webview_flutter` package, use a more controlled approach with https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewController/runJavaScript.html[`runJavaScript`] -with origin validation in your message handling code. +For the `webview_flutter` package, there is currently no way to know the origin of an incoming JavaScript message. Therefore using JavaScript communication with `webview_flutter` is discouraged. -For the `flutter_inappwebview` package, use https://pub.dev/documentation/flutter_inappwebview/latest/flutter_inappwebview/InAppWebViewController/evaluateJavascript.html[`evaluateJavascript`] -combined with https://pub.dev/documentation/flutter_inappwebview/latest/flutter_inappwebview/WebMessageListener-class.html[`WebMessageListener`] -which allows you to restrict the origins that can send messages to your application. +For the `flutter_inappwebview` package, use https://pub.dev/documentation/flutter_inappwebview/latest/flutter_inappwebview/WebMessageListener-class.html[`WebMessageListener`] which allows you to restrict the origins that can send messages to your application. == Sensitive Code Example +https://pub.dev/packages/webview_flutter[`webview_flutter`] [source,dart] ---- final _controller = WebViewController() @@ -58,11 +56,24 @@ final _controller = WebViewController() ..addJavaScriptChannel("channel", onMessageReceived: (m) {}); // Sensitive ---- +https://pub.dev/packages/flutter_inappwebview[`flutter_inappwebview`] +---- +InAppWebView( + onWebViewCreated: (controller) { + controller?.addJavaScriptHandler( // Sensitive + handlerName: "channel", + callback: (args) {} + ); + }, +); +---- + == Compliant Solution The most secure option is to not enable, or disable, JavaScript entirely. S6362 further explains why it should not be enabled unless absolutely necessary. +https://pub.dev/packages/webview_flutter[`webview_flutter`] [source,dart] ---- final _controller = WebViewController(); @@ -70,67 +81,34 @@ final _controller = WebViewController(); If possible, remove the JavaScript channel after it is no longer needed, or before loading any untrusted content. +https://pub.dev/packages/webview_flutter[`webview_flutter`] [source,dart] ---- _controller.removeJavaScriptChannel("channel"); ---- -If a JavaScript channel must be used, consider using `runJavascript` (in the case of `webview_flutter`, and -`evaluateJavascript` in the case of `flutter_inappwebview`) instead. This allows you to restrict the origins that -can send messages to the JavaScript bridge. +https://pub.dev/packages/flutter_inappwebview[`flutter_inappwebview`] +---- +_controller.removeJavaScriptHandler("channel"); +---- + +If a JavaScript channel must be used, consider using `flutter_inappwebview` instead of `webview_flutter`. The Web Message API allows you to restrict the origins that can send messages to the JavaScript bridge. [source,dart] ---- -import 'package:webview_flutter/webview_flutter.dart'; - -class _SecureWebViewExampleState extends State { - late final WebViewController _controller; - - // List of trusted origins - final Set _trustedOrigins = { - 'https://example.com', - 'https://yourtrustedsite.com', - }; - - void _setupJavaScriptMessageReceiver() { - // Inject JavaScript code that sets up a message handler - _controller.runJavaScript(''' - window.addEventListener('message', function(event) { - // Origin validation happens on the JavaScript side - if (${_trustedOrigins.map((origin) => "event.origin === '$origin'").join(' || ')}) { - // Process message from trusted origin - window.flutter_inappwebview?.callHandler('messageHandler', - JSON.stringify({ - origin: event.origin, - data: event.data - }) - ); - } else { - console.warn('Blocked message from untrusted origin: ' + event.origin); - } - }, false); - '''); - } - - // Method to receive and process messages from WebView with validation - Future _processMessageFromWebView() async { - // Getting the document's origin to validate it's trusted - final String originScript = 'window.location.origin'; - final String origin = await _controller.runJavaScriptReturningResult(originScript) as String; - - if (_trustedOrigins.contains(origin)) { - // Safe to get and process messages as we've validated the origin - final String messageScript = 'window.getLatestMessage()'; // Assuming such a function exists - final String messageJson = await _controller.runJavaScriptReturningResult(messageScript) as String; - - // Process the message - debugPrint('Received valid message from $origin: $messageJson'); - // Process messageJson as needed... - } else { - debugPrint('Blocked processing message from untrusted origin: $origin'); - } - } -} +InAppWebView( + onWebViewCreated: (controller) async { + await inAppController.addWebMessageListener( + WebMessageListener( + jsObjectName: "channel", + allowedOriginRules: { + "https://secure-origin", + }, // Restrict to specific origin + onPostMessage: (message, origin, isMainFrame, replyProxy) {}, + ), + ); + }, +); ---- include::../see.adoc[] From 520522e6062a741339331f4e505fe47c4fbb3430 Mon Sep 17 00:00:00 2001 From: Pierre-Loup Date: Tue, 20 May 2025 11:04:04 +0200 Subject: [PATCH 4/6] Add Drt syntax highlighting for code examples --- rules/S7409/dart/rule.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rules/S7409/dart/rule.adoc b/rules/S7409/dart/rule.adoc index b3fc14a574f..266e5779179 100644 --- a/rules/S7409/dart/rule.adoc +++ b/rules/S7409/dart/rule.adoc @@ -57,6 +57,7 @@ final _controller = WebViewController() ---- https://pub.dev/packages/flutter_inappwebview[`flutter_inappwebview`] +[source,dart] ---- InAppWebView( onWebViewCreated: (controller) { @@ -88,6 +89,7 @@ _controller.removeJavaScriptChannel("channel"); ---- https://pub.dev/packages/flutter_inappwebview[`flutter_inappwebview`] +[source,dart] ---- _controller.removeJavaScriptHandler("channel"); ---- From 461e65c03c87c3bda5d7943c12cf4abe43f0d052 Mon Sep 17 00:00:00 2001 From: Pierre-Loup Date: Wed, 21 May 2025 11:01:22 +0200 Subject: [PATCH 5/6] Update S7409 title for all languages --- rules/S7409/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/S7409/metadata.json b/rules/S7409/metadata.json index 02666dcfab1..b2e2ae86327 100644 --- a/rules/S7409/metadata.json +++ b/rules/S7409/metadata.json @@ -1,5 +1,5 @@ { - "title": "Exposing Java objects through JavaScript interfaces is security-sensitive", + "title": "Exposing native code through JavaScript interfaces is security-sensitive", "type": "SECURITY_HOTSPOT", "status": "ready", "remediation": { From a536907ff717bc8322a29c80faa29412c0d5e838 Mon Sep 17 00:00:00 2001 From: Pierre-Loup <49131563+pierre-loup-tristant-sonarsource@users.noreply.github.com> Date: Wed, 21 May 2025 15:17:07 +0200 Subject: [PATCH 6/6] Apply suggestions from code review Co-authored-by: Egon Okerman --- rules/S7409/dart/rule.adoc | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/rules/S7409/dart/rule.adoc b/rules/S7409/dart/rule.adoc index 266e5779179..29169521ca8 100644 --- a/rules/S7409/dart/rule.adoc +++ b/rules/S7409/dart/rule.adoc @@ -8,22 +8,14 @@ include::../ask-yourself.adoc[] If it is possible to disable JavaScript in the WebView, this is the most secure option. -When using the https://pub.dev/packages/webview_flutter[`webview_flutter` package] v4 and above, by default -JavaScript is disabled in every https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewController-class.html[`WebViewController`] -instance. Therefore, https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewController/setJavaScriptMode.html[`setJavaScriptMode`] -does not need to be explicitly called with https://pub.dev/documentation/webview_flutter/latest/webview_flutter/JavaScriptMode.html[`JavaScriptMode.disabled`]. - -The same applies to https://pub.dev/documentation/webview_flutter/3.0.4/webview_flutter/WebView-class.html[`WebView`] -instances in https://pub.dev/packages/webview_flutter[`webview_flutter` package] v3 and below: there is no -need to explicitly set the named constructor parameter `javascriptMode` to https://pub.dev/documentation/webview_flutter/3.0.4/webview_flutter/JavascriptMode.html[`JavascriptMode.disabled`]. +When using the https://pub.dev/packages/webview_flutter[`webview_flutter` package], JavaScript is disabled by default. Therefore, https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewController/setJavaScriptMode.html[`setJavaScriptMode`] does not need to be explicitly called with https://pub.dev/documentation/webview_flutter/latest/webview_flutter/JavaScriptMode.html[`JavaScriptMode.disabled`] on https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewController-class.html[`WebViewController`] (v4 and above) or https://pub.dev/documentation/webview_flutter/3.0.4/webview_flutter/WebView-class.html[`WebView`] (v3 and below). When using the https://pub.dev/packages/flutter_inappwebview[`flutter_inappwebview` package], on the other hand, the default behavior is to enable JavaScript. Therefore, it is necessary to set the `enableJavaScript` parameter -to `false` in https://pub.dev/documentation/flutter_inappwebview/latest/flutter_inappwebview/InAppWebViewSettings-class.html[InAppWebViewSettings] -and https://pub.dev/documentation/flutter_inappwebview/latest/flutter_inappwebview/InAppWebViewOptions-class.html[InAppWebViewOptions] -instances. +to `false` in instances of https://pub.dev/documentation/flutter_inappwebview/latest/flutter_inappwebview/InAppWebViewSettings-class.html[InAppWebViewSettings] +and https://pub.dev/documentation/flutter_inappwebview/latest/flutter_inappwebview/InAppWebViewOptions-class.html[InAppWebViewOptions]. -Of course, sometimes it is necessary to enable, or keep enabled, JavaScript, in which case the following +Of course, sometimes it is necessary to enable JavaScript, in which case the following recommendations should be considered. === Remove JavaScript interface when loading untrusted content @@ -39,7 +31,7 @@ if the content is untrusted. === Alternative methods to implement native bridges -If a native channel has to be added to the web view or its controller, and it is impossible to remove it +If a native channel has to be added to the WebView or its controller, and it is impossible to remove it at a later point, consider using an alternative method that offers more control over the communication flow. For the `webview_flutter` package, there is currently no way to know the origin of an incoming JavaScript message. Therefore using JavaScript communication with `webview_flutter` is discouraged.