Harden audit findings: correctness, type-safety, Windows conventions & polish
Audit-pass over CODE-AUDIT.md (~48 items closed this pass; all verified — typecheck + 234 tests + eslint + prettier green). Correctness / bugs: - B3: match the release checksum to the asset's filename line (no wrong-hash verify) - B4: newline-safe metadata probe (one --print with a unit-separator delimiter) - B5 / L88: guard the meta event against canceled items; progress no longer promotes a queued item outside pump() - B7: cookie-login promise always resolves (handles destroy-without-close) - L146: trim parser rejects >2 colon-group times; M36: Library selection counts only actionable rows - L11 / L50 / L156 / L57 / L159 / L15 / L3: live queue count, empty-cookie message, schedule picker min, dead-code/comment cleanup Type safety: - Enable noUncheckedIndexedAccess + noFallthroughCasesInSwitch (15 real edge cases fixed) Resilience / Windows / metadata: - R5: settings write failure handled (no unhandled IPC rejection; reconciles to truth) - W1 / W5 / W6: min window size, seeded folder picker, parented sign-in window; L147 dead macOS branches removed - CL1: shared stdout markers; package/builder metadata (license, homepage, repository, copyright, tsbuildinfo glob) Copy / docs / tests: - M37 / SR9 dev-jargon cleanup in hints; M8 / M25 / M26 / L66 / L80 / L81 reconciled - New unit tests for L35 (isValidMediaItem) and L36 (compareVersions) This commit also checkpoints the previously-uncommitted feat/tray-background-clipboard work it builds on: background running + auto-download, library clipboard detection, tray, binary management & library scale, credential encryption at rest, the shared jsonStore and ui/ primitives, and the eslint/prettier tooling. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+29
-7
@@ -68,9 +68,10 @@ function updateDir(): string {
|
||||
* an "upgrade" over the same shipped version.
|
||||
*/
|
||||
function parseVersion(v: string): number[] {
|
||||
return v
|
||||
.replace(/^v/i, '')
|
||||
.split(/[-+]/)[0]
|
||||
// split() always yields at least one element; `?? ''` only satisfies the type
|
||||
// checker (noUncheckedIndexedAccess) for the [0] access.
|
||||
const core = v.replace(/^v/i, '').split(/[-+]/)[0] ?? ''
|
||||
return core
|
||||
.split('.')
|
||||
.map((p) => parseInt(p, 10))
|
||||
.filter((n) => !Number.isNaN(n))
|
||||
@@ -105,9 +106,28 @@ interface GiteaRelease {
|
||||
* output (`<hash> file`), or PowerShell Get-FileHash (uppercase). Returns the
|
||||
* lowercase digest, or null if there's no standalone 64-char hex token (so a
|
||||
* longer run like a sha512 digest is ignored rather than sliced).
|
||||
*
|
||||
* When `fileName` is given (the installer asset's name), a multi-line / combined
|
||||
* checksum file is matched line-by-line and the hash on the line naming THIS
|
||||
* asset wins — so a `<asset>.sha256` that happens to list several files can't
|
||||
* verify the installer against the wrong line's hash (B3). Falls back to the
|
||||
* first standalone digest for bare single-hash files (no filename present).
|
||||
*/
|
||||
export function extractSha256(text: string): string | null {
|
||||
const m = text.match(/\b[a-f0-9]{64}\b/i)
|
||||
export function extractSha256(text: string, fileName?: string): string | null {
|
||||
const hashRe = /\b[a-f0-9]{64}\b/i
|
||||
if (fileName) {
|
||||
const base = fileName.toLowerCase()
|
||||
for (const line of text.split(/\r?\n/)) {
|
||||
const m = line.match(hashRe)
|
||||
if (!m) continue
|
||||
// The filename is the last whitespace-delimited token on a sha256sum /
|
||||
// Get-FileHash line (optionally with a '*' binary-mode marker). Match it
|
||||
// exactly — a loose substring would let 'App.exe' match a 'MyApp.exe' line.
|
||||
const last = (line.trim().split(/\s+/).pop() ?? '').replace(/^\*/, '')
|
||||
if (last.toLowerCase() === base) return m[0].toLowerCase()
|
||||
}
|
||||
}
|
||||
const m = text.match(hashRe)
|
||||
return m ? m[0].toLowerCase() : null
|
||||
}
|
||||
|
||||
@@ -200,7 +220,9 @@ function fetchTrustedText(
|
||||
): Promise<{ ok: true; text: string } | { ok: false; status?: number; error: string }> {
|
||||
return new Promise((resolve) => {
|
||||
let settled = false
|
||||
const done = (r: { ok: true; text: string } | { ok: false; status?: number; error: string }): void => {
|
||||
const done = (
|
||||
r: { ok: true; text: string } | { ok: false; status?: number; error: string }
|
||||
): void => {
|
||||
if (settled) return
|
||||
settled = true
|
||||
clearTimeout(timer)
|
||||
@@ -261,7 +283,7 @@ export async function downloadAppUpdate(url: string, wc: WebContents): Promise<A
|
||||
const sum = await fetchTrustedText(checksumUrl)
|
||||
let expectedSha: string | null = null
|
||||
if (sum.ok) {
|
||||
expectedSha = extractSha256(sum.text)
|
||||
expectedSha = extractSha256(sum.text, safeName)
|
||||
if (!expectedSha) return { ok: false, error: "The update's checksum file is malformed." }
|
||||
} else if (sum.status === 404) {
|
||||
if (REQUIRE_CHECKSUM) {
|
||||
|
||||
Reference in New Issue
Block a user