release: privacy scan of signed macOS artifacts leaves out Apple code signatures only (copies with codesign --remove-signature on every Mach-O, the DMG's koly signature blob zeroed); any other hit still fails; tests and docs

This commit is contained in:
CRTkafa 2026-10-14 08:21:35 +08:00
parent 3a27c78f49
commit ae681e3ecd
6 changed files with 228 additions and 3 deletions

View file

@ -41,7 +41,8 @@ import { verify as verifySig } from '../cli/lib/minisign';
import { buildOne, OUT_DIR as SIDECAR_DIR } from './build-sidecars';
import { versions } from './desktop-version';
import { upsert as forgejoUpsert } from './forgejo-release';
import { scan } from './privacy-scan';
import { isMacArtifact, scanSignedMac } from './mac-signature-scan';
import { allTerms, DEFAULT_TERMS, type Report, scan, TERMS_ENV } from './privacy-scan';
export const ROOT = resolve(import.meta.dir, '..');
const APP_DIR = join(ROOT, 'apps/desktop');
@ -187,8 +188,27 @@ function signFile(file: string): string {
return sig;
}
/**
* The privacy scan of release artifacts. Signed macOS artifacts (.app, .dmg, .app.tar.gz) are scanned without their
* Apple code signatures (tools/mac-signature-scan.ts): the Developer ID certificate in them names the developer, and
* that name may appear there only. Every other byte is scanned; a hit outside a signature fails the release.
*/
function privacyGate(paths: string[]): { files: number; bytes: number; hits: number } {
const r = scan(paths);
const mac = process.platform === 'darwin' ? paths.filter(isMacArtifact) : [];
const r = scan(paths.filter(p => !mac.includes(p)));
if (mac.length) {
const termsFile = process.env[TERMS_ENV] ?? DEFAULT_TERMS;
const terms = allTerms(termsFile);
const m: Report = { files: 0, bytes: 0, hits: [], errors: [] };
for (const p of mac) {
if (!existsSync(p)) { m.errors.push(`${p}: does not exist`); continue; }
try { scanSignedMac(p, terms, m); } catch (e) { m.errors.push((e as Error).message); }
}
r.files += m.files; r.bytes += m.bytes; r.hits.push(...m.hits); r.errors.push(...m.errors);
r.terms = terms.length;
if (!r.termsFile && existsSync(termsFile)) r.termsFile = termsFile;
say(`privacy: ${mac.length} signed macOS artifact(s) scanned without their code signatures (the Developer ID certificate may name the developer there only)`);
}
for (const h of r.hits) console.error(`privacy: ${h.file} @${h.offset}${h.encoding ? ` (${h.encoding})` : ''}: ${h.what}`);
for (const e of r.errors) console.error(`privacy: could not open ${e}`);
if (r.hits.length || r.errors.length) fail(`privacy scan failed (${r.hits.length} hits, ${r.errors.length} errors): nothing may ship`);