release: harden Android network security

This commit is contained in:
key
2026-07-29 01:13:18 +08:00
parent 9ea5d79a62
commit ba68fc8326
16 changed files with 198 additions and 29 deletions
+59 -1
View File
@@ -15,6 +15,16 @@ let releaseLegalUrls;
const fail = (message) => failures.push(message);
const compiledAppRoot = resolve(projectRoot, 'dist/build/app');
const sourceAndroidManifestPath = resolve(projectRoot, 'AndroidManifest.xml');
const compiledAndroidManifestPath = resolve(compiledAppRoot, 'AndroidManifest.xml');
const sourceNetworkSecurityPath = resolve(
projectRoot,
'nativeResources/android/res/xml/network_security_config.xml'
);
const compiledNetworkSecurityPath = resolve(
compiledAppRoot,
'nativeResources/android/res/xml/network_security_config.xml'
);
const releaseTextExtensions = new Set(['.css', '.html', '.js', '.json', '.txt', '.xml']);
const releaseSensitiveFilePattern = /(^|[\\/])(?:\.env(?:\.|$)|id_rsa$)|\.(?:jks|keystore|p12|pfx|pem)$/i;
const releaseSensitiveContentPatterns = [
@@ -126,7 +136,7 @@ if (app?.ssl?.untrustedca !== 'refuse') {
fail('app-plus.ssl.untrustedca must remain refuse');
}
if (!/^\d+\.\d+\.\d+$/.test(String(manifest.versionName || ''))) {
fail('versionName must use semantic numeric form such as 0.1.6');
fail('versionName must use semantic numeric form such as 0.1.8');
}
if (!/^[1-9]\d*$/.test(String(manifest.versionCode || ''))) {
fail('versionCode must be a positive integer');
@@ -137,6 +147,9 @@ if (android?.packagename !== 'com.yincheng.wygj') {
if (android?.targetSdkVersion !== 35) {
fail('Android targetSdkVersion must be 35 for the August 1 test package');
}
if (android?.usesCleartextTraffic !== false) {
fail('Android manifest configuration must explicitly disable cleartext traffic');
}
const expectedAndroidAbis = ['arm64-v8a', 'armeabi-v7a'];
const actualAndroidAbis = Array.isArray(android?.abiFilters)
? [...android.abiFilters].sort()
@@ -162,6 +175,46 @@ for (const permission of ['android.permission.RECORD_AUDIO', 'android.permission
}
}
const verifyAndroidNetworkSecurity = (manifestPath, networkSecurityPath, label) => {
if (!existsSync(manifestPath)) {
fail(`${label} is missing AndroidManifest.xml`);
return;
}
if (!existsSync(networkSecurityPath)) {
fail(`${label} is missing network_security_config.xml`);
return;
}
const androidManifest = readFileSync(manifestPath, 'utf8');
const networkSecurity = readFileSync(networkSecurityPath, 'utf8');
if (!/android:usesCleartextTraffic\s*=\s*["']false["']/.test(androidManifest)) {
fail(`${label} AndroidManifest.xml must disable cleartext traffic`);
}
if (!/android:networkSecurityConfig\s*=\s*["']@xml\/network_security_config["']/.test(androidManifest)) {
fail(`${label} AndroidManifest.xml must reference network_security_config`);
}
if (!/cleartextTrafficPermitted\s*=\s*["']false["']/.test(networkSecurity)) {
fail(`${label} network security config must reject cleartext traffic`);
}
if (/cleartextTrafficPermitted\s*=\s*["']true["']/.test(networkSecurity)) {
fail(`${label} network security config must not contain a cleartext exception`);
}
if (!/<certificates\s+src\s*=\s*["']system["']\s*\/>/.test(networkSecurity)) {
fail(`${label} network security config must trust system certificates only`);
}
if (/<certificates\s+src\s*=\s*["'](?!system["'])[^"']+["']/.test(networkSecurity)) {
fail(`${label} network security config must not trust user or bundled certificates`);
}
if (/<debug-overrides\b/.test(networkSecurity)) {
fail(`${label} network security config must not contain debug trust overrides`);
}
};
verifyAndroidNetworkSecurity(
sourceAndroidManifestPath,
sourceNetworkSecurityPath,
'source App'
);
const androidIcons = [
['hdpi', 72], ['xhdpi', 96], ['xxhdpi', 144], ['xxxhdpi', 192]
];
@@ -251,6 +304,11 @@ if (requirePrivacy) {
fail(`compiled App androidPrivacy.json is invalid: ${error instanceof Error ? error.message : String(error)}`);
}
}
verifyAndroidNetworkSecurity(
compiledAndroidManifestPath,
compiledNetworkSecurityPath,
'compiled App'
);
scanCompiledAppForSensitiveValues();
}