Files
orca/config/scripts/scan-release-artifacts-antivirus.test.mjs
T
Neil d2dbe2c385 fix(windows): replace the managed CLI launcher with a native one (#24094)
* docs(security): add the antivirus clearance path for future releases

Every AV false positive here has been handled one vendor and one shipped
version at a time. Document the programs that clear future releases instead --
signer and product enrollment rather than per-build sample submission -- and add
a script that reports an RC's current detection state by hash, so a verdict is
found before users meet it in an issue report.

Hash lookup only by default; --upload transmits the artifact and stays manual.

* fix(windows): replace the managed CLI launcher with a native one

resources\bin\orca.exe was a csc-compiled MSIL assembly: a small, freshly
compiled .NET image in a user-writable directory that mutates environment
variables and proxies a child process. That is the shape .NET dropper
heuristics are trained on, and every verdict against it named the family --
MSILHeracles from two vendors, Wacatac!ml from a third. Signing the file does
not change its shape, so signing never cleared it.

Rebuild it in Rust. Same resolution, same environment contract, same argv
passthrough that keeps newline-bearing orchestration bodies intact (#8374), and
the child still inherits our environment block rather than an explicit map, so
a block carrying both PATH and Path survives (#12046). The PE now carries
publisher, version, icon and an asInvoker manifest from build.rs.

Refs #23383

* ci(windows): install the Rust toolchain before building the CLI launcher

The hosted runners happen to ship cargo, but a real Windows dev box does not --
verified on our own Windows QA host, where cargo and rustc were both absent.
Relying on the image means a future image change fails deep inside
electron-builder's native hook instead of at an obvious step.
2026-09-30 02:49:03 -07:00

97 lines
3.4 KiB
JavaScript

import { mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { describe, expect, it } from 'vitest'
import {
formatArtifactReport,
hasAnyDetection,
hashFile,
summarizeEngineVerdicts
} from './scan-release-artifacts-antivirus.mjs'
describe('antivirus detection report', () => {
it('hashes an artifact with the same digest the issue reports quote', async () => {
const directory = await mkdtemp(join(tmpdir(), 'orca-av-scan-'))
try {
const artifact = join(directory, 'orca-windows-setup.exe')
await writeFile(artifact, 'orca', 'utf8')
// Lowercase hex sha256, so a reporter's `shasum -a 256` output and ours
// compare directly — that comparison is what makes a submission credible.
expect(await hashFile(artifact)).toBe(
'e0c924608fdcda8536bd9cc86b0fce0ab2d54ecc1e8ed9673624c39cde7f7820'
)
} finally {
await rm(directory, { recursive: true, force: true })
}
})
it('counts only malicious and suspicious categories as verdicts', () => {
const { scanned, flagged } = summarizeEngineVerdicts({
Microsoft: { category: 'malicious', result: 'Trojan:Win32/Wacatac.B!ml' },
Bitdefender: { category: 'suspicious', result: 'Gen:Variant.MSILHeracles' },
Kaspersky: { category: 'undetected', result: null },
ESET: { category: 'type-unsupported', result: null },
Avast: { category: 'timeout', result: null }
})
expect(scanned).toBe(5)
expect(flagged).toEqual([
{ engine: 'Bitdefender', category: 'suspicious', detection: 'Gen:Variant.MSILHeracles' },
{ engine: 'Microsoft', category: 'malicious', detection: 'Trojan:Win32/Wacatac.B!ml' }
])
})
it('treats a missing results payload as nothing scanned rather than clean', () => {
expect(summarizeEngineVerdicts(undefined)).toEqual({ scanned: 0, flagged: [] })
expect(summarizeEngineVerdicts(null)).toEqual({ scanned: 0, flagged: [] })
})
it('names an unnamed detection instead of printing undefined', () => {
const { flagged } = summarizeEngineVerdicts({ Sophos: { category: 'malicious' } })
expect(flagged[0].detection).toBe('<unnamed>')
})
it('distinguishes an unscanned build from a clean one', () => {
const unscanned = formatArtifactReport({
name: 'orca-windows-setup.exe',
sha256: 'a'.repeat(64),
known: false,
scanned: 0,
flagged: []
})
expect(unscanned).toContain('never been scanned')
const clean = formatArtifactReport({
name: 'orca-windows-setup.exe',
sha256: 'a'.repeat(64),
known: true,
scanned: 70,
flagged: []
})
expect(clean).toContain('clean across 70 engines')
})
it('reports every flagging engine and its detection name', () => {
const report = formatArtifactReport({
name: 'orca.exe',
sha256: 'b'.repeat(64),
known: true,
scanned: 70,
flagged: [
{ engine: 'TrendMicro', category: 'malicious', detection: 'Trojan.MSIL.MSILHERACLES' }
]
})
expect(report).toContain('1 of 70 engines flag this build')
expect(report).toContain('TrendMicro: Trojan.MSIL.MSILHERACLES')
})
it('flags the release when any single artifact carries a verdict', () => {
const clean = { flagged: [] }
const flagged = { flagged: [{ engine: 'Microsoft' }] }
expect(hasAnyDetection([clean, clean])).toBe(false)
expect(hasAnyDetection([clean, flagged])).toBe(true)
})
})