What WinQuick does and does not protect you from, stated precisely.
WinQuick runs code inside a real Windows guest under a hardware hypervisor (Apple's Hypervisor Framework, via QEMU). The guest is a separate operating system on separate virtual hardware; it does not share a kernel with macOS.
This is a strong isolation boundary, but WinQuick is not a hardened malware sandbox and is not audited as one. It is designed for running your builds and tests, and for giving an automated agent somewhere safe to be wrong. It is not designed for detonating hostile samples. If you need that, use something built and audited for it.
| Direction | What can cross |
|---|---|
| host → guest | the command, the workspace copy, capability volumes, the package-cache copy |
| guest → host | stdout, stderr, the exit code, and files you explicitly request with --artifact |
Nothing else. In particular the guest has:
- no network at all — no NIC is attached, so no internet, no LAN, no host
- no access to your filesystem — only copies of what you named
- no way to change the Windows image — the base is opened read-only
- no persistence between runs — every run starts from the same frozen state
Every run gets fresh copies of everything writable:
base.qcow2 read-only, never written
└─ root.qcow2 per-run copy-on-write overlay, deleted afterwards
workspace.img per-run copy of your project
caps/*.img per-run copies of PowerShell, .NET, the package cache
artifacts.img per-run, empty at the start
uefi-vars.fd per-run, so even firmware state does not carry over
Filesystem writes, registry changes and environment variables set by one run are
absent in the next. This is tested, not assumed — see the disposability checks in
tests/integration.sh.
-w <dir> copies the directory into a disk image. The guest sees a copy at
C:\workspace and can write to it freely; none of that reaches the host. A build
script cannot modify, delete or plant files in your project.
The only way out is --artifact, which you have to ask for by name.
Artifacts are read out of a filesystem the guest controlled, so their names are
treated as hostile input. WinQuick refuses any entry that is not a single
ordinary path component — anything containing /, \, a NUL, ., .., or an
absolute path is skipped with a warning. A guest cannot use a crafted filename to
write outside the artifacts directory.
Artifacts are written to ./winquick-artifacts by default, never into your
project, and WinQuick refuses to write into a non-empty directory unless you pass
--artifact-overwrite.
The canonical NuGet cache lives on your Mac and is written only by host-side
dotnet restore when you run winquick cache sync. The guest gets a
copy-on-write clone of it, so a build script can write into %NUGET_PACKAGES%
all it likes and none of it survives the run or reaches the canonical copy.
This matters: a persistent package cache is exactly the kind of place a hostile build would try to leave something for a future run to pick up. It cannot.
Verified in the test suite: after a run that writes into the cache, the canonical image's SHA-256 is unchanged and the next run does not see the file.
PowerShell and .NET are downloaded from Microsoft over HTTPS and verified against pinned SHA-256 digests before use. They are stored as disk images and, like everything else, cloned per run — a run cannot modify the copy of PowerShell that later runs get.
Arguments are passed as a Windows command line built with the Windows C-runtime quoting rules, so an argument containing spaces, quotes or backslashes reaches the program as one argument rather than being re-split. There is no shell interpolation of host variables into the guest command.
WinQuick does not interpret the command itself, and does not pass it through a host shell.
Every run carries a nonce that the guest echoes back with the exit code. If it does not match, WinQuick treats the result as invalid, rebuilds, and retries rather than reporting a confident wrong answer. This exists because a stale read once produced "exit 0, no output" for a command that never ran — a wrong result that looks right is worse than a crash.
Everything is under ~/.winquick:
~/.winquick/
images/ the Windows runtime built from Microsoft's image
capabilities/ PowerShell, .NET, the package-cache volume
caches/nuget/ packages restored on this Mac
states/ the frozen prepared guest
cache/ downloaded installers
run/ transient per-run state, deleted when a run ends
winquick clean removes generated data; --all removes everything. Neither
touches your projects. Files are created with your user's normal permissions;
WinQuick never needs root and never asks for it.
The desktop capability gives the guest synthetic keyboard and mouse input and the ability to read any window's pixels and control tree. That is the feature, and it is worth being explicit about what it does and does not change.
- The boundary is unchanged. Same QEMU isolation as
winquick run: no network in the guest, a disposable disk, and no host filesystem access beyond the volumes WinQuick attaches. Input injection and screen capture happen inside that boundary and do not widen it. - This is not a malware-analysis sandbox. It was not one before the desktop capability and it is not one now. WinQuick does not attempt to resist a guest that is actively trying to escape.
- No ports are opened. The session's QMP socket is a unix socket under
~/.winquick/desktop/. There is no VNC or RDP server and nothing listens on TCP. - Screenshots are of the guest's screen, written where you asked on your Mac. Nothing is uploaded anywhere.
- A session is disposable like a run. It works on a copy-on-write overlay
over the desktop image;
winquick desktop stopdeletes it, andwinquick cleanstops a running one first. Capability volumes are cloned, so a session cannot modify what is installed. - The desktop image is derived from your Microsoft media and, like the base image, must not be redistributed.
- Hypervisor escapes. WinQuick's isolation is QEMU's and Apple's. A bug in either is a bug in WinQuick's boundary. QEMU is a large program with a real CVE history; keep it updated.
- Resource exhaustion. A run can spin the CPU, fill its disk image and use its full memory allocation until the timeout fires. There is no CPU or I/O quota.
- Anything you extract. Artifacts are files a possibly-hostile guest wrote. WinQuick guarantees where they land, not what is in them.
- Side channels. No attempt is made to mitigate speculative-execution or timing side channels between guest and host.
Open a security advisory on the GitHub repository rather than a public issue.