The question

On October 10, Dominik Fojcik posted a website that turns a laptop screen into a window. The webcam follows your head, and the picture shifts so the room behind the glass has depth. The trick itself is old. Johnny Lee did it with a Wii remote in 2007. The part I kept thinking about was the question at the end of the post: how do you prove to people the camera is only used for the effect?

A privacy policy is a promise. This page doesn't ask for your trust. It locks itself, and it tells you how to check the lock.

What this looks like off the screen

Think of an evidence bag. An officer can tell you nobody opened it. Or you can look at the tape and the signature across the seal and see for yourself. A privacy policy is the officer's word. This page is the seal.

You'll run into this more. A website asks you to look into your camera to prove you're over 18. The front office at your kid's school photographs every visitor on a tablet. Your city asks you to register your doorbell camera with the police department's real-time crime center. You should be able to ask where the picture goes and get something better than a promise: a lock you can check, a log of what it blocked, and a straight answer about what the lock can't cover.

Two locks

The first lock arrives with the page. Before any of my code runs, the server tells your browser what this page may talk to: matthewvisher.com, and nothing else. No analytics, no font service, no CDN. That's why the fonts and the tracker are served from this site.

content-security-policy:
  default-src 'none'
  script-src  'self' 'wasm-unsafe-eval'
  connect-src 'self'
  style-src 'self'   font-src 'self'   img-src 'self'
  worker-src 'none'  base-uri 'none'   form-action 'none'
  frame-ancestors 'none'
permissions-policy:
  camera=(self), microphone=()
x-dns-prefetch-control: off
referrer-policy: no-referrer

The second lock closes before the camera opens. When you press Start, the page loads the tracker, then adds a stricter rule: default-src 'none'. From then on the page can't fetch, post, or load anything from anywhere, my own server included. A browser lets a page tighten these rules but never loosen them, so there's no unlock. Reload and you get the page you started with, camera off.

Check it yourself

  1. Open your browser's developer tools to the Network tab and reload. One row is already marked blocked; the section below explains it. Press Start. Everything else arrives before the camera light comes on: the tracker, a 230 KB face model, and 11.7 MB of WebAssembly. Once the light is on, nothing new loads. About a minute in, a second row shows up marked blocked.
  2. In the Console, type fetch('/') before you press Start. It works. Type it again after. The browser refuses and quotes the rule that stopped it.
  3. Turn off your Wi-Fi with the camera running. Nothing changes. The window doesn't need the internet.
  4. Reload with the Network tab open, click the first row, and read the response headers. Those are the rules your browser held this page to from the first byte.
  5. Check the tracker against Google's published package, @mediapipe/[email protected] on npm. Hash the two files below from that package and from this site's /lab/window/vendor/. Each pair should match.
wasm/vision_wasm_internal.wasm
  8da277a733926eacd0474b8704b36742
  d6ec3231c57a860c5b889dff8f1df886
vision_bundle.mjs
  d885630c297c0b20b1fe86096cb06291
  c4c8080876f27852e724f24ac603713f

What the lock caught

It catches something before you press anything. Cloudflare, which serves this site, adds an analytics script to the pages it sends out. That's a setting on my hosting account, and the script isn't in this page's source. It gets added on the way to you, and the first lock refuses it, so Refused reads one as soon as the page loads.

About a minute after you press Start, Refused ticks up again. The request it caught is a POST to odml.pa.googleapis.com/v1/log. That's MediaPipe, the tracker, reporting usage metrics to Google on a one-minute timer. Google's documentation says the library does this, and says the metrics cover performance and usage, not your images. The page refuses it anyway, because it promised the camera stays in this tab. A site that runs this version of MediaPipe without a rule like this one sends that report.

What this doesn't prove