mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-25 07:16:51 +00:00
fix: image_scan_helper 自动检测 task_for_pid 权限不足时通过 osascript 提权运行
macOS SIP 下 ad-hoc 签名的 debugger entitlement 不被信任,导致 image_scan_helper 调用 task_for_pid 返回 kr=5。现在先尝试直接运行, 检测到权限错误后自动切换到 osascript with administrator privileges 方式运行 helper,后续调用跳过直接运行直接走提权路径。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,7 @@ export class KeyServiceMac {
|
|||||||
private machVmRegion: any = null
|
private machVmRegion: any = null
|
||||||
private machVmReadOverwrite: any = null
|
private machVmReadOverwrite: any = null
|
||||||
private machPortDeallocate: any = null
|
private machPortDeallocate: any = null
|
||||||
|
private _needsElevation = false
|
||||||
|
|
||||||
private getHelperPath(): string {
|
private getHelperPath(): string {
|
||||||
const isPackaged = app.isPackaged
|
const isPackaged = app.isPackaged
|
||||||
@@ -634,30 +635,27 @@ export class KeyServiceMac {
|
|||||||
ciphertext: Buffer,
|
ciphertext: Buffer,
|
||||||
onProgress?: (message: string) => void
|
onProgress?: (message: string) => void
|
||||||
): Promise<string | null> {
|
): Promise<string | null> {
|
||||||
// 优先通过 image_scan_helper 子进程调用(有 debugger entitlement)
|
// 优先通过 image_scan_helper 子进程调用
|
||||||
try {
|
try {
|
||||||
const helperPath = this.getImageScanHelperPath()
|
const helperPath = this.getImageScanHelperPath()
|
||||||
const ciphertextHex = ciphertext.toString('hex')
|
const ciphertextHex = ciphertext.toString('hex')
|
||||||
const result = await new Promise<string | null>((resolve, reject) => {
|
|
||||||
const child = spawn(helperPath, [String(pid), ciphertextHex], { stdio: ['ignore', 'pipe', 'pipe'] })
|
// 1) 直接运行 helper(有正式签名的 debugger entitlement 时可用)
|
||||||
let stdout = ''
|
if (!this._needsElevation) {
|
||||||
child.stdout.on('data', (chunk: Buffer) => { stdout += chunk.toString() })
|
const direct = await this._spawnScanHelper(helperPath, pid, ciphertextHex, false)
|
||||||
child.stderr.on('data', (chunk: Buffer) => { console.log('[image_scan_helper]', chunk.toString().trim()) })
|
if (direct.key) return direct.key
|
||||||
child.on('error', reject)
|
if (direct.permissionError) {
|
||||||
child.on('close', () => {
|
console.warn('[KeyServiceMac] task_for_pid 权限不足,切换到 osascript 提权模式')
|
||||||
try {
|
this._needsElevation = true
|
||||||
const lines = stdout.split(/\r?\n/).map(x => x.trim()).filter(Boolean)
|
onProgress?.('需要管理员权限,请在弹出的对话框中输入密码...')
|
||||||
const last = lines[lines.length - 1]
|
}
|
||||||
if (!last) { resolve(null); return }
|
}
|
||||||
const payload = JSON.parse(last)
|
|
||||||
resolve(payload?.success && payload?.aesKey ? payload.aesKey : null)
|
// 2) 通过 osascript 以管理员权限运行 helper(SIP 下 ad-hoc 签名无法获取 task_for_pid)
|
||||||
} catch {
|
if (this._needsElevation) {
|
||||||
resolve(null)
|
const elevated = await this._spawnScanHelper(helperPath, pid, ciphertextHex, true)
|
||||||
}
|
if (elevated.key) return elevated.key
|
||||||
})
|
}
|
||||||
setTimeout(() => { try { child.kill('SIGTERM') } catch {} }, 30_000)
|
|
||||||
})
|
|
||||||
if (result) return result
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.warn('[KeyServiceMac] image_scan_helper unavailable, fallback to Mach API:', e?.message)
|
console.warn('[KeyServiceMac] image_scan_helper unavailable, fallback to Mach API:', e?.message)
|
||||||
}
|
}
|
||||||
@@ -757,6 +755,45 @@ export class KeyServiceMac {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _spawnScanHelper(
|
||||||
|
helperPath: string, pid: number, ciphertextHex: string, elevated: boolean
|
||||||
|
): Promise<{ key: string | null; permissionError: boolean }> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let child: ReturnType<typeof spawn>
|
||||||
|
if (elevated) {
|
||||||
|
const shellCmd = `'${helperPath}' ${pid} ${ciphertextHex}`
|
||||||
|
child = spawn('osascript', ['-e', `do shell script ${JSON.stringify(shellCmd)} with administrator privileges`],
|
||||||
|
{ stdio: ['ignore', 'pipe', 'pipe'] })
|
||||||
|
} else {
|
||||||
|
child = spawn(helperPath, [String(pid), ciphertextHex], { stdio: ['ignore', 'pipe', 'pipe'] })
|
||||||
|
}
|
||||||
|
const tag = elevated ? '[image_scan_helper:elevated]' : '[image_scan_helper]'
|
||||||
|
let stdout = '', stderr = ''
|
||||||
|
child.stdout.on('data', (chunk: Buffer) => { stdout += chunk.toString() })
|
||||||
|
child.stderr.on('data', (chunk: Buffer) => {
|
||||||
|
stderr += chunk.toString()
|
||||||
|
console.log(tag, chunk.toString().trim())
|
||||||
|
})
|
||||||
|
child.on('error', reject)
|
||||||
|
child.on('close', () => {
|
||||||
|
const permissionError = !elevated && stderr.includes('task_for_pid failed')
|
||||||
|
try {
|
||||||
|
const lines = stdout.split(/\r?\n/).map(x => x.trim()).filter(Boolean)
|
||||||
|
const last = lines[lines.length - 1]
|
||||||
|
if (!last) { resolve({ key: null, permissionError }); return }
|
||||||
|
const payload = JSON.parse(last)
|
||||||
|
resolve({
|
||||||
|
key: payload?.success && payload?.aesKey ? payload.aesKey : null,
|
||||||
|
permissionError
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
resolve({ key: null, permissionError })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setTimeout(() => { try { child.kill('SIGTERM') } catch {} }, elevated ? 60_000 : 30_000)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private async findWeChatPid(): Promise<number | null> {
|
private async findWeChatPid(): Promise<number | null> {
|
||||||
const { execSync } = await import('child_process')
|
const { execSync } = await import('child_process')
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user