Skip to content

T1: [Security] fetch-binaries.ts has no SHA-256 verification of downloaded MediaInfo binary #75

Description

@curtyo18

Summary

scripts/fetch-binaries.ts downloads MediaInfo from mediaarea.net over HTTPS and shells out to it during every scan to extract video metadata. There is no integrity verification (no SHA-256, no signature) on the downloaded binary — only a version-pinned URL. A future mediaarea.net compromise (or a MITM on a corporate proxy that re-signs TLS) would land an attacker-controlled executable in packages/engine/bin/mediainfo, where it then executes with the user's privileges on every scan.

Additionally, the downloaded .zip is written directly to its final name (mediainfo.zip). If the fetch is interrupted, a corrupt zip remains at the final path, which can mislead other tooling. The extracted binary already uses the tmpfile→rename pattern correctly (fetch-binaries.ts:108-118); the zip download should follow the same pattern.

Background

From the 2026-05-17 multi-agent full-repo review. Verified: grep -n "createHash\|sha256\|checksum\|integrity\|verify" scripts/fetch-binaries.ts returns zero hits.

Acceptance criteria

SHA-256 verification

  • The expected SHA-256 of mediainfo.zip is pinned in scripts/fetch-binaries.ts alongside the URL (same MEDIAINFO_VERSION constant, parallel MEDIAINFO_ZIP_SHA256).
  • After download (and before extraction), the script computes createHash('sha256').update(zipBytes).digest('hex') and compares to the pinned value.
  • On mismatch: delete the downloaded file, print the expected vs actual hashes, exit non-zero with a clear message ("expected SHA-256 ABC, got XYZ — refusing to install").
  • On match: proceed to extraction.
  • The pinned hash for the current MEDIAINFO_VERSION is captured by running the download once and recording the result (note in PR description: "captured 2026-MM-DD by downloading from https://...").
  • Test added: a unit test that feeds the verifier known bytes + known hash and confirms accept/reject behavior.

Zip tmpfile→rename

  • The zip download path is mediainfo.zip.partial; only after successful download AND hash verification is it renamed to mediainfo.zip.
  • An interrupted download leaves only the .partial file, which is .gitignored. Re-running npm run fetch-binaries cleans it up and re-downloads.

Files affected (likely)

  • scripts/fetch-binaries.ts — verification + tmpfile pattern
  • .gitignore — confirm packages/engine/bin/*.partial and *.zip already ignored, or add
  • A small unit test of the verifier (place in scripts/ if a test path exists, otherwise inline in the script's own test file)

Suggested approach

import { createHash } from 'node:crypto';
import { readFileSync } from 'node:fs';

const MEDIAINFO_ZIP_SHA256 = '...'; // capture once; pin

async function verifyZip(path: string): Promise<void> {
  const bytes = readFileSync(path);
  const actual = createHash('sha256').update(bytes).digest('hex');
  if (actual !== MEDIAINFO_ZIP_SHA256) {
    throw new Error(`SHA-256 mismatch: expected ${MEDIAINFO_ZIP_SHA256}, got ${actual}`);
  }
}

Download to mediainfo.zip.partial, verify, then renameSync to mediainfo.zip.

Out of scope

  • GPG-signature verification (overkill for v1; SHA-256 from a pinned constant is the minimum bar).
  • Switching to a self-hosted mirror.
  • ffprobe (the spec mentions ffprobe as a backup; not currently fetched).

References

  • Spec §3.1 — mediainfo as a subprocess dependency
  • Code: scripts/fetch-binaries.ts
  • Standards: implicit "supply-chain hygiene" — standards/dependency-discipline.md covers third-party packages but not bundled binaries; this is the binary-fetch equivalent

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions