# What breaks over plain http

**Audited 2026-08-13**, after `crypto.randomUUID` took the chat page down at the end of every turn.

Officer is reached at `http://officer-dev:9000` — a tailnet address, so **neither https nor
localhost**, and therefore not a [secure context]. A set of browser APIs are unavailable there by
specification, not by policy, and there is no flag that changes it.

The failure mode is what makes this worth a document. Two of the three shapes below are silent:

| shape | what a user sees |
| --- | --- |
| `crypto.randomUUID()` | `TypeError` — and if it is inside a `useState` initialiser, the whole tree unmounts |
| `navigator.clipboard.writeText()` | `TypeError`, killing the click handler |
| `navigator.clipboard?.writeText()` | **nothing at all** — the button reports success and copies nothing |

The optional-chained one is the worst: indistinguishable from working until somebody pastes.

---

## Fixed

### `crypto.randomUUID` — 18 call sites
Secure-context only. `crypto.getRandomValues` is **not** — it lives on `Crypto` rather than
`SubtleCrypto` — so `helpers/random-id.ts` builds the same v4 UUID from the same CSPRNG when
`randomUUID` is absent. Same entropy, same version and variant bits.

### `navigator.clipboard.writeText` — 20 call sites across 18 files
Secure-context only. `helpers/clipboard.ts` falls back to `document.execCommand('copy')` over an
off-screen textarea, which predates the secure-context rule and works on any origin. Deprecated and
working beats modern and absent.

One call site carried the comment *"Officer is always behind HTTPS"*. It was not.

---

## Cannot be fixed this way

### `navigator.clipboard.read()` — pasting a file in the file browser
No fallback exists. `document.execCommand('paste')` was never permitted from script, so on an
insecure origin there is no way to pull clipboard contents on demand — only a real paste event the
user initiates, which is a different interaction. Now guarded by `canReadClipboard()` and refuses
with an explanation instead of throwing.

### `getUserMedia` — audio recording, 4 files
`apps/Chat/useAudioRecording.ts`, `apps/FileBrowser/.../DictateDialog.tsx`,
`apps/QrTransfer/Receiver.tsx`, and a test. Requires a secure context and cannot be polyfilled — the
browser will not hand out a microphone or camera over http.

**Being removed** rather than guarded: the owner uses an external dictation app. Note `QrTransfer`
uses it for the CAMERA rather than a microphone, so removing "audio" does not cover it — that one
needs its own decision.

### `navigator.credentials` — passkeys
WebAuthn is secure-context only. `helpers/passkeys.ts` exists and cannot work over http, whatever is
done to it. Not currently reachable, so nothing is broken today.

---

## Checked and clear

- **`crypto.subtle`** — not used anywhere in the frontend. This was the one worth confirming, since
  it would have had no cheap fallback.
- **`Notification`** — the six matches are type names, not the browser API. Nothing calls
  `new Notification` or `requestPermission`.
- **Service workers, WebUSB, WebSerial, WebBluetooth, Payment Request, Wake Lock, Storage Manager,
  `SharedArrayBuffer`** — not used.
- **`navigator.geolocation`** (`widgets/Weather`) — secure-context only, but already guarded with
  `if (!navigator.geolocation) return;`, so it degrades rather than throws. The widget simply cannot
  locate you over http.
- **`navigator.share`** (`Headscale/InvitesView`) — already guarded with a `typeof` check, and its
  comment notes it is absent on desktop browsers anyway.
- **WebSockets, IndexedDB, localStorage, EventSource** — no secure-context restriction. Chat,
  terminal and the sidecar transports are unaffected.

---

## The alternative

All of this disappears with a certificate, and `tailscale cert` issues a real one for the MagicDNS
name in about one command — no public DNS, no port 80 challenge, no renewal to remember. Worth
knowing that the choice here was "make it work over http", not "http is the only option".

[secure context]: https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts
