收紧桌面壳命令白名单
桌面壳检查脚本校验 Tauri build manifest、invoke handler 与 capability 只暴露 host_bridge_request 桌面壳检查脚本拒绝残留的非白名单自动生成权限文件 宿主壳方案和共享决策记录桌面壳命令暴露边界
This commit is contained in:
@@ -14,6 +14,10 @@ const buildScript = fs.readFileSync(buildScriptPath, 'utf8');
|
||||
const cargoManifestPath = new URL('../src-tauri/Cargo.toml', import.meta.url);
|
||||
const cargoManifest = fs.readFileSync(cargoManifestPath, 'utf8');
|
||||
const iconDirPath = new URL('../src-tauri/icons/', import.meta.url);
|
||||
const generatedPermissionDir = new URL(
|
||||
'../src-tauri/permissions/autogenerated/',
|
||||
import.meta.url,
|
||||
);
|
||||
const sharedContractPath = new URL(
|
||||
'../../../packages/shared/src/contracts/hostBridge.ts',
|
||||
import.meta.url,
|
||||
@@ -228,6 +232,66 @@ function assertSameList(actual, expected, label) {
|
||||
}
|
||||
}
|
||||
|
||||
function extractTauriBuildCommands(source) {
|
||||
const match = source.match(/\.commands\(\s*&\[\s*([^\]]*?)\s*\]\s*\)/);
|
||||
if (!match) {
|
||||
throw new Error('unable to read Tauri build manifest commands');
|
||||
}
|
||||
|
||||
return [...match[1].matchAll(/"([^"]+)"/g)].map((entry) => entry[1]);
|
||||
}
|
||||
|
||||
function extractTauriInvokeCommands(source) {
|
||||
const match = source.match(/tauri::generate_handler!\[\s*([^\]]*?)\s*\]/);
|
||||
if (!match) {
|
||||
throw new Error('unable to read Tauri invoke handler commands');
|
||||
}
|
||||
|
||||
return match[1]
|
||||
.split(',')
|
||||
.map((command) => command.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function assertGeneratedPermissions(commandNames) {
|
||||
if (!fs.existsSync(generatedPermissionDir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const expectedPermissionFiles = new Set(
|
||||
commandNames.map((command) => `${command}.toml`),
|
||||
);
|
||||
for (const entry of fs.readdirSync(generatedPermissionDir, {
|
||||
withFileTypes: true,
|
||||
})) {
|
||||
if (entry.isDirectory()) {
|
||||
throw new Error(
|
||||
`desktop shell generated permissions must not include nested directory ${entry.name}`,
|
||||
);
|
||||
}
|
||||
if (!expectedPermissionFiles.has(entry.name)) {
|
||||
throw new Error(
|
||||
`desktop shell generated permission exposes an unexpected command: ${entry.name}`,
|
||||
);
|
||||
}
|
||||
|
||||
const permissionFile = new URL(entry.name, generatedPermissionDir);
|
||||
const permissionSource = fs.readFileSync(permissionFile, 'utf8');
|
||||
const commandName = entry.name.replace(/\.toml$/, '');
|
||||
for (const expectedSnippet of [
|
||||
`identifier = "allow-${commandName.replaceAll('_', '-')}"`,
|
||||
`commands.allow = ["${commandName}"]`,
|
||||
`commands.deny = ["${commandName}"]`,
|
||||
]) {
|
||||
if (!permissionSource.includes(expectedSnippet)) {
|
||||
throw new Error(
|
||||
`desktop shell generated permission ${entry.name} drifted from ${commandName}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const sharedCapabilities = extractStringArrayExport(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_CAPABILITIES',
|
||||
@@ -344,11 +408,11 @@ assertSameList(
|
||||
'desktop shell dev hostCapabilities',
|
||||
);
|
||||
|
||||
const requiredPermissions = [
|
||||
const allowedPermissions = [
|
||||
'core:default',
|
||||
'allow-host-bridge-request',
|
||||
];
|
||||
const requiredBuildCommands = ['host_bridge_request'];
|
||||
const allowedTauriCommands = ['host_bridge_request'];
|
||||
const requiredMainSnippets = [
|
||||
'tauri_plugin_single_instance::init',
|
||||
'resolve_desktop_single_instance_action',
|
||||
@@ -414,17 +478,22 @@ const requiredMainSnippets = [
|
||||
'app.notification().builder()',
|
||||
];
|
||||
|
||||
for (const permission of requiredPermissions) {
|
||||
if (!capability.permissions?.includes(permission)) {
|
||||
throw new Error(`desktop shell capability missing ${permission}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const command of requiredBuildCommands) {
|
||||
if (!buildScript.includes(command)) {
|
||||
throw new Error(`desktop shell build manifest missing ${command}`);
|
||||
}
|
||||
}
|
||||
assertSameList(
|
||||
capability.permissions ?? [],
|
||||
allowedPermissions,
|
||||
'desktop shell capability permissions',
|
||||
);
|
||||
assertSameList(
|
||||
extractTauriBuildCommands(buildScript),
|
||||
allowedTauriCommands,
|
||||
'desktop shell build manifest commands',
|
||||
);
|
||||
assertSameList(
|
||||
extractTauriInvokeCommands(main),
|
||||
allowedTauriCommands,
|
||||
'desktop shell invoke handler commands',
|
||||
);
|
||||
assertGeneratedPermissions(allowedTauriCommands);
|
||||
|
||||
if (buildScript.includes('resolve_desktop_shell_runtime')) {
|
||||
throw new Error('desktop shell build manifest exposes an unused runtime command');
|
||||
|
||||
Reference in New Issue
Block a user