We use essential cookies for the website to function, as well as analytics cookies for analyzing and creating statistics of the website performance. To agree to the use of analytics cookies, click "Accept All". You can manage your preferences at any time by clicking "Cookie Settings" on the footer. More Information.

Only Essential Cookies
Accept All
Best PracticesApplication SecuritySecure Development with the ArkWeb Component

Secure Development with the ArkWeb Component

Content overview
Expand

Overview

Objectives

This topic aims to guide you in securely using the ArkWeb component under the Hybrid application development model. Hybrid development involves building dynamically loadable and renderable pages (for example, product promotions and privacy policies) using the Web/HTML5 technology, which are then displayed via the ArkWeb component embedded within the application. Furthermore, you can leverage the JSBridge capability to enable web pages to conveniently access native application features, such as geolocation services, camera, and mobile payment.

While this model enhances development flexibility and web interactivity, it also exposes a significant number of sensitive capabilities to the web side, markedly expanding the attack surface. Additionally, common web vulnerabilities like cross-site scripting (XSS), identity confusion, and plaintext data transmission further undermine the overall security of hybrid applications, increasing the likelihood of high-risk threats such as malicious code injection, privilege abuse, and sensitive information leakage.

Therefore, based on numerous real-world vulnerability cases, this topic provides you with several best practices for secure ArkWeb development, covering secure web resource access, appropriate permission control, and secure transmission of sensitive data, to systematically enhance the overall security posture of applications.

When to Use

This topic applies to applications using the ArkWeb component for hybrid development, including scenarios such as loading dynamic HTML5 pages and invoking native capabilities via JSBridge.

Note: Scenarios requiring access to the full web content, such as pure web browsing or resource search (for example, developing a browser application), are not subject to the guidelines in this topic.

Secure Web Resource Access

Following the best practices below when using ArkWeb for web resource access can enhance the trustworthiness of the web runtime environment and protect critical service operations and data.

Configuring an Allowlist to Restrict Web Content Loaded by the ArkWeb Component

[Description]

Restrict ArkWeb to loading only trusted service web pages, not pages from arbitrary sources. You are advised to use setUrlTrustList() to configure an allowlist for ArkWeb. Once configured, the ArkWeb component will automatically verify the target URL before loading or navigating to a page. Only URLs matching the allowlist will be permitted; others will be automatically blocked, with a warning page displayed.

[Risk]

Attackers may attempt to load malicious URLs into the application's ArkWeb component. The specific impact varies depending on the ArkWeb's usage context but can lead to severe consequences such as account hijacking, privacy leakage, or arbitrary code execution.

[Recommended Practice]

Collapse
Word wrap
Dark theme
Copy code
  1. import { webview } from '@kit.ArkWeb';
  2. import { BusinessError } from '@kit.BasicServicesKit';
  3. import { hilog } from '@kit.PerformanceAnalysisKit';
  4. @Entry
  5. @Component
  6. struct WebComponent {
  7. controller: webview.WebviewController = new webview.WebviewController();
  8. urlTrustList: string =
  9. '{\'UrlPermissionList\':[{\'scheme\':\'https\', \'host\':\'trust.example.com\', \'port\':80, \'path\':\'test\'}]}'
  10. build() {
  11. Column() {
  12. Button('Setting the trustList')
  13. .onClick(() => {
  14. try {
  15. // Set up an allowlist to allow access only to trusted web pages.
  16. this.controller.setUrlTrustList(this.urlTrustList);
  17. } catch (error) {
  18. hilog.error(0x0000, 'ArkWebSecurity',
  19. `ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`);
  20. }
  21. })
  22. Button('Access the trust web')
  23. .onClick(() => {
  24. try {
  25. // Allowlist activated, access to trusted web pages is permitted.
  26. this.controller.loadUrl('https://trust.example.com/test');
  27. } catch (error) {
  28. hilog.error(0x0000, 'ArkWebSecurity',
  29. `ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`);
  30. }
  31. })
  32. Button('Access the untrusted web')
  33. .onClick(() => {
  34. try {
  35. // Allowlist activated, blocking access to untrusted web and displaying an alarm page.
  36. this.controller.loadUrl('http://untrust.example.com/test');
  37. } catch (error) {
  38. hilog.error(0x0000, 'ArkWebSecurity',
  39. `ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`);
  40. }
  41. })
  42. }
  43. }
  44. }

Avoiding Configuring Untrusted Domains to the Allowlist

[Description]

Avoid adding domains not exclusively owned by the service, such as public CDN domains (for example, cdn.example.com), to the allowlist.

[Risk]

Attackers may bypass the allowlist and load malicious URLs.

[Non-compliant Example]

Collapse
Word wrap
Dark theme
Copy code
  1. import { webview } from '@kit.ArkWeb';
  2. import { BusinessError } from '@kit.BasicServicesKit';
  3. import { hilog } from '@kit.PerformanceAnalysisKit';
  4. @Entry
  5. @Component
  6. struct WebComponent {
  7. webviewController: webview.WebviewController = new webview.WebviewController();
  8. urlTrustList: string = '{\'UrlPermissionList\':'
  9. + '[{\'scheme\':\'https\', \'host\':\'cdnjs.cloudflare.com\'},' // Public CDN domains are untrusted.
  10. + '{\'scheme\':\'https\', \'host\':\'cdn.ampproject.org\'}]}' // Public service domains are at risk of being abused.
  11. urlStr: string = '' // Any URL to be loaded.
  12. build() {
  13. Column() {
  14. Web({ src: this.urlStr, controller: this.webviewController })
  15. .javaScriptAccess(true)
  16. .onControllerAttached(() => {
  17. try {
  18. this.webviewController.setUrlTrustList(this.urlTrustList);
  19. } catch (error) {
  20. hilog.error(0x0000, 'ArkWebSecurity',
  21. `ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`);
  22. }
  23. })
  24. }
  25. }
  26. }

Always Validating or Filtering Untrusted Content When Loading Scripts or Resources from External Sources

[Description]

WebviewController provides the loadData() method for loading resources, and the runJavaScript() and runJavaScriptExt() methods for executing scripts, which return execution results via callbacks.

If the JavaScript scripts or resources to be loaded originate from external, controllable, and untrusted sources (for example, passed via Want), their content must be inspected and filtered prior to loading.

[Risk]

Loading untrusted resources or scripts can lead to malicious code injection, resulting in XSS attacks. Additionally, if the ArkWeb component has registered sensitive JavaScriptProxy APIs, these APIs could be maliciously invoked, compromising the security of the Hybrid application.

[Non-compliant Example]

Collapse
Word wrap
Dark theme
Copy code
  1. Web({ src: $rawfile('index.html'), controller: this.controller })
  2. .javaScriptAccess(true)
  3. .onPageEnd(error => {
  4. try {
  5. let jsMethod: string = 'alert("xss")' // External controlled string
  6. this.controller.runJavaScript(jsMethod)
  7. .then((result) => {
  8. hilog.info(0x0000, 'ArkWebSecurity', 'result: ' + result);
  9. })
  10. .catch((error: BusinessError) => {
  11. hilog.error(0x0000, 'ArkWebSecurity', 'error: ' + error);
  12. })
  13. if (error) {
  14. hilog.error(0x0000, 'ArkWebSecurity', 'url: ', error.url);
  15. }
  16. } catch (error) {
  17. hilog.error(0x0000, 'ArkWebSecurity', `ErrorCode: ${error.code}, Message: ${error.message}`);
  18. }
  19. })

The index.html file contains the following content:

Collapse
Word wrap
Dark theme
Copy code
  1. <html>
  2. <meta charset="UTF-8">
  3. <body>
  4. Hello world!
  5. </body>
  6. <script type="text/javascript">
  7. function test() {
  8. console.log('Ark WebComponent')
  9. return "This value is from index.html"
  10. }
  11. </script>
  12. </html>

[Recommended Practice]

Collapse
Word wrap
Dark theme
Copy code
  1. Web({ src: $rawfile('index.html'), controller: this.controller })
  2. .javaScriptAccess(true)
  3. .onPageEnd(event => {
  4. try {
  5. let whiteMethods = ['test()']
  6. let jsMethod: string = 'alert("xss")' // External controlled string
  7. if (whiteMethods.indexOf(jsMethod) === -1) {
  8. hilog.error(0x0000, 'ArkWebSecurity', 'input method not in whiteList')
  9. return;
  10. }
  11. this.controller.runJavaScript(jsMethod)
  12. .then((result) => {
  13. hilog.info(0x0000, 'ArkWebSecurity', 'result: ' + result);
  14. })
  15. .catch((error: BusinessError) => {
  16. hilog.error(0x0000, 'ArkWebSecurity', 'error: ' + error);
  17. })
  18. if (event) {
  19. hilog.info(0x0000, 'ArkWebSecurity', 'url: ', event.url);
  20. }
  21. } catch (error) {
  22. let e: BusinessError = error;
  23. hilog.error(0x0000, 'ArkWebSecurity', `ErrorCode: ${e.code}, Message: ${e.message}`);
  24. }
  25. })

Always Validating File URLs When Loading Local Files in onInterceptRequest to Prevent Local Data Theft

[Description]

The onInterceptRequest() method can intercept URL resource access, allowing customization of the response status and content. If the application needs to open a local file via getRequestUrl(), the URL must be checked for path traversal sequences (../) to prevent path traversal attacks.

[Risk]

Path traversal occurs when an attacker can use path traversal sequences to access files in directories they should not be able to read. This could lead to theft of sensitive files within the application or even severe consequences like remote code execution.

[Recommended Practice]

Collapse
Word wrap
Dark theme
Copy code
  1. import { webview } from '@kit.ArkWeb';
  2. import { fileIo } from '@kit.CoreFileKit';
  3. import { util } from '@kit.ArkTS';
  4. import { hilog } from '@kit.PerformanceAnalysisKit';
  5. @Entry
  6. @Component
  7. struct WebComponent {
  8. controller: webview.WebviewController = new webview.WebviewController();
  9. responseWeb: WebResourceResponse = new WebResourceResponse();
  10. heads: Header[] = [];
  11. build() {
  12. Column() {
  13. Web({ src: 'www.example.com', controller: this.controller })
  14. // Intercept URL resource access through onInterceptRequest and customize the response message.
  15. .onInterceptRequest((event) => {
  16. // The URL passed by event.request.getRequestUrl() should be restricted to a specific path,
  17. // and the presence of '../' should be checked.
  18. if (event.request.getRequestUrl().startsWith('file:///data/trusted_path') &&
  19. event.request.getRequestUrl().includes('../') !== true) {
  20. try {
  21. let file =
  22. fileIo.openSync(event.request.getRequestUrl(), fileIo.OpenMode.READ_ONLY | fileIo.OpenMode.CREATE);
  23. let arrayBuffer: ArrayBuffer = new ArrayBuffer(6);
  24. fileIo.readSync(file.fd, arrayBuffer, { offset: 0, length: arrayBuffer.byteLength });
  25. fileIo.closeSync(file);
  26. let decoder = util.TextDecoder.create('utf-8');
  27. let stringData = decoder.decodeToString(new Uint8Array(arrayBuffer));
  28. let head1: Header = {
  29. headerKey: 'Connection',
  30. headerValue: 'keep-alive'
  31. }
  32. let head2: Header = {
  33. headerKey: 'Cache-Control',
  34. headerValue: 'no-cache'
  35. }
  36. this.heads.push(head1);
  37. this.heads.push(head2);
  38. const promise: Promise<String> = new Promise((resolve: Function) => {
  39. this.responseWeb.setResponseHeader(this.heads);
  40. // After the file is read, it will be encapsulated and the response will be called back to the web page.
  41. this.responseWeb.setResponseData(stringData);
  42. this.responseWeb.setResponseEncoding('utf-8');
  43. this.responseWeb.setResponseMimeType('text/html');
  44. this.responseWeb.setResponseCode(200);
  45. this.responseWeb.setReasonMessage('OK');
  46. resolve('success');
  47. })
  48. promise.then(() => {
  49. hilog.info(0x0000, 'ArkWebSecurity', 'prepare response ready');
  50. this.responseWeb.setResponseIsReady(true);
  51. })
  52. this.responseWeb.setResponseIsReady(false);
  53. } catch (error) {
  54. hilog.error(0x0000, 'ArkWebSecurity', `ErrorCode: ${error.code}, Message: ${error.message}`);
  55. }
  56. }
  57. return this.responseWeb;
  58. })
  59. }
  60. }
  61. }

Avoiding Storing Sensitive Resources in Local File Directories Allowed for Cross-Origin Access

[Description]

By default, ArkWeb does not allow cross-origin access to local file resources unless setPathAllowingUniversalAccess() is used to specify the local directories allowed for cross-origin access. In such cases, minimize the scope of accessible directories and ensure that they do not contain sensitive resources such as user data, cookies, or tokens.

Note: HarmonyOS only allows setting cross-origin access for specific directories like the application files directory (Context.filesDir) and the application resource directory (Context.resourceDir). If the specified path list does not meet requirements, the setting will fail.

[Risk]

Configuring directories for cross-origin access may allow attackers to access local files, leading to sensitive resource leakage and application cloning attacks.

Avoiding Mixed HTTP and HTTPS Content Loading

[Description]

The mixedMode() setting controls whether loading mixed HTTP and HTTPS content is allowed. ArkWeb defaults to disallowing mixed content (mixedMode is set to None by default). Avoid setting it to All. If accessing HTTP is necessary during debugging, mixedMode must be reset to None after debugging.

[Risk]

Allowing ArkWeb to load mixed HTTP and HTTPS content introduces the risk of man-in-the-middle (MitM) attacks.

[Non-compliant Example]

Collapse
Word wrap
Dark theme
Copy code
  1. Web({ src: 'www.huawei.com', controller: this.controller })
  2. .mixedMode(MixedMode.All)

[Recommended Practice]

Collapse
Word wrap
Dark theme
Copy code
  1. Web({ src: 'www.huawei.com', controller: this.controller })
  2. .mixedMode(MixedMode.None)

Avoiding Continuing Page Load When the SSL Verification Fails

[Description]

When an SSL verification error occurs for a web resource being loaded (for example, certificate or protocol verification error for the target website), the ArkWeb component notifies the application via onSslErrorEvent(). By default, ArkWeb cancels loading the resource causing the error. If the service scenario requires proceeding, you must perform explicit validation or provide a clear explanation before calling confirm. You are advised to use onSslErrorEvent to capture all resource errors, rather than onSslErrorEventReceive().

[Risk]

Ignoring SSL errors and continuing to load resources can easily lead to risks such as MitM attacks.

Exception: Applications used to load URLs, such as browsers, can be excluded. However, users must be explicitly notified of security risks on the page to be loaded.

[Non-compliant Example]

Collapse
Word wrap
Dark theme
Copy code
  1. import { webview } from '@kit.ArkWeb';
  2. @Entry
  3. @Component
  4. struct WebComponent {
  5. controller: webview.WebviewController = new webview.WebviewController();
  6. build() {
  7. Column() {
  8. Web({ src: 'www.example.com', controller: this.controller })
  9. .onSslErrorEvent((event: SslErrorEvent) => {
  10. console.log('ssl check failed, error is: ' + event.error.toString());
  11. event.handler.handleConfirm();
  12. })
  13. }
  14. }
  15. }

[Recommended Practice]

Collapse
Word wrap
Dark theme
Copy code
  1. import { webview } from '@kit.ArkWeb';
  2. import { hilog } from '@kit.PerformanceAnalysisKit';
  3. @Entry
  4. @Component
  5. struct WebComponent {
  6. controller: webview.WebviewController = new webview.WebviewController();
  7. build() {
  8. Column() {
  9. Web({ src: 'www.example.com', controller: this.controller })
  10. .onSslErrorEvent((event: SslErrorEvent) => {
  11. hilog.info(0x0000, 'ArkWebSecurity', 'onSslErrorEvent url: ' + event.url);
  12. hilog.info(0x0000, 'ArkWebSecurity', 'onSslErrorEvent error: ' + event.error);
  13. hilog.info(0x0000, 'ArkWebSecurity', 'onSslErrorEvent originalUrl: ' + event.originalUrl);
  14. hilog.info(0x0000, 'ArkWebSecurity', 'onSslErrorEvent referrer: ' + event.referrer);
  15. hilog.info(0x0000, 'ArkWebSecurity', 'onSslErrorEvent isFatalError: ' + event.isFatalError);
  16. hilog.info(0x0000, 'ArkWebSecurity', 'onSslErrorEvent isMainFrame: ' + event.isMainFrame);
  17. this.getUIContext().showAlertDialog({
  18. title: 'onSslErrorEvent',
  19. message: 'text',
  20. primaryButton: {
  21. value: 'Confirm',
  22. action: () => {
  23. event.handler.handleConfirm();
  24. }
  25. },
  26. secondaryButton: {
  27. value: 'cancel',
  28. action: () => {
  29. event.handler.handleCancel();
  30. }
  31. },
  32. cancel: () => {
  33. event.handler.handleCancel();
  34. }
  35. })
  36. })
  37. }
  38. }
  39. }

Ensuring that ArkWeb Web Debugging is Disabled in Official Release Versions

[Description]

setWebDebuggingAccess() can be used to enable web debugging for ArkWeb. By default, debugging is disabled. This feature must remain disabled in official release versions.

[Risk]

Enabling debugging increases the risk of service APIs being maliciously exploited.

[Non-compliant Example]

Collapse
Word wrap
Dark theme
Copy code
  1. try {
  2. webview.WebviewController.setWebDebuggingAccess(true);
  3. } catch (error) {
  4. hilog.error(0x0000, 'ArkWebSecurity', `ErrorCode: ${error.code}`);
  5. }

Appropriate Permission Control

Follow the best practices below to ensure the ArkWeb runtime adheres to the principle of least privilege, granting only the necessary permissions to trusted web pages.

Configuring an Allowlist for Calling Page Identity Verification During JavaScriptProxy API Registration

[Description]

For JavaScriptProxy APIs, especially those accessing sensitive resources, an allowlist must be used to verify the legitimacy of the calling page before invocation, adhering to the principle of least privilege. You are advised to configure an invocation allowlist via the permission parameter when registering the JavaScriptProxy API. After configuration, when the web side calls this API, ArkWeb checks the URL of the calling page. Only requests from URLs matching the allowlist are permitted; others are intercepted.

NOTE

Avoid implementing custom allowlist checks using lifecycle functions like onPageBegin(). Uniform control must be exercised through the permission parameter.

[Risk]

Methods registered via the JavaScriptProxy API can be exploited by attackers, enabling threats such as loading malicious content, executing malicious code, or abusing sensitive resources.

For details, see Establishing an Interaction Channel Between the Application Side and the HTML5 Page.

Applying the Least Privilege Principle to JavaScriptProxy API Registration

[Description]

Only register JavaScriptProxy APIs that are strictly necessary for services with web pages. Avoid directly exposing debugging APIs or internal logic APIs to web pages. Additionally, implement access control (for example, an invocation allowlist) to verify the caller's legitimacy.

[Risk]

If exposed APIs involve returning sensitive information or performing sensitive operations, invocation by an attacker could lead to risks like account impersonation attacks or malicious code execution.

[Non-compliant Example]

Collapse
Word wrap
Dark theme
Copy code
  1. import { webview } from '@kit.ArkWeb';
  2. import { hilog } from '@kit.PerformanceAnalysisKit';
  3. class InsecureObj {
  4. executeSQL(cmd: string) {
  5. // Do something dangerous here
  6. hilog.info(0x0000, 'ArkWebSecurity', 'Execute: ' + cmd);
  7. }
  8. }
  9. @Entry
  10. @Component
  11. struct WebComponent {
  12. controller: webview.WebviewController = new webview.WebviewController();
  13. testObj = new InsecureObj();
  14. build() {
  15. Column() {
  16. Web({ src: 'www.example.com', controller: this.controller })
  17. .javaScriptAccess(true)
  18. .javaScriptProxy({
  19. object: this.testObj,
  20. name: 'objName',
  21. methodList: ['executeSQL'],
  22. controller: this.controller,
  23. })
  24. }
  25. }
  26. }

Avoiding Page Loading Within onOverrideUrlLoading

[Description]

When a URL is about to be loaded into the Web component, the hybrid application can receive a notification via onOverrideUrlLoading() to take control of the loading process. onOverrideUrlLoading() is an untrusted lifecycle method; executing any service logic within it is not recommended. Avoid calling loadUrl() to load pages within the onOverrideUrlLoading() implementation.

[Risk]

This operation may not only unexpectedly terminate the current loading flow but also bypass ArkWeb's security model, introducing additional security risks. (Note: onOverrideUrlLoading() is triggered by the page, while loadUrl() is called by the hybrid application. The ArkWeb kernel applies different security checks for these two flows, and thus they must not be mixed).

[Non-compliant Example]

Collapse
Word wrap
Dark theme
Copy code
  1. import { webview } from '@kit.ArkWeb';
  2. import { hilog } from '@kit.PerformanceAnalysisKit';
  3. @Entry
  4. @Component
  5. struct WebComponent {
  6. controller: webview.WebviewController = new webview.WebviewController();
  7. build() {
  8. Column() {
  9. Web({ src: $rawfile('index.html'), controller: this.controller })
  10. .onOverrideUrlLoading((webResourceRequest: WebResourceRequest) => {
  11. if (webResourceRequest && webResourceRequest.getRequestUrl() === 'http://www.example.com') {
  12. try {
  13. this.controller.loadUrl('www.example.com');
  14. return true;
  15. } catch (error) {
  16. hilog.error(0x0000, 'ArkWebSecurity', `ErrorCode: ${error.code}, Message: ${error.message}`);
  17. }
  18. }
  19. return false;
  20. })
  21. }
  22. }
  23. }

Avoiding Using getUrl/getOriginalUrl Functions to Obtain URLs for Invocation Allowlist Validation

[Description]

During a JavaScriptProxy call, getUrl() or getOriginalUrl() may retrieve an inaccurate page URL. Using such URLs for validation could lead to erroneous judgments, introducing security risks. You are advised to use the getLastJavascriptProxyCallingFrameUrl() function to obtain the accurate URL of the page that initiated the call.

Note: getLastJavascriptProxyCallingFrameUrl should only be used within JavaScriptProxy. Do not use this function to obtain URLs in other contexts, as it may return incorrect values.

[Risk]

Performing security validation based on an inaccurate page URL renders the validation ineffective, potentially enabling privilege escalation attacks.

[Non-compliant Example]

Collapse
Word wrap
Dark theme
Copy code
  1. let url: string = this.controller.getUrl();
  2. if (url === 'https://www.huawei.com') {
  3. hilog.info(0x0000, 'ArkWebSecurity', 'Pass the check');
  4. // do some native invoke
  5. } else {
  6. hilog.error(0x0000, 'ArkWebSecurity', 'Not allowed to execute: ' + cmd);
  7. }

[Recommended Practice]

Collapse
Word wrap
Dark theme
Copy code
  1. let url: string = this.controller.getLastJavascriptProxyCallingFrameUrl();
  2. if (url === 'https://www.huawei.com') {
  3. hilog.info(0x0000, 'ArkWebSecurity', 'Pass the check');
  4. // do some native invoke
  5. } else {
  6. hilog.error(0x0000, 'ArkWebSecurity', 'Not allowed to execute: ' + cmd);
  7. }

Avoiding Providing the Page Loading Functionality Within JavaScriptProxy

[Description]

Avoid calling loadUrl() within JavaScriptProxy to load pages. If the service requires page loading, it should be implemented via HTML navigation.

[Risk]

Improper operations may not only terminate the current page lifecycle unexpectedly but also bypass ArkWeb's security model, introducing additional security risks.

[Non-compliant Example]

Collapse
Word wrap
Dark theme
Copy code
  1. import { webview } from '@kit.ArkWeb';
  2. import { hilog } from '@kit.PerformanceAnalysisKit';
  3. class TestObj {
  4. controller: webview.WebviewController
  5. constructor(webview_controller: webview.WebviewController) {
  6. this.controller = webview_controller
  7. }
  8. goto(uri: string) {
  9. try {
  10. this.controller.loadUrl(uri);
  11. } catch (error) {
  12. hilog.error(0x0000, 'ArkWebSecurity', `ErrorCode: ${error.code}, Message: ${error.message}`);
  13. }
  14. }
  15. }
  16. @Entry
  17. @Component
  18. struct WebComponent {
  19. controller: webview.WebviewController = new webview.WebviewController();
  20. testObj = new TestObj(this.controller);
  21. build() {
  22. Column() {
  23. Web({ src: 'www.example.com', controller: this.controller })
  24. .javaScriptAccess(true)
  25. .javaScriptProxy({
  26. object: this.testObj,
  27. name: 'objName',
  28. methodList: ['goto'],
  29. controller: this.controller,
  30. })
  31. }
  32. }
  33. }

Avoiding Providing the Script Execution Functionality Within JavaScriptProxy

[Description]

Avoid providing code execution functionalities such as runJavaScript() and runJavaScriptExt() within JavaScriptProxy.

[Risk]

Since JavaScriptProxy is registered on the Web component, JavaScript scripts executed via it can affect all pages running within that component. This capability allows attackers to execute code on the parent page using a controlled subpage, resulting in cross-origin code execution.

[Non-compliant Example]

Collapse
Word wrap
Dark theme
Copy code
  1. import { webview } from '@kit.ArkWeb';
  2. import { BusinessError } from '@kit.BasicServicesKit';
  3. import { hilog } from '@kit.PerformanceAnalysisKit';
  4. class TestObj {
  5. controller: webview.WebviewController
  6. constructor(webview_controller: webview.WebviewController) {
  7. this.controller = webview_controller
  8. }
  9. eval(uri: string) {
  10. this.controller.runJavaScript(uri, (error) => {
  11. if (error) {
  12. hilog.error(0x0000, 'ArkWebSecurity',
  13. `run JavaScript error, ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`);
  14. return;
  15. }
  16. });
  17. return 'AceString';
  18. }
  19. }
  20. @Entry
  21. @Component
  22. struct WebComponent {
  23. controller: webview.WebviewController = new webview.WebviewController();
  24. testObj: TestObj = new TestObj(this.controller);
  25. build() {
  26. Column() {
  27. Web({ src: 'www.example.com', controller: this.controller })
  28. .javaScriptAccess(true)
  29. .javaScriptProxy({
  30. object: this.testObj,
  31. name: 'objName',
  32. methodList: ['eval'],
  33. controller: this.controller,
  34. })
  35. }
  36. }
  37. }

Explicitly Notifying the User for Authorization in onPermissionRequest

[Description]

Web pages can access device cameras/microphones via the standard getUserMedia() API. The ArkWeb component triggers the onPermissionRequest() function to manage authorization for the current web page. An explicit dialog (for example, using AlertDialog) should be created to obtain user authorization. Avoid directly granting page permissions. By default, the ArkWeb component denies permission requests.

[Risk]

Unauthorized access may lead to privacy leakage and compliance violations.

[Non-compliant Example]

Collapse
Word wrap
Dark theme
Copy code
  1. import { webview } from '@kit.ArkWeb';
  2. import { BusinessError } from '@kit.BasicServicesKit';
  3. import { abilityAccessCtrl, common } from '@kit.AbilityKit';
  4. import { hilog } from '@kit.PerformanceAnalysisKit';
  5. @Entry
  6. @Component
  7. struct WebComponent {
  8. controller: webview.WebviewController = new webview.WebviewController();
  9. aboutToAppear(): void {
  10. let atManager = abilityAccessCtrl.createAtManager();
  11. let context: Context = this.getUIContext().getHostContext() as common.UIAbilityContext;
  12. atManager.requestPermissionsFromUser(context, ['ohos.permission.CAMERA', 'ohos.permission.MICROPHONE'])
  13. .then((data) => {
  14. hilog.info(0x0000, 'ArkWebSecurity', 'data: ' + JSON.stringify(data));
  15. hilog.info(0x0000, 'ArkWebSecurity', 'data permission: ' + data.permissions);
  16. hilog.info(0x0000, 'ArkWebSecurity', 'data authResults: ' + data.authResults);
  17. }).catch((error: BusinessError) => {
  18. hilog.error(0x0000, 'ArkWebSecurity',
  19. `Failed to request permissions from user. Code is ${error.code}, message is ${error.message}`);
  20. })
  21. }
  22. build() {
  23. Column() {
  24. Web({ src: 'https://example.com/index.html', controller: this.controller })
  25. .onPermissionRequest((event) => {
  26. // Directly granting page permissions is a wrong approach.
  27. event.request.grant(event.request.getAccessibleResource());
  28. })
  29. }
  30. }
  31. }

The index.html file contains the following content:

Collapse
Word wrap
Dark theme
Copy code
  1. <html>
  2. <head>
  3. <meta charset="UTF-8">
  4. </head>
  5. <body>
  6. <video id="video" width="500px" height="500px" autoplay="autoplay"></video>
  7. <canvas id="canvas" width="500px" height="500px"></canvas>
  8. <br>
  9. <input type="button" title="HTML5 Camera" value="Open Camera" onclick="getMedia()">
  10. <script>
  11. function getMedia() {
  12. let constraints = {
  13. video: {width: 500, height: 500},
  14. audio: true
  15. };
  16. // Get video
  17. let video = document.getElementById("video");
  18. // Return Promise object
  19. let promise = navigator.mediaDevices.getUserMedia(constraints);
  20. // then() sync, invoke MediaStream as param
  21. promise.then(function (MediaStream) {
  22. video.srcObject = MediaStream;
  23. video.play();
  24. });
  25. }
  26. </script>
  27. </body>
  28. </html>

[Recommended Practice]

Collapse
Word wrap
Dark theme
Copy code
  1. import { webview } from '@kit.ArkWeb';
  2. import { BusinessError } from '@kit.BasicServicesKit';
  3. import { abilityAccessCtrl, common } from '@kit.AbilityKit';
  4. import { hilog } from '@kit.PerformanceAnalysisKit';
  5. @Entry
  6. @Component
  7. struct WebComponent {
  8. controller: webview.WebviewController = new webview.WebviewController();
  9. aboutToAppear(): void {
  10. let atManager = abilityAccessCtrl.createAtManager();
  11. let context: Context = this.getUIContext().getHostContext() as common.UIAbilityContext;
  12. atManager.requestPermissionsFromUser(context, ['ohos.permission.CAMERA', 'ohos.permission.MICROPHONE'])
  13. .then((data) => {
  14. hilog.info(0x0000, 'ArkWebSecurity', 'data: ' + JSON.stringify(data));
  15. hilog.info(0x0000, 'ArkWebSecurity', 'data permission: ' + data.permissions);
  16. hilog.info(0x0000, 'ArkWebSecurity', 'data authResults: ' + data.authResults);
  17. }).catch((error: BusinessError) => {
  18. hilog.error(0x0000, 'ArkWebSecurity',
  19. `Failed to request permissions from user. Code is ${error.code}, message is ${error.message}`);
  20. })
  21. }
  22. build() {
  23. Column() {
  24. Web({ src: 'https://example.com/index.html', controller: this.controller })
  25. .onPermissionRequest((event) => {
  26. if (event) {
  27. this.getUIContext().showAlertDialog({
  28. title: 'title',
  29. message: 'text',
  30. primaryButton: {
  31. value: 'deny',
  32. action: () => {
  33. event.request.deny();
  34. }
  35. },
  36. secondaryButton: {
  37. value: 'onConfirm',
  38. action: () => {
  39. // Explicit pop-ups that ask user for authorization, e.g., using AlertDialog, is the correct approach.
  40. event.request.grant(event.request.getAccessibleResource());
  41. }
  42. },
  43. cancel: () => {
  44. event.request.deny();
  45. }
  46. })
  47. }
  48. })
  49. }
  50. }
  51. }

Explicitly Notifying the User for Authorization in onGeolocationShow

[Description]

When a web page requests access to the device's geographical location, an explicit dialog must be initiated within the onGeolocationShow() callback to clearly inform the user and obtain authorization. Since the geolocationAccess property defaults to true, ArkWeb permits web pages to request location access by default. Therefore, you must only return location information after obtaining user confirmation and consent; otherwise, geolocationAccess should be set to false to prohibit web pages from obtaining location data.

[Risk]

Unauthorized access may lead to privacy leakage and compliance violations.

[Non-compliant Example]

Collapse
Word wrap
Dark theme
Copy code
  1. import { webview } from '@kit.ArkWeb';
  2. @Entry
  3. @Component
  4. struct WebComponent {
  5. controller: webview.WebviewController = new webview.WebviewController();
  6. build() {
  7. Column() {
  8. Web({ src: $rawfile('index.html'), controller: this.controller })
  9. .geolocationAccess(true)
  10. .onGeolocationShow((event) => {
  11. event.geolocation.invoke(event.origin, true, true);
  12. })
  13. }
  14. }
  15. }

[Recommended Practice]

Collapse
Word wrap
Dark theme
Copy code
  1. import { webview } from '@kit.ArkWeb';
  2. @Entry
  3. @Component
  4. struct WebComponent {
  5. controller: webview.WebviewController = new webview.WebviewController();
  6. build() {
  7. Column() {
  8. Web({ src: $rawfile('index.html'), controller: this.controller })
  9. .geolocationAccess(true)
  10. .onGeolocationShow((event) => {
  11. if (event) {
  12. this.getUIContext().showAlertDialog({
  13. title: 'title',
  14. message: 'text',
  15. confirm: {
  16. value: 'onConfirm',
  17. action: () => {
  18. event.geolocation.invoke(event.origin, true, true);
  19. }
  20. },
  21. cancel: () => {
  22. event.geolocation.invoke(event.origin, false, true);
  23. }
  24. })
  25. }
  26. })
  27. }
  28. }
  29. }

Avoiding Concatenating and Executing the JavaScript Content Passed from the Web Side Without Validation

[Description]

The application side (ArkTS) may receive data passed from web pages (HTML+JS) through open APIs or lifecycle interception, and then asynchronously execute JavaScript via runJavaScript() or runJavaScriptExt(), returning the execution result. You must apply security filtering to the passed data before executing the asynchronous callback.

[Risk]

Content passed from the web page to the application side (ArkTS) is untrusted. If controlled by an attacker, it could lead to universal cross-site scripting (UXSS) attacks.

[Non-compliant Example]

Collapse
Word wrap
Dark theme
Copy code
  1. this.controller.runJavaScript(
  2. // Trusting data_from_H5 passed through web pages will result in the execution of arbitrary JS code,
  3. // e.g., enclose the alert and execute other JS code through data_from_H5.
  4. 'javascript:alert(' + this.dataFromH5 + ')',
  5. (error, result) => {
  6. if (error) {
  7. hilog.error(0x0000, 'ArkWebSecurity',
  8. `run JavaScript error, ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`);
  9. return;
  10. }
  11. if (result) {
  12. this.webResult = result;
  13. hilog.info(0x0000, 'ArkWebSecurity', `The test() return value is: ${result}`);
  14. }
  15. });

[Recommended Practice]

Collapse
Word wrap
Dark theme
Copy code
  1. import { webview } from '@kit.ArkWeb';
  2. import { BusinessError } from '@kit.BasicServicesKit';
  3. import { hilog } from '@kit.PerformanceAnalysisKit';
  4. @Entry
  5. @Component
  6. struct WebComponent {
  7. controller: webview.WebviewController = new webview.WebviewController();
  8. dataFromH5: string = '' // Messages passed from web pages
  9. @State webResult: string = '';
  10. build() {
  11. Column() {
  12. Text(this.webResult).fontSize(20)
  13. Web({ src: $rawfile('index.html'), controller: this.controller })
  14. .javaScriptAccess(true)
  15. .onPageEnd(event => {
  16. try {
  17. // Use regular expressions to match all single and double quotes.
  18. const regex = /['"\(\)]/g;
  19. // Use the replace method to replace all matched characters with an empty string.
  20. let sanitizedStr = this.dataFromH5.replace(regex, '');
  21. this.controller.runJavaScript(
  22. // Perform security filtering on the data_from_H5 passed through the web page before calling back.
  23. 'javascript:alert(' + sanitizedStr + ')',
  24. (error, result) => {
  25. if (error) {
  26. hilog.error(0x0000, 'ArkWebSecurity',
  27. `run JavaScript error, ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`);
  28. return;
  29. }
  30. if (result) {
  31. this.webResult = result;
  32. hilog.info(0x0000, 'ArkWebSecurity', `The test() return value is: ${result}`);
  33. }
  34. });
  35. if (event) {
  36. hilog.info(0x0000, 'ArkWebSecurity', 'url: ', event.url);
  37. }
  38. } catch (error) {
  39. hilog.error(0x0000, 'ArkWebSecurity',
  40. `ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`);
  41. }
  42. })
  43. }
  44. }
  45. }

The index.html file contains the following content:

Collapse
Word wrap
Dark theme
Copy code
  1. <html>
  2. <meta charset="UTF-8">
  3. <body>
  4. Hello world!
  5. </body>
  6. <script type="text/javascript">
  7. function test() {
  8. console.log('Ark WebComponent')
  9. return "This value is from index.html"
  10. }
  11. </script>
  12. </html>

Ensuring Secure Transmission of Sensitive Data

Follow the best practices below to ensure secure data transfer between the application and web pages, enhancing application security and providing privacy protection for users.

Validating URLs Before Appending Authentication Cookies to Web Pages

[Description]

To synchronize user state for service needs, WebCookieManager can be used to pass cookies to pages of a specified URL.

Before using configCookieSync() to append an authentication cookie, always verify that the target URL matches service expectations. You are advised to perform an additional legitimacy check on the target URL after configuring a loading allowlist with setUrlTrustList().

[Risk]

Directly appending authentication cookies to unvalidated URLs is highly dangerous and may lead to authentication cookie theft.

[Non-compliant Example]

Collapse
Word wrap
Dark theme
Copy code
  1. import { webview } from '@kit.ArkWeb';
  2. import { BusinessError } from '@kit.BasicServicesKit';
  3. import { hilog } from '@kit.PerformanceAnalysisKit';
  4. @Entry
  5. @Component
  6. struct WebComponent {
  7. controller: webview.WebviewController = new webview.WebviewController();
  8. unknownUrl: string = ''; // unknownUrl from external source
  9. build() {
  10. Column() {
  11. Button('configCookieSync')
  12. .onClick(() => {
  13. try {
  14. // unknownUrl is not verified.
  15. webview.WebCookieManager.configCookieSync(this.unknownUrl, 'a=b');
  16. } catch (error) {
  17. hilog.error(0x0000, 'ArkWebSecurity',
  18. `ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`);
  19. }
  20. })
  21. Web({ src: this.unknownUrl, controller: this.controller })
  22. }
  23. }
  24. }

[Recommended Practice]

Collapse
Word wrap
Dark theme
Copy code
  1. import { webview } from '@kit.ArkWeb';
  2. import { BusinessError } from '@kit.BasicServicesKit';
  3. import { hilog } from '@kit.PerformanceAnalysisKit';
  4. @Entry
  5. @Component
  6. struct WebComponent {
  7. controller: webview.WebviewController = new webview.WebviewController();
  8. unknownUrl: string = ''; // unknownUrl from external source
  9. urlTrustList: string =
  10. '{\'UrlPermissionList\':[{\'scheme\':\'http\', \'host\':\'trust.example.com\', \'port\':80, \'path\':\'test\'}]}'
  11. build() {
  12. Column() {
  13. Button('configCookieSync')
  14. .onClick(() => {
  15. try {
  16. // Set up an allowlist to allow access only to trusted web pages.
  17. this.controller.setUrlTrustList(this.urlTrustList);
  18. } catch (error) {
  19. hilog.error(0x0000, 'ArkWebSecurity',
  20. `ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`);
  21. }
  22. try {
  23. // Perform another validity check on unknown_url to implement more granular control.
  24. if (this.unknownUrl === 'https://www.example.com') {
  25. webview.WebCookieManager.configCookieSync(this.unknownUrl, 'a=b');
  26. }
  27. } catch (error) {
  28. hilog.error(0x0000, 'ArkWebSecurity',
  29. `ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`);
  30. }
  31. })
  32. Web({ src: this.unknownUrl, controller: this.controller })
  33. }
  34. }
  35. }

Avoiding Directly Concatenating User Sensitive Information into URLs for Loading

[Description]

Avoid directly concatenating user-sensitive information, such as OAuth tokens, session IDs, or phone numbers, into URLs and accessing/loading them (for example, via loadUrl()).

[Risk]

When the URL is externally controllable, this practice leads to sensitive information leakage.

[Non-compliant Example]

Collapse
Word wrap
Dark theme
Copy code
  1. import { webview } from '@kit.ArkWeb';
  2. @Entry
  3. @Component
  4. struct WebComponent {
  5. @State message: string = 'Hello World';
  6. webviewController: webview.WebviewController = new webview.WebviewController();
  7. accessToken: string = 'xxxx'; // After the user completes the login operation, the token returned by the server.
  8. untrustedURL: string = '' // Any URL to be loaded
  9. build() {
  10. Column() {
  11. Web({ src: this.untrustedURL + '?token=' + this.accessToken, controller: this.webviewController })
  12. }
  13. }
  14. }

Specifying the Authorized Receiver URI When Sending Sensitive Data to a Web Page via postMessage

[Description]

When establishing a connection with a web page using WebMessagePort, ensure that the peer web page is trusted.

[Risk]

Lack of access control may accidentally expose application-side (ArkTS) code to the web page's JavaScript, leading to sensitive resource leakage.

[Non-compliant Example]

Collapse
Word wrap
Dark theme
Copy code
  1. this.ports = this.controller.createWebMessagePorts();
  2. this.controller.postMessage('__init_port__', [this.ports[0]], '*');
  3. this.ports[1].postMessageEvent('Post message event to html');

[Recommended Practice]

Collapse
Word wrap
Dark theme
Copy code
  1. this.ports = this.controller.createWebMessagePorts();
  2. this.controller.postMessage('__init_port__', [this.ports[0]], this.url_in_whitelist);