Files
brainwave/scripts/verify-local-data-boundary.mjs
T

62 lines
1.8 KiB
JavaScript
Raw Normal View History

import { readFile } from "node:fs/promises";
import path from "node:path";
import { repositoryRoot } from "./repository-files.mjs";
const manifestPath = path.join(repositoryRoot, "app", "src", "main", "AndroidManifest.xml");
const backupRulesPath = path.join(
repositoryRoot,
"app",
"src",
"main",
"res",
"xml",
"backup_rules.xml",
);
const extractionRulesPath = path.join(
repositoryRoot,
"app",
"src",
"main",
"res",
"xml",
"data_extraction_rules.xml",
);
const [manifest, backupRules, extractionRules] = await Promise.all([
readFile(manifestPath, "utf8"),
readFile(backupRulesPath, "utf8"),
readFile(extractionRulesPath, "utf8"),
]);
const failures = [];
if (!manifest.includes('android:allowBackup="false"')) {
failures.push("Android backup must remain disabled");
}
if (!manifest.includes('android:usesCleartextTraffic="false"')) {
failures.push("cleartext traffic must remain disabled");
}
if (/android\.permission\.INTERNET/u.test(manifest)) {
failures.push("the offline application must not request INTERNET permission");
}
for (const domain of ["root", "file", "database", "sharedpref", "external"]) {
const exclusion = `<exclude domain="${domain}" path="." />`;
if (!backupRules.includes(exclusion)) {
failures.push(`legacy backup rules do not exclude ${domain}`);
}
const extractionOccurrences = extractionRules.split(exclusion).length - 1;
if (extractionOccurrences !== 2) {
failures.push(
`data extraction rules must exclude ${domain} from cloud backup and device transfer`,
);
}
}
if (failures.length > 0) {
console.error("Local-data boundary verification failed:");
for (const failure of failures) console.error(`- ${failure}`);
process.exitCode = 1;
} else {
console.log("Local-data boundary verification passed.");
}