Skip to content

Windows: release and update artifacts are unsigned — no provenance anchor, and Defender already ate an update mid-swap #19

Description

@slabbdev

This one is process rather than code, and it's already on your own list (TODO-windows.md, "Still open" item 1) — filing it because it sits on the update trust path that #13/#16 touch, and because the incident you documented makes it more than a checkbox.

Facts

  • release.yml has no signtool anywhere — the only signing is ad-hoc macOS codesign (.github/workflows/release.yml:278,350-351). Every Windows artifact ships with a fresh, unknown hash.
  • runtime/update.js:280-281 makes that explicit for the update path: "Windows has no codesign equivalent here; the https + sha256 manifest is the trust anchor" — so unlike macOS (Team ID pinning), a Windows install has nothing to verify the downloaded bundle against beyond TLS + the manifest hash.
  • Your own 0.30.0 incident (TODO-windows.md): Defender's cloud heuristic (Behavior:Win32/Execution.A!ml, severity "Severe") quarantined the freshly downloaded tjs.exe out of tinyjs update's staging dir mid-swap, and the dev box needed exclusions on %LOCALAPPDATA%\tinyjs, tinyjs.new, and the examples tree before a release pass could run. The trigger is structural — unsigned + new hash + Mark-of-the-Web + spawns/executes — and every release re-rolls it.

Consequences

No provenance users can verify, update availability hostage to cloud heuristics, SmartScreen flags first runs, and end users downloading the example -win.zips roll the same dice.

Suggested fix

Authenticode cert in the release CI (OV builds per-cert reputation over time; EV starts trusted), signing tjs.exe/launcher-win and the per-app exes tinyjs build emits. Longer term, a signed per-app exe gives update.js something to pin — the Windows analogue of the macOS Team ID check. Meanwhile, submitting each release's tjs.exe at microsoft.com/wdsi/filesubmission (as your TODO notes) keeps the heuristics tame.

Can't help test signing without a cert, so reporting rather than PR'ing this one.

Activity

  1. tarwin commented on Oct 3, 2026

    @tarwin
    Owner

    Thanks @slabbdev - I've looked at this a bit and am at a loss of where to start? Have you gone through whatever it is Microsoft asks you to do? The MacOS one was ... frustrating ... but at least there seemed to be only a single way to go about it. It seems that for Windows apps the whole ecosystem is a bit more confusing?

  2. slabbdev commented on Oct 3, 2026

    @slabbdev
    ContributorAuthor

    Ha — yes, the Windows ecosystem, where the answer to "how do I sign" used to be "buy a certificate, then buy a USB token to keep it in". It's better now. The two real paths:

    1. Trusted Signing (Microsoft's own service) — the one I'd try first. An Azure service where Microsoft holds the key and does the signing: no certificate file, no hardware token, and certificates issued under Microsoft's CA get SmartScreen reputation immediately instead of after months of accumulated downloads. Individual developers are eligible (identity validation with an ID), the base tier is a flat ~$10/month, and it integrates with signtool through their dlib. Caveat: eligibility and pricing moved around while it was in preview, so worth re-checking the current terms before committing.

    2. The classic route: an OV or EV code-signing certificate (DigiCert, Sectigo, …). OV is ~$100–300/yr but SmartScreen reputation builds slowly; EV is ~$300–600/yr and historically got faster reputation, but wants a hardware token or a cloud HSM (DigiCert KeyLocker and friends).

    For tinyjs specifically I'd split the work in two:

    • Provenance for updates, independent of the CA question: the manifest already carries a sha256 — it could also carry the expected Authenticode signer thumbprint, and update.install would refuse a download whose signature doesn't match. That works with any certificate and plugs the "Defender ate an update mid-swap" hole (a swapped file that isn't ours gets rolled back). Happy to PR that — it's contained in update.js.
    • The actual signing step: a tinyjs.json signing block for Windows + tinyjs build invoking signtool (either the Trusted Signing dlib or a conventional certificate). Say which path you want and I'll wire it.

    And I still owe the #20 review's icon items (icon: 'app', real cross-platform PNG padding) — still wanted, or are you taking those?

    (Also — thanks for the v0.44–0.46 blitz. Seeing #18's subframe gate and the pipe ACLs land that fast was something.)

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