fix(main): SFTP data integrity, session lifecycle, security hardening
- upload: per-chunk buffer (ssh2 re-reads the overflow tail after the ACK; a reused buffer silently corrupted every file >= ~254KB) - close the cached SFTP channel on eviction, attach an 'error' handler, close the download handle, time out execQuiet, fail partial deletes - per-session StringDecoder for the ssh data plane (CJK mojibake), real exit codes, safe replay truncation, zmodem abort/counter/timer fixes - sysinfo: idempotent poll end, error routing, per-poll watchdog, proc(5) CPU total; expand cd ~/$HOME/%USERPROFILE% paths; log sanitizer fixes - security: will-navigate guard, central IPC sender check, scheme allowlist for openExternal, single-instance else branch, layout id and log-name whitelists, custom theme sanitizing, atomic JSON writes with EPERM retry in store.writeJson - updater: per-attempt feed choice, quitAndInstall relaunch, dev guard, update-state getter
This commit is contained in:
1 parent
4c6a29ef28
commit
e04f4f0ac1
16 files changed
+452
-98
No files matched your search
+31
-6
@@ -129,10 +129,26 @@ function pollOnce(id: string, state: PollState): void {
|
||||
return
|
||||
}
|
||||
let out = ''
|
||||
let done = false
|
||||
// A wedged remote command (e.g. df on a hung NFS mount) never closes the
|
||||
// stream; without this watchdog the poll loop freezes silently forever.
|
||||
const watchdog = setTimeout(() => {
|
||||
if (done) return
|
||||
done = true
|
||||
try {
|
||||
stream.close()
|
||||
} catch {
|
||||
// best effort
|
||||
}
|
||||
handleError(id, state, t('main.sysinfo.pollTimeout'))
|
||||
}, state.intervalMs * 2)
|
||||
stream.on('data', (d: Buffer) => {
|
||||
out += d.toString('utf8')
|
||||
})
|
||||
const onEnd = (): void => {
|
||||
if (done) return
|
||||
done = true
|
||||
clearTimeout(watchdog)
|
||||
if (state.stopped) return
|
||||
try {
|
||||
stream.close()
|
||||
@@ -142,7 +158,18 @@ function pollOnce(id: string, state: PollState): void {
|
||||
handleOutput(id, state, out)
|
||||
}
|
||||
stream.on('close', onEnd)
|
||||
stream.on('error', () => onEnd())
|
||||
stream.on('error', (streamErr: Error) => {
|
||||
if (done) return
|
||||
done = true
|
||||
clearTimeout(watchdog)
|
||||
if (state.stopped) return
|
||||
try {
|
||||
stream.close()
|
||||
} catch {
|
||||
// best effort
|
||||
}
|
||||
handleError(id, state, streamErr?.message || t('main.sysinfo.execFailed'))
|
||||
})
|
||||
})
|
||||
} catch (err) {
|
||||
handleError(id, state, (err as Error).message)
|
||||
@@ -304,8 +331,8 @@ function parseProc(blob: string): { cpu: SysinfoSample['cpu']; cpuIdle: number;
|
||||
const irq = f[5] ?? 0
|
||||
const softirq = f[6] ?? 0
|
||||
const steal = f[7] ?? 0
|
||||
const guest = f[8] ?? 0
|
||||
const guestNice = f[9] ?? 0
|
||||
// guest/guest_nice (f[8]/f[9]) are already included in user/nice per
|
||||
// proc(5) — summing them in would double-count and understate CPU%.
|
||||
idle = idleTicks + iowait
|
||||
total =
|
||||
user +
|
||||
@@ -314,9 +341,7 @@ function parseProc(blob: string): { cpu: SysinfoSample['cpu']; cpuIdle: number;
|
||||
idle +
|
||||
irq +
|
||||
softirq +
|
||||
steal +
|
||||
guest +
|
||||
guestNice
|
||||
steal
|
||||
} else {
|
||||
cores += 1
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user