Merge remote-tracking branch 'gh/main' into worktree-agent-a97453f7fa65d51b9
This commit is contained in:
@@ -238,26 +238,19 @@ optimising for: `http://localhost` is not a secure-context problem, so nothing
|
||||
below is needed to develop, to demo locally, or to share a TUI-hosted document
|
||||
with another tab on the same machine.
|
||||
|
||||
One caveat even there, for the reason spelled out below: a WebSocket is exempt
|
||||
from the same-origin policy, and **nothing in the server checks `Origin`** — so
|
||||
a page you did not open can connect to `ws://127.0.0.1:8787` and read or edit
|
||||
the document while the server runs.
|
||||
The server checks `Origin` on the handshake, which is what makes that safe. A
|
||||
WebSocket is exempt from the same-origin policy — there is no CORS preflight —
|
||||
so without the check any page you visited could connect to `ws://127.0.0.1:8787`
|
||||
and read or edit the document while the server runs. **Pages served from
|
||||
loopback are allowed by default**, which is exactly the recipe above; every
|
||||
other page is refused unless you name it with `--allow-origin`. A client that
|
||||
sends no `Origin` at all — curl, a script, a native client — is allowed, because
|
||||
no browser omits it and refusing them would answer a question `--bind` already
|
||||
answers.
|
||||
|
||||
How much the *browser* mitigates that is in flux and should not be leaned on.
|
||||
Chromium has been rolling out **Local Network Access**, which puts requests
|
||||
from a public-network origin to a loopback or private-network address behind a
|
||||
permission prompt; per Chrome's own documentation WebSockets were **not yet**
|
||||
covered when the prompt began shipping, with coverage planned, so whether a
|
||||
given browser and version gates *this* socket is not something this project can
|
||||
state — and nothing here can test it: the Playwright harness serves a loopback
|
||||
origin, so Local Network Access never engages in it. Two gaps are structural
|
||||
rather than version-dependent: it is a Chromium feature, so other browsers need
|
||||
not behave the same; and it only engages for a **public** origin, so the
|
||||
locally-served page this section is about is outside it entirely.
|
||||
|
||||
Treat it, then, as tolerable for a local session you start and stop, and as a
|
||||
reason not to leave one running unattended. The durable fix is the server
|
||||
checking `Origin` itself, which it does not yet do.
|
||||
This is not authentication (see the next section) and it does not depend on the
|
||||
browser: whatever Chromium's **Local Network Access** work ends up covering, the
|
||||
handshake is refused here rather than at the browser's discretion.
|
||||
|
||||
### Sharing a TUI-hosted document with a remote browser
|
||||
|
||||
@@ -311,9 +304,13 @@ any hostile page an allowed user visits can open a socket to your endpoint
|
||||
*from that user's own allowed address*, and read and write the document. An
|
||||
address check cannot see that, because by then the request genuinely does come
|
||||
from an allowed client. Only the **`Origin` header** distinguishes them, and
|
||||
`improvise-ws-server` **never looks at it** — it accepts every handshake. So a
|
||||
proxy needs an Origin allow-list **in addition to**, never instead of,
|
||||
whichever control below you pick. The worked example carries both.
|
||||
`improvise-ws-server` checks it: pages served from loopback are allowed, and
|
||||
anything else must be named with `--allow-origin`. **Deploying behind a proxy
|
||||
therefore needs the flag** — the deployed page's origin is
|
||||
`https://fiddlerwoaroof.github.io`, which is not loopback. Keep the Origin
|
||||
allow-list in the proxy as well: it is defence in depth now rather than the only
|
||||
defence, and it is what covers a proxy configuration that strips or rewrites the
|
||||
header before the server can see it.
|
||||
|
||||
So the honest controls are the ones that need **no credential from the page**:
|
||||
|
||||
@@ -352,11 +349,11 @@ improvise.example.com {
|
||||
@denied not remote_ip 203.0.113.4 198.51.100.0/24
|
||||
abort @denied
|
||||
|
||||
# ALSO not optional, and it is NOT redundant with the address check.
|
||||
# A WebSocket is exempt from the same-origin policy, so any hostile page
|
||||
# an allowed user visits can open this socket FROM THAT ALLOWED ADDRESS
|
||||
# and read and edit the document. The address check cannot see that; only
|
||||
# the Origin can. Name the exact origins the page is served from.
|
||||
# Defence in depth, and NOT redundant with the address check. A WebSocket
|
||||
# is exempt from the same-origin policy, so any hostile page an allowed
|
||||
# user visits can open this socket FROM THAT ALLOWED ADDRESS. The address
|
||||
# check cannot see that; only the Origin can. The server checks it too —
|
||||
# this is what covers a proxy that strips or rewrites the header.
|
||||
@badorigin not header Origin https://fiddlerwoaroof.github.io
|
||||
abort @badorigin
|
||||
|
||||
@@ -365,8 +362,11 @@ improvise.example.com {
|
||||
```
|
||||
|
||||
```sh
|
||||
improvise-ws-server bank-info.improv --port 8787 # still plain ws, still loopback
|
||||
caddy run # terminates TLS, proxies to it
|
||||
# --allow-origin is required here: the deployed page is not a loopback origin,
|
||||
# so without it the server refuses the handshake the proxy forwards.
|
||||
improvise-ws-server bank-info.improv --port 8787 \
|
||||
--allow-origin https://fiddlerwoaroof.github.io # still plain ws, still loopback
|
||||
caddy run # terminates TLS, proxies to it
|
||||
```
|
||||
|
||||
On the same terms as the browser caveat below: **this configuration has not
|
||||
@@ -390,8 +390,9 @@ answered as an ordinary request and the socket never opens.
|
||||
For a one-off share with no DNS name and no certificate of your own,
|
||||
`cloudflared tunnel --url http://127.0.0.1:8787` or `ngrok http 8787` each
|
||||
print an https hostname that works the same way — pass the hostname to `?ws=`
|
||||
without a scheme. Both print a **public** URL and neither authenticates
|
||||
anything by default, and an unguessable URL is not a control.
|
||||
without a scheme, and pass the page's own origin to `--allow-origin`, since it
|
||||
is not loopback. Both print a **public** URL and neither authenticates anything
|
||||
by default, and an unguessable URL is not a control.
|
||||
|
||||
Two things carry over from above. `ngrok http --basic-auth` does **not** help
|
||||
here — the page cannot answer a basic-auth challenge on a socket handshake —
|
||||
|
||||
Reference in New Issue
Block a user