This document focuses on the governance of security risks at the native component and service coding levels of applications, providing guidance for general secure coding practices for HarmonyOS apps. HarmonyOS apps are developed based on core system capabilities such as Ability components, cross-component Want communication, common event broadcasting, WebView containers, local data storage, project configuration, and app signing. While these capabilities provide flexibility in service implementation, they also expose apps to multiple security threats, including unauthorized component exposure, implicit launch data leakage, abuse of common events without permission, malicious injection into web containers, SQL injection and path traversal, sensitive data leakage from the clipboard, debugging configuration leakage, and non-compliant signatures. These threats can lead to privacy breaches, malicious calls, man-in-the-middle attacks, reverse cracking of apps, and failure to pass app release compliance checks.
This document systematically sorts out secure coding specifications based on typical vulnerabilities and compliance requirements of HarmonyOS apps, covering seven dimensions: common component security, public event security, WebView security, data transmission security, data storage security, configuration security, and app signature security. By providing risk descriptions, incorrect examples, correct code examples, and service exception descriptions, this document clarifies prohibited behaviors and compliance implementation solutions in various development scenarios, while also considering the differentiated configuration requirements for development and debugging as well as official release. This document aims to help you establish a comprehensive awareness of secure coding practices, avoid common high-risk vulnerabilities, standardize application permission control, data processing, component interaction, and packaging and signing behaviors, enhance the overall security protection capabilities of apps, and meet the requirements for personal information protection, privacy compliance, and security review for app release on app stores.
Components that are used only inside an app must be set to invisible to prevent them from being called by other apps.
For app components without external interaction services, explicitly set the exported property to false to prevent missing and incorrect configurations. This prevents internal APIs from being exploited by malicious apps, which may cause information leakage, function exceptions, or app denial of service (DoS).
When exported of an app component is set to false, the error message "error: failed to start ability. error: ability visible false deny request" is displayed when you attempt to start debugging or run an app/service on DevEco Studio, or start an app/service using the CLI. The system security permission does not allow starting apps/services whose exported property is set to false. You are advised to set exported to true during debugging and set it to false before releasing your app/service.
Incorrect example:
// module.json5
"abilities": [
{
"name": "PrivacyAbility",
"srcEntry": "./ets/privacyability/PrivacyAbility.ts",
"description": "$string:PrivacyAbility_desc",
"exported": true
}
] The PrivacyAbility is a component without external interaction services. However, exported is set to true. As a result, any app can access the component.
Correct example:
// module.json5
"abilities": [
{
"name": "PrivacyAbility",
"srcEntry": "./ets/privacyability/PrivacyAbility.ts",
"description": "$string:PrivacyAbility_desc",
"exported": false
}
]
Some app components are used not only internally but also performs external interactions. The following situations indicate that a component performs external interactions:
If an app component can perform external interactions, data will cross trust boundaries. If the data is exploited by malicious apps, information leakage, function exceptions, or app DoS may occur. Therefore, you should use the permissions property to set access permissions for those app components.
Sensitive features and abilities provided externally have exploitation risks. You are advised to set permissions for related components for protection.
Exception: For app components that must be provided externally and have no exploitation risks, there is no need to set permissions.
Incorrect example:
// module.json5
"abilities": [
{
"name": "ContactDataAbility",
"srcEntry": "./ets/DataAbility/DataAbility.ts",
"description": "$string: DataAbility_desc",
"exported": true
}
] It can be seen that any external app can access ContactDataAbility, which may be used by a malicious app to obtain contact data.
Correct example:
// module.json5
"abilities": [
{
"name": "ContactDataAbility",
"srcEntry": "./ets/DataAbility/DataAbility.ts",
"description": "$string: DataAbility_desc",
"exported": true,
"permissions": ["ohos.permission.READ_CONTACTS"]
}
] Implicit Want is used to filter components by matching action conditions. All app components that meet the conditions may be started. If personal data is carried, malicious apps may hijack the carried personal data. Therefore, if an app needs to carry the personal data, the target app component (bundle name or ability name) must be explicitly specified or the personal data must be anonymized. For details about how to obtain the context in the example, see Obtaining the Context of UIAbility.
Incorrect example:
import { Want } from '@kit.AbilityKit';
// ...
let wantInfo:Want = {
deviceId: '',
action: 'ability.want.test',
parameters: {
'password': 'xxxxxxxx'
}
};
try{
let data = this.context.startAbility(wantInfo);
hilog.info(0x0000, 'privacy', 'startAbility success ' + JSON.stringify(data));
} catch (err) {
hilog.error(0x0000, 'privacy', 'startAbility with error message: ' + err.message + ', error code: ' + err.code);
} action "ability.want.test" in the sample code can be used to implicitly start a component. If a malicious app also declares the same action, a list is displayed for users to select when the app is implicitly called. If the user selects the malicious app, the app may obtain the transferred sensitive information.
Correct example:
import { Want } from '@kit.AbilityKit';
// ...
let wantInfo:Want = {
deviceId: '',
action: 'ability.want.test',
bundleName:'com.example.myapplication10',
abilityName:'MainAbility1',
parameters: {
'password': 'xxxxxxxx'
}
}
try{
let data = this.context.startAbility(wantInfo);
hilog.info(0x0000, 'privacy', 'startAbility success ' + JSON.stringify(data));
} catch (err) {
hilog.error(0x0000, 'privacy', 'startAbility with error message: ' + err.message + ', error code: ' + err.code);
} The bundleName and abilityName of the app component to be started are explicitly specified. Only the specified target component is started.
The password input page contains the account and password text boxes and the input method pop-up box. When a user enters a password, screen capture or recording is not allowed. If malware is authorized to steal user accounts and passwords, it may log in to other devices, causing user privacy leakage.
Implementation Guide:
Call setWindowPrivacyMode() on the page to be protected to disable screen capture or recording.
Note: This API requires the ohos.permission.PRIVACY_WINDOW permission.
Correct example:
Set privacy attributes on the password input and transfer payment pages to prevent screenshots and screen recording.
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
onPageShow(): void {
window.getLastWindow(this.getUIContext().getHostContext()).then((windowStage: window.Window) => {
windowStage.setWindowPrivacyMode(true);
}).catch((error: BusinessError) => {
hilog.error(0x0000, 'PrivacyMode', `getLastWindow error code ${error.code}`)
});
}
onPageHide(): void {
window.getLastWindow(this.getUIContext().getHostContext()).then((windowStage: window.Window) => {
windowStage.setWindowPrivacyMode(false);
}).catch((error: BusinessError) => {
hilog.error(0x0000, 'PrivacyMode', `getLastWindow error code ${error.code}`)
});
}
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
Button('click to start Succ Page')
.onClick(async () => {
this.getUIContext().getRouter().pushUrl({ url: 'pages/loginSuccPage' });
})
}
.width('100%')
}
.height('100%')
}
}
Common events can be used for inter-process communication between different apps. If the permission for sending common events is not properly set and personal data is carried, any app can read the personal data, causing user data leakage.
For common events that carry personal data, the sending permission must be set or personal data must be encrypted.
Incorrect example:
import { commonEventManager } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
function publishEventWithData() {
let options: commonEventManager.CommonEventPublishData = {
code: 1,
data: 'ContactData', // Sending sensitive data
}
commonEventManager.publish('MyCommonEvent', options, (err) => {
if (err.code) {
hilog.error(0x0000, 'privacy', `publish event error: ${err.code} ${err.message} ${err.name} ${err.stack}`);
} else {
hilog.info(0x0000, 'privacy', 'publish event with data Succeeded');
}
})
} A common event containing contact data is sent, but the required permission of the recipient is not set.
import { commonEventManager } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
function publishEventWithData() {
let options: commonEventManager.CommonEventPublishData = {
code: 1,
data: 'ContactData', // Sending sensitive data
subscriberPermissions: ['ohos.permission.READ_CONTACTS'], // Set the permission
}
commonEventManager.publish('MyCommonEvent', options, (err) => {
if (err.code) {
hilog.error(0x0000, 'privacy', `publish event error: ${err.code} ${err.message} ${err.name} ${err.stack}`);
} else {
hilog.info(0x0000, 'privacy', 'publish event with data Succeeded');
}
})
} The subscriberPermissions field is used to set the permission to receive common events. In this way, the publisher requires that the subscriber must have the ohos.permission.READ_CONTACTS permission to receive the common event that carries the contact data.
Apps can subscribe to custom common events, execute certain features, and set the permission to receive the common event through publisherPermission. If no permission is set, any app can send a custom common event with the same name, which may cause the receiving app to refuse services or leak sensitive functions.
For common events involving sensitive data or operations, the subscriber must set sufficient permissions.
Incorrect example:
import { commonEventManager } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
let subscriber: commonEventManager.CommonEventSubscriber;
//Subscriber information
let subscribeInfo: commonEventManager.CommonEventSubscribeInfo = {
events: ['event']
};
// Create a subscriber
try {
commonEventManager.createSubscriber(subscribeInfo, (err, commonEventSubscriber) => {
if (!err) {
hilog.info(0x0000, 'privacy', 'createSubscriber Succeed');
subscriber = commonEventSubscriber;
//Subscribe to public events
try {
commonEventManager.subscribe(subscriber, (err, data) => {
if (err) {
hilog.error(0x0000, 'privacy', `subscribe failed, code is ${err.code}, message is ${err.message}`);
} else {
//Event processing after receiving public events
// doSomeDangerousThing(data)
}
});
} catch (err) {
hilog.error(0x0000, 'privacy', `subscribe failed, code is ${err.code}, message is ${err.message}`);
}
} else {
hilog.error(0x0000, 'privacy', `createSubscriber failed, code is ${err.code}, message is ${err.message}`);
}
});
} catch (err) {
hilog.error(0x0000, 'privacy', `createSubscriber failed, code is ${err.code}, message is ${err.message}`);
} import { commonEventManager } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
let subscriber: commonEventManager.CommonEventSubscriber;
//Subscriber information
let subscribeInfo: commonEventManager.CommonEventSubscribeInfo = {
events: ['event'],
publisherPermission: 'ohos.permission.publisherPermission1', // Set the subscription permission
};
// Create a subscriber
try {
commonEventManager.createSubscriber(subscribeInfo, (err, commonEventSubscriber) => {
if (!err) {
hilog.info(0x0000, 'privacy', 'createSubscriber');
subscriber = commonEventSubscriber;
//Subscribe to public events
try {
commonEventManager.subscribe(subscriber, (err, data) => {
if (err) {
hilog.error(0x0000, 'privacy', `subscribe failed, code is ${err.code}, message is ${err.message}`);
} else {
//Event processing after receiving public events
// doSomeDangerousThing(data)
}
});
} catch (err) {
hilog.error(0x0000, 'privacy', `subscribe failed, code is ${err.code}, message is ${err.message}`);
}
} else {
hilog.error(0x0000, 'privacy', `createSubscriber failed, code is ${err.code}, message is ${err.message}`);
}
});
} catch (err) {
hilog.error(0x0000, 'privacy', `createSubscriber failed, code is ${err.code}, message is ${err.message}`);
} Common sensitive features and common events provided externally may be exploited. You are advised to set permission protection for them.
Exception: For app common events that must be provided externally and have no exploitation risks, there is no need to set permissions.
Both the Web component and WebController can load URLs or pages. If an attacker controls the loaded URLs or pages and loads the malicious JavaScript code, the malicious code can call sensitive JavaScript APIs opened by ArkTS to obtain users' personal data or attack apps.
Before loading a URL, use the onLoadIntercept() method of the Web component to intercept the URL and check whether the field value is in the preset trustlist.
Correct example:
import { uri } from '@kit.ArkTS';
import { webview } from '@kit.ArkWeb';
import { hilog } from '@kit.PerformanceAnalysisKit';
// Check function
function checkUrl(str: string): boolean {
let tmpUri = new uri.URI(str);
let res =
tmpUri.normalize(); // Note that you need to normalize tmpUri first. Otherwise, security verification will be bypassed
hilog.info(0x0000, 'privacy', 'res.scheme:' + res.scheme); // Protocol
hilog.info(0x0000, 'privacy', 'res.host:' + res.host); // domain name
hilog.info(0x0000, 'privacy', 'res.port:' + res.port); // Ports
hilog.info(0x0000, 'privacy', 'res.path:' + res.path); // Path
hilog.info(0x0000, 'privacy', 'res.ssp:' + res.ssp);
// After obtaining the values of scheme, host, port, and path, perform security verification based on the actual service
// Omit the part here. Perform the verification based on service requirements
if ('Verification successful.') {
return true;
}
return false;
}
@Entry
@Component
struct WebComponent {
controller: webview.WebviewController = new webview.WebviewController();
build() {
Column() {
Button('loadUrl')
.onClick(() => {
this.controller.loadUrl('www.huawei.com');
})
.margin({ top: 50 })
Web({ src: 'www.huawei.com', controller: this.controller })
.onLoadIntercept((event) => {
hilog.info(0x0000, 'privacy', 'onLoadIntercept:' + event.data.toString());
let tempUrl = event.data.toString();
return checkUrl(tempUrl); // Return true to block the load, otherwise allow the load
})
}
}
} Exception: An app, such as a browser, provides the URL loading capability. It should be noted that if the app also provides sensitive JS APIs, prevent the APIs from being maliciously called.
WebController provides the runJavaScript() and runJavaScriptExt() functions to asynchronously execute JavaScript scripts and return the script execution result using a callback. The following uses runJavaScript() as an example:
import { webview } from '@kit.ArkWeb';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
@Entry
@Component
struct WebComponent {
controller: webview.WebviewController = new webview.WebviewController();
build() {
Column() {
Web({ src: $rawfile('index.html'), controller: this.controller })
.javaScriptAccess(true)
.onPageEnd(e => {
this.controller.runJavaScript('test()')
.then((result) => {
hilog.info(0x0000, 'privacy', 'result: ' + result);
})
.catch((error: BusinessError) => {
hilog.error(0x0000, 'privacy', `ErrorCode: ${error.code}, Message: ${error.message}`);
})
if (e) {
hilog.info(0x0000, 'privacy', 'url: ', e.url);
}
})
}
}
} The content of the index.html file to be loaded is as follows:
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<body>
Hello world!
</body>
<script type="text/javascript">
function test() {
console.log('Ark WebComponent')
return "This value is from index.html"
}
</script>
</html> If the loaded script content is untrusted, malicious JavaScript code may be loaded, causing cross-site scripting (XSS) attacks. If sensitive JavaScript APIs are registered with the Web component, malicious code may be used to call these APIs, further affecting app security.
Therefore, if the JavaScript script to be loaded is externally controllable, trustlist verification is required before the script is loaded.
Correct example:
import { webview } from '@kit.ArkWeb';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
@Entry
@Component
struct WebComponent {
controller: webview.WebviewController = new webview.WebviewController();
build() {
Column() {
Web({ src: $rawfile('index.html'), controller: this.controller })
.javaScriptAccess(true)
.onPageEnd(e => {
let whiteMethods = ['test()', 'test1()'];
let jsMethod: string = 'alert(`xss`)'; // Externally controllable fields
// Check the trustlist
if (whiteMethods.indexOf(jsMethod) === -1) {
hilog.error(0x0000, 'privacy', 'input method not in whiteList');
return;
}
this.controller.runJavaScript(jsMethod)
.then((result) => {
hilog.info(0x0000, 'privacy', 'result: ' + result);
})
.catch((error: BusinessError) => {
hilog.error(0x0000, 'privacy', `ErrorCode: ${error.code}, Message: ${error.message}`);
})
if (e) {
hilog.info(0x0000, 'privacy', 'url: ', e.url);
}
})
}
}
} Incorrect example:
import { webview } from '@kit.ArkWeb';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
@Entry
@Component
struct WebComponent {
controller: webview.WebviewController = new webview.WebviewController();
build() {
Column() {
Web({ src: $rawfile('index.html'), controller: this.controller })
.javaScriptAccess(true)
.onPageEnd(e => {
let jsMethod: string = 'alert(`xss`)' // Externally controllable fields
this.controller.runJavaScript(jsMethod)
.then((result) => {
hilog.info(0x0000, 'privacy', 'result: ' + result);
})
.catch((error: BusinessError) => {
hilog.error(0x0000, 'privacy', `ErrorCode: ${error.code}, Message: ${error.message}`);
})
if (e) {
hilog.info(0x0000, 'privacy', 'url: ', e.url);
}
})
}
}
} mixedMode sets whether to enable the loading of HTTP and HTTPS mixed content. By default, this feature is disabled. Three modes are supported. All indicates that mixed content can be loaded, which may cause man-in-the-middle attacks. By default, All is not allowed.
Incorrect example:
import { webview } from '@kit.ArkWeb';
@Entry
@Component
struct WebComponent {
controller: webview.WebviewController = new webview.WebviewController();
build() {
Column() {
Web({ src:'www.huawei.com', controller: this.controller })
.mixedMode(MixedMode.All)
}
}
} Delete this configuration item or select MixedMode.None.
Exception: Apps that provide specific services, such as browsers, can be excluded.
The onSslErrorEventReceive() callback function is used to notify the user that an SSL error occurs during resource loading.
When the Web component makes a network request using the SSL protocol, the underlying Web component cancels the request by default if an error occurs during the verification of the certificate or protocol returned by the server. This is to prevent man-in-the-middle attacks.
import { webview } from '@kit.ArkWeb';
import { hilog } from '@kit.PerformanceAnalysisKit';
@Entry
@Component
struct WebComponent {
controller: webview.WebviewController = new webview.WebviewController();
build() {
Column() {
Web({ src: 'www.example.com', controller: this.controller })
.onSslErrorEventReceive((event) => {
hilog.info(0x0000, 'privacy', 'ssl check failed,error is : ' + event.error.toString());
event.handler.handleCancel();
})
}
}
} Incorrect example:
import { webview } from '@kit.ArkWeb';
@Entry
@Component
struct WebComponent {
controller: webview.WebviewController = new webview.WebviewController();
build() {
Column() {
Web({ src: 'www.example.com', controller: this.controller })
.onSslErrorEventReceive((event) => {
event.handler.handleConfirm();
})
}
}
} If the handleConfirm() function is directly called when an SSL verification error occurs, the page ignores the SSL error and continues loading, which may cause man-in-the-middle attacks.
Exception: Apps 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.
The geolocationAccess switch is used to configure whether to enable the geographical location permission. By default, the permission is enabled. The onGeolocationShow() callback function is used to notify a user that the request for obtaining geographical location information is received. When an H5 page requests to obtain geographical location information, the web component uses the preceding two APIs to configure the information.
If there is no app scenario, you need to explicitly disable geolocationAccess(). In some scenarios, display a dialog box in the onGeolocationShow() callback to notify the user of the location information, which can be returned only after the user confirms and agrees. Otherwise, privacy leakage may occur.
import { webview } from '@kit.ArkWeb';
@Entry
@Component
struct WebComponent {
controller: webview.WebviewController = new webview.WebviewController();
build() {
Column() {
Web({ src: 'www.example.com', controller: this.controller })
.geolocationAccess(true)
.onGeolocationShow((event) => {
if (event === undefined) {
return;
}
AlertDialog.show({
title: 'title',
message: 'text',
confirm: {
value: 'onConfirm',
action: () => {
event.geolocation.invoke(event.origin, true, true);
}
},
cancel: () => {
event.geolocation.invoke(event.origin, false, true);
}
});
})
}
}
} Incorrect example:
import { webview } from '@kit.ArkWeb';
@Entry
@Component
struct WebComponent {
controller: webview.WebviewController = new webview.WebviewController();
build() {
Column() {
Web({ src: 'www.example.com', controller: this.controller })
.geolocationAccess(true)
.onGeolocationShow((event) => {
if (event === undefined) {
return;
}
event.geolocation.invoke(event.origin, true, true);
})
}
}
} In scenarios where the location permission is granted, the location information can be directly returned only after the user's explicit consent is obtained.
In HarmonyOS, the Web component can be used to load HTML5 pages, and provides JS APIs for HTML5 through the JavaScriptProxy() method or the registerJavaScriptProxy() method of the WebController.
There will be security risks if the provided JS APIs return sensitive information or involve sensitive operations, especially when the JS APIs return the global authentication credential ServiceToken (ST for short) or AccessToken (AT for short). Once obtaining the ST or AT, an attacker can maliciously call all APIs of the app on the cloud, increasing the impact of attacks. Therefore, the Web component must be prohibited from providing JavaScriptProxy for obtaining the global authentication credential ST or AT.
Incorrect example:
import { webview } from '@kit.ArkWeb';
@Entry
@Component
struct Index {
controller: webview.WebviewController = new webview.WebviewController();
build() {
Column() {
Row() {
Button('registerJavaScriptProxy').onClick(() => {
this.controller.registerJavaScriptProxy(
{
getServiceToken: () => {
return 'DLFJSLDFJALGJLDFJSDDFISLDF323LSDJFLS212DLSFJOEV';
}
},
'objName',
['getServiceToken'],
);
this.controller.refresh();
})
}
Web({ src: $rawfile('H5CallETS.html'), controller: this.controller })
.javaScriptAccess(true)
.javaScriptProxy({
object: {
getServiceToken: () => {
return 'DLFJSLDFJALGJLDFJSDDFISLDF323LSDJFLS212DLSFJOEV';
}
},
name: 'objName',
methodList: ['getServiceToken'],
controller: this.controller
})
}
}
} H5 attack page:
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<body>
Hello js call ets interface!
<br>
<script type="text/javascript">
function callJSInt() {
let ST = objName.getServiceToken();
console.log('wzz:ST = '+ ST);
alert(ST);
}
</script>
<button onclick="callJSInt()">get ST</button>
</body>
</html> An ST API is provided for HTML5 to obtain global authentication credentials. If HTML5 has vulnerabilities, attackers may obtain the ST and impersonate users to access personal data on the cloud service and perform malicious operations.
To reduce security risks caused by the loss of the global authentication credential ST or AT, you are advised to use either of the following methods:
Method 1: Before loading the URL page, check whether the URL of the current page is in the trustlist. Only URLs in the trustlist can be called. The protocol, domain name, and path must be verified at the same time. The smaller the trustlist scope, the more secure the system.
You can use the following API to format related URLs and compare them with the preset data.
1. Use the URI library to format the URL, accurately obtain each URL field value, and check whether the value is in the trustlist preset by the service.
// Index.ets
import { uri } from '@kit.ArkTS';
import { webview } from '@kit.ArkWeb';
import { hilog } from '@kit.PerformanceAnalysisKit';
// Check function
function checkUrl(surl: string): boolean {
let tmpUri: uri.URI = new uri.URI(surl);
let res = tmpUri.normalize(); // Note that you need to normalize tmpUri first. Otherwise, security verification will be bypassed
hilog.info(0x0000, 'privacy', 'res.scheme:' + res.scheme); // scheme
hilog.info(0x0000, 'privacy', 'res.host:' + res.host); // host
hilog.info(0x0000, 'privacy', 'res.port:' + res.port); // port
hilog.info(0x0000, 'privacy', 'res.path:' + res.path); // path
hilog.info(0x0000, 'privacy', 'res.ssp:' + res.ssp);
// After obtaining the values of the scheme, host, port, and path parameters, perform security verification based on the service requirements
// The part is omitted here. The verification is performed based on the service requirements
if ('Verification successful.') {
return true;
}
return false;
}
@Entry
@Component
struct Index {
controller: webview.WebviewController = new webview.WebviewController();
build() {
Column() {
Row() {
Button('registerJavaScriptProxy').onClick(() => {
this.controller.registerJavaScriptProxy(
{
getServiceToken: () => {
return 'DLFJSLDFJALGJLDFJSDDFISLDF323LSDJFLS212DLSFJOEV';
}
},
'objName',
['getServiceToken'],
);
this.controller.refresh();
})
}
Web({ src: $rawfile('H5CallETS.html'), controller: this.controller })
.javaScriptProxy({
object: {
getServiceToken: () => {
return 'DLFJSLDFJALGJLDFJSDDFISLDF323LSDJFLS212DLSFJOEV';
}
},
name: 'objName',
methodList: ['getServiceToken'],
controller: this.controller
})
.onLoadIntercept((event) => {
hilog.info(0x0000, 'privacy', 'onLoadIntercept:' + event.data.toString());
let tempUrl = event.data.toString();
return checkUrl(tempUrl); // Returns true to block the load, otherwise allow the load
})
}
}
} // Index.ets
import { url } from '@kit.ArkTS';
import { webview } from '@kit.ArkWeb';
import { hilog } from '@kit.PerformanceAnalysisKit';
// Check Function
function checkUrl(surl: string): boolean {
let tmpUrl = url.URL.parseURL(surl);
hilog.info(0x0000, 'privacy', 'res.scheme:' + tmpUrl.protocol); // scheme
hilog.info(0x0000, 'privacy', 'res.host:' + tmpUrl.host); // host
hilog.info(0x0000, 'privacy', 'res.port:' + tmpUrl.port); // port
hilog.info(0x0000, 'privacy', 'res.path:' + tmpUrl.pathname); // path
hilog.info(0x0000, 'privacy', 'res.ssp:' + tmpUrl.hostname); // ssp
// After obtaining the values of the scheme, host, port, and path parameters, perform security verification based on the service requirements
// The part is omitted here. The verification is performed based on the service requirements
if ('Verification successful.') {
return true;
}
return false;
}
@Entry
@Component
struct Index {
controller: webview.WebviewController = new webview.WebviewController();
build() {
Column() {
Row() {
Button('registerJavaScriptProxy').onClick(() => {
this.controller.registerJavaScriptProxy(
{
getServiceToken: () => {
return 'DLFJSLDFJALGJLDFJSDDFISLDF323LSDJFLS212DLSFJOEV';
}
},
'objName',
['getServiceToken']
);
this.controller.refresh();
})
}
Web({ src: $rawfile('H5CallETS.html'), controller: this.controller })
.javaScriptAccess(true)
.javaScriptProxy({
object: {
getServiceToken: () => {
return 'DLFJSLDFJALGJLDFJSDDFISLDF323LSDJFLS212DLSFJOEV';
}
},
name: 'objName',
methodList: ['getServiceToken'],
controller: this.controller
})
.onLoadIntercept((event) => {
hilog.info(0x0000, 'privacy', 'wzz:onLoadIntercept:' + event.data.toString());
let tempUrl = event.data.toString();
return checkUrl(tempUrl); // Returns true to block the load, otherwise allow the load
})
}
}
} Method 2: Request a new H5 token for the H5 page and strictly limit the cloud service APIs that can be called using the H5 token.
If an attacker sends an empty Want or malicious data to a specified app ability, and the app receives the Want but does not check its validity, tampered service logic, data leakage, and property loss may occur. Therefore, the validity of Want content transferred from the external system must be checked.
Check the validity of Want:
1. Check for null and undefined before using the data attributes of Want to ensure that the object attribute to be accessed exists.
import { hilog } from '@kit.PerformanceAnalysisKit';
import { UIAbility, Want, AbilityConstant } from '@kit.AbilityKit';
// Check function
function checkWant(want: Want): boolean {
if (want === null || want === undefined) {
return false;
}
if (want.parameters === null || want.parameters === undefined) {
return false;
}
return true;
}
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
if (checkWant(want)) {
hilog.info(0x0000, 'privacy', 'correct want');
} else {
hilog.info(0x0000, 'privacy', 'invalid want');
}
}
} 2. Use try-catch to capture exceptions before using Want data to prevent app breakdown and locate faults.
import { hilog } from '@kit.PerformanceAnalysisKit';
import { UIAbility, Want, AbilityConstant } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
// Check function
function checkWant(want: Want): boolean {
try {
if (want.parameters && want.parameters['test']) {
let testParam = want.parameters['test'] as Record<string, Object>;
if (testParam && typeof testParam === 'object') {
let uri = testParam['uri'] as string;
if (uri) {
hilog.info(0x0000, 'privacy', 'get uri string: ' + uri);
} else {
hilog.error(0x0000, 'privacy', 'uri parameter is missing');
return false;
}
} else {
hilog.error(0x0000, 'privacy', 'test parameter is not an object');
return false;
}
} else {
hilog.error(0x0000, 'privacy', 'parameters or test key is missing');
return false;
}
} catch (err) {
let error = err as BusinessError;
hilog.error(0x0000, 'privacy', `checkWant failed: code is ${err.code}, message is ${err.message}`);
return false;
}
return true;
}
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
if (checkWant(want)) {
hilog.info(0x0000, 'privacy', 'correct want');
} else {
hilog.info(0x0000, 'privacy', 'invalid want');
}
}
} An app can obtain external untrusted Want in many ways.
The methods of obtaining Want in different scenarios are as follows:
// Application life cycle:
// @ohos.application.AbilityStage.d.ts
onAcceptWant(want: Want): string;
// Ability life cycle:
// @ohos.application.Ability.d.ts
onCreate(want: Want, param: AbilityConstant.LaunchParam): void;
onContinue(wantParam: {}): AbilityConstant.OnContinueResult;
onNewWant(want: Want, launchParams: AbilityConstant.LaunchParam): void;
// ServiceExtensionAbility life cycle:
// @ohos.application.ServiceExtensionAbility.d.ts
onCreate(want: Want): void;
onRequest(want: Want, startId: number): void;
onConnect(want: Want): rpc.RemoteObject;
onDisconnect(want: Want): void;
onReconnect(want: Want): void;
SQL injection occurs when the validity of user input data is not checked or filtered. Attackers can input specific content to change the meaning of SQL statements predefined by apps, causing information leakage or data tampering.
Any external input, including user input, is insecure and needs to be filtered.
The relational database provided by HarmonyOS is based on SQLite and can directly run SQL statements entered by users. If untrusted data is used to concatenate SQL statements, there will be SQL injection risks. Therefore, you should use parameterized query or filter untrusted content.
In HarmonyOS, querySql and executeSql are used to concatenate SQL statements for execution.
For details about how to obtain the context in the example, see Obtaining the Context of UIAbility.
import { BusinessError } from '@kit.BasicServicesKit';
import { relationalStore } from '@kit.ArkData'
import { hilog } from '@kit.PerformanceAnalysisKit';
// ...
// The sql parameters are from external input
function exesql(sql: string) {
const STORE_CONFIG: relationalStore.StoreConfig = {
name: 'RdbTest.db',
securityLevel: relationalStore.SecurityLevel.S1
};
let store: relationalStore.RdbStore | undefined = undefined;
relationalStore.getRdbStore(context, STORE_CONFIG, (err: BusinessError, rdbStore: relationalStore.RdbStore) => {
store = rdbStore;
if (err) {
hilog.error(0x0000, 'privacy', `Get RdbStore failed, code is ${err.code},message is ${err.message}`);
return;
}
hilog.info(0x0000, 'privacy', 'Get RdbStore successfully.');
})
// The external input is used to directly concatenate the SQL statement, and the verification is not performed
let SQL_DELETE_TABLE = 'DELETE FROM test WHERE name = ' + sql;
if (store != undefined) {
(store as relationalStore.RdbStore).executeSql(SQL_DELETE_TABLE, (err) => {
if (err) {
hilog.error(0x0000, 'privacy', `ExecuteSql failed, code is ${err.code},message is ${err.message}`);
return;
}
hilog.info(0x0000, 'privacy', 'Delete table done.');
})
}
} If user input is directly used as a query parameter, malicious users can type a string similar to 1' OR 'a' = 'a to concatenate SQL statements into select *from book where name='1' OR 'a' = 'a', causing SQL injection attacks. Therefore, do not directly use external input as a part of the query string, but verify external input before concatenating SQL statements.
The clipboard is a module provided by an operating system for temporarily storing data and providing sharing, and is also referred to as a data transfer station. It works in the background and is stored in the memory.
It is not safe to put personal data in the clipboard because any program can access the content in the clipboard.
You can use the setPasteData() method in the SystemPasteboard class to save content to the clipboard.
Exception: This rule does not apply to scenarios where users proactively copy data through the clipboard.
HarmonyOS provides sandbox isolation for file storage. Only apps have the permission to read files stored in the internal storage, and the files can only be stored in the internal directories of the app. To read external shared storage, you must add the read permission.
If external input is used to concatenate file paths when files are read, written, downloaded, or deleted, attackers can inject malicious characters such as ../ to launch cross-directory attacks, causing security issues such as data leakage and app DoS. Therefore, you need to verify external input before constructing a path to avoid malicious characters.
// The fileName is from external
function readFile(fileName: string) {
let filePath = '/data/storage/';
if (fileName.indexOf('..') === -1) { // Anti-splicing verification
let fileAllPath: string = filePath + fileName;
// Service processing
} else {
hilog.info(0x0000, 'privacy', 'be attacked');
return;
}
}
The debug attribute is used to facilitate app debugging. However, for an official version, if the debug attribute is set to the adjustable mode, attackers can perform in-depth analysis and debugging on the app, which is unfavorable to app protection. Therefore, the debug attribute must be set to the non-debugging mode for the official version.
You need to set the debug attribute in the app.json5 file. The default value of debug is false.
Incorrect example:
// app.json5
{
"app": {
"bundleName": "com.application.music",
"vendor": "application",
"versionCode": 1,
"versionName": "1.0",
"minCompatibleVersionCode": 1,
"minAPIVersion": 7,
"targetAPIVersion": 8,
"apiReleaseType": "Release",
"debug": true, // Set the debug field.
"icon": "$media:app_icon",
"label": "$string:app_name",
"description": "$string:description_application",
"distributedNotificationEnabled": true,
"entityType": "game",
"car": {
"apiCompatibleVersion": 8
}
}
} Correct example:
// app.json5
{
"app": {
"bundleName": "com.application.music",
"vendor": "application",
"versionCode": 1,
"versionName": "1.0",
"minCompatibleVersionCode": 1,
"minAPIVersion": 7,
"targetAPIVersion": 8,
"apiReleaseType": "Release",
"debug": false,
"icon": "$media:app_icon",
"label": "$string:app_name",
"description": "$string:description_application",
"distributedNotificationEnabled": true,
"entityType": "game",
"car": {
"apiCompatibleVersion": 8
}
}
}
The HarmonyOS app software packages (HAP, HAR, and HSP) can be decompiled. Configuring code obfuscation can increase the difficulty of code analysis for attackers.
Correct example: enabling attribute obfuscation
# Define project specific obfuscation rules here. # You can include the obfuscation configuration files in the current module's build-profile.json5. # # For more details, see # https://gitcode.com/openharmony/arkcompiler_ets_frontend/blob/master/arkguard/README.md # Obfuscation options: # -disable-obfuscation: disable all obfuscations # Enable object attribute obfuscation. -enable-property-obfuscation: obfuscate the property names # -enable-toplevel-obfuscation: obfuscate the names in the global scope # -compact: remove unnecessary blank spaces and all line feeds # -remove-log: remove all console.* statements # -print-namecache: print the name cache that contains the mapping from the old names to new names # -apply-namecache: reuse the given cache file # Keep options: # -keep-property-name: specifies property names that you want to keep # -keep-global-name: specifies names that you want to keep in the global scope
In the obfuscation-rules.txt file of the app source code project, configure and enable obfuscation rules based on the app service requirements.
Apps usually depend on external third-party libraries and system SDKs to complete specific tasks. Updating these dependencies in a timely manner enhances the security of your app.
Before releasing your app, ensure that all libraries, SDKs, and other dependencies are of the latest version.
When packaging a HarmonyOS app, you can select the debug or release mode. The debug mode facilitates app debugging. However, if the app is set to the debug mode in the official release version, attackers may analyze and debug the app, which is not conducive to app security protection. Therefore, the official release version of your app must use the release mode and the debug signature must be disabled.
Note: By default, the HAP compiled in DevEco Studio is in debug mode and cannot be directly used for commercial release.
App signing is key to app integrity. If resources need to be tailored or bytes need to be adjusted for an app due to version update, the app must be signed again after the modification. Otherwise, the signature integrity will be damaged.
Therefore, do not release an app after its signature is damaged or directly pre-install the app in the version.
Before app signing, you must apply for a signature certificate, with the developer and company information provided. The certificate is the identity and authentication credential of the app. During signature, the certificate is embedded into the app package and becomes a part of the app package data structure. You can obtain app certificate information through public APIs or by reading the app package data structure.
If personal information such as name and employee ID is entered in the certificate information, information leakage may occur, which may cause doubts. Therefore, enter company or team name instead of personal information in the certificate.
Preset apps must contain authentic and valid signature information. For non-open-source HarmonyOS apps, do not use open-source HarmonyOS public certificates or signature information irrelevant to the product, such as demos. The information about the app developer, company, and department must be authentic and valid. Invalid information such as website addresses and garbled characters is not allowed. The signature certificate must correctly identify the developer's identity, including the enterprise name, organization, province, city, and country. Do not include information irrelevant to the subject.
Incorrect example:
10-11 17:51:53 WARN - Missing parameter: outproof 10-11 17:51:53 INFO - Find Hap Signing Block success, version: 3, block count: 2 10-11 17:51:54 INFO - +++++++++++++++++++++++++++certificate #0 +++++++++++++++++++++++++++++++ 10-11 17:51:54 INFO - Subject: C=CN, O=Organization, OU=Unit, CN=ide_demo_app // The CN field cannot identify the development organization. 10-11 17:51:54 INFO - Issuer: CN=OpenHarmony Application CA, OU=OpenHarmony Team, O=OpenHarmony, C=CN
Correct example:
10-11 17:51:54 INFO - Issuer: CN=OpenHarmony Application CA, OU=OpenHarmony Team, O=OpenHarmony, C=CN 10-11 17:52:21 WARN - Missing parameter: outproof 10-11 17:52:21 INFO - Find Hap Signing Block success, version: 3, block count: 2 10-11 17:52:21 INFO - +++++++++++++++++++++++++++certificate #0 +++++++++++++++++++++++++++++++ 10-11 17:52:21 INFO - Subject: CN = App name, OU = Developer organization/Company/Department, O = Developer organization/Company/Department, C = CN // The CN field identifies the development organization.