|
|
|
@@ -1,28 +1,27 @@
|
|
|
|
|
# dsh-trustworthy
|
|
|
|
|
|
|
|
|
|
**Removes the untrusted-origin restriction from DeepSeek Harness without
|
|
|
|
|
removing its authentication.** Every browser window is treated as if it were
|
|
|
|
|
running on the harness machine itself: no declared trusted host and no
|
|
|
|
|
per-plugin origin fence, because one interceptor answers every request with the
|
|
|
|
|
loopback authority. The harness's own browser-session check stays in place —
|
|
|
|
|
open the one-time launch URL from the process log once, and the 30-day cookie it
|
|
|
|
|
exchanges covers the rewritten requests too.
|
|
|
|
|
**Removes the untrusted-origin restriction from DeepSeek Harness, and the
|
|
|
|
|
launch-token prompt with it.** Every browser window is treated as if it were
|
|
|
|
|
running on the harness machine itself: no declared trusted host, no per-plugin
|
|
|
|
|
origin fence, and no process token to paste into a phone — one interceptor
|
|
|
|
|
answers every request with the loopback authority, and the session check is off
|
|
|
|
|
by default.
|
|
|
|
|
|
|
|
|
|
A window that reaches the harness by a LAN address, a DNS name, or a tunnel
|
|
|
|
|
authenticates normally and then fails every mutating request with
|
|
|
|
|
`403 {"error":"untrusted origin"}`. Cards render, but Install, Update, toggle,
|
|
|
|
|
and "Update all" do nothing that the user can see. This plugin removes the
|
|
|
|
|
distinction between a local window and a remote one — with no declared trusted
|
|
|
|
|
host, no `--trusted-host`, and no per-plugin configuration. Removing the session
|
|
|
|
|
check as well (`disableAuth: true`) is available, but it is now opt-in.
|
|
|
|
|
host, no `--trusted-host`, and no per-plugin configuration. The launch-token
|
|
|
|
|
check is removed as well; `disableAuth: false` puts it back.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## ⚠️ Read this before enabling it
|
|
|
|
|
|
|
|
|
|
**This plugin is a deliberate and total removal of the harness's request-identity
|
|
|
|
|
fence. It is not a bug fix and it is not a workaround for one specific plugin.**
|
|
|
|
|
The session check is a separate matter: it stays unless you ask for it to go.
|
|
|
|
|
fence, and by default of its browser-session check too. It is not a bug fix and
|
|
|
|
|
it is not a workaround for one specific plugin.**
|
|
|
|
|
|
|
|
|
|
The harness fences requests by their `Host` and `Origin` headers for one
|
|
|
|
|
reason: those are the only headers a page on another origin cannot forge, so
|
|
|
|
@@ -30,47 +29,36 @@ they are what stops a web page you happen to visit from driving your agent API
|
|
|
|
|
while the harness is reachable. With this plugin enabled:
|
|
|
|
|
|
|
|
|
|
- **DNS rebinding** against the harness API is no longer defended.
|
|
|
|
|
- **A page on any other origin** that carries a session cookie can issue
|
|
|
|
|
mutating requests to the API.
|
|
|
|
|
- **A page on any other origin** can issue mutating requests to the API.
|
|
|
|
|
- Those requests can **run commands on your machine** through the agent.
|
|
|
|
|
|
|
|
|
|
The default posture keeps the browser-session check, so the additional exposure
|
|
|
|
|
is limited to a page holding a live session cookie. Set `disableAuth: true` and
|
|
|
|
|
there is no check either: **anyone who can reach the port has full control of
|
|
|
|
|
the agent, including code execution.** Reachability becomes the only access
|
|
|
|
|
control.
|
|
|
|
|
And with the default `disableAuth: true` there is no session check either:
|
|
|
|
|
**anyone who can reach the port has full control of the agent, including code
|
|
|
|
|
execution.** Reachability is the only access control.
|
|
|
|
|
|
|
|
|
|
That second posture is reasonable when the port is behind something that
|
|
|
|
|
authenticates on your behalf, or when it is bound to a network you fully
|
|
|
|
|
control. It is a poor posture for a `0.0.0.0` bind fronted by a public tunnel
|
|
|
|
|
with no authentication of its own. Decide accordingly, and re-read this section
|
|
|
|
|
if you change how the port is exposed.
|
|
|
|
|
That posture is reasonable when the port is behind something that authenticates
|
|
|
|
|
on your behalf, or when it is bound to a network you fully control. It is a poor
|
|
|
|
|
posture for a `0.0.0.0` bind fronted by a public tunnel with no authentication of
|
|
|
|
|
its own. Decide accordingly, and re-read this section if you change how the port
|
|
|
|
|
is exposed. Set `disableAuth: false` to restore the launch token as a second
|
|
|
|
|
line of defence.
|
|
|
|
|
|
|
|
|
|
### The launch token, and why the default changed
|
|
|
|
|
### The launch token, and the `disableAuth` switch
|
|
|
|
|
|
|
|
|
|
The Web UI prints a one-time launch token in the process log —
|
|
|
|
|
`dsh web: http://…/?token=…` — and from `0.2.1-alpha.4` the container entrypoint
|
|
|
|
|
writes that line into the Docker log as well. The token rotates on every
|
|
|
|
|
restart. Open the URL once on each origin you use, and the harness exchanges it
|
|
|
|
|
for a **30-day cookie** bound to the authority it was minted under; until then
|
|
|
|
|
every page answers `401`.
|
|
|
|
|
restart. With the check enabled, every new device or origin must open that URL
|
|
|
|
|
once; the harness exchanges the token for a **30-day cookie** bound to the
|
|
|
|
|
authority it was minted under, and until then every page answers `401`.
|
|
|
|
|
|
|
|
|
|
The exchange, the cookie, and the authority check are all present in
|
|
|
|
|
`0.2.1-alpha.2` and later; what changed is that the bypass (0.3.0 and the
|
|
|
|
|
`disableAuth: true` default it shipped) discarded all three. The default now
|
|
|
|
|
keeps them.
|
|
|
|
|
`0.4.0` made that check the default, on the argument that one exchange covers
|
|
|
|
|
every domain because the plugin rewrites `Host` to the same loopback authority
|
|
|
|
|
the cookie is bound to. In practice it still means producing a token on each new
|
|
|
|
|
device, which is the friction this plugin exists to remove — `0.5.0` therefore
|
|
|
|
|
restores `disableAuth: true` as the default. Set `disableAuth: false` to get the
|
|
|
|
|
check, and the cookie, back.
|
|
|
|
|
|
|
|
|
|
Because this plugin rewrites `Host` to the loopback authority before any route
|
|
|
|
|
looks at it, the cookie is minted for the loopback authority too, and the
|
|
|
|
|
rewrite presents that same authority on every request afterwards. One exchange
|
|
|
|
|
therefore covers every entry domain, and the token never has to be pasted into
|
|
|
|
|
the address bar by hand again. That is why the plugin now keeps the check by
|
|
|
|
|
default instead of removing it: the token flow is the access control that makes
|
|
|
|
|
an internet-facing port defensible.
|
|
|
|
|
|
|
|
|
|
If you want the errors gone but not the exposure, leave `disableAuth` at its
|
|
|
|
|
default `false`. Only set it `true` when something else already authenticates
|
|
|
|
|
callers, and accept that the harness then has no second line of defence.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
@@ -184,7 +172,7 @@ Override any key in your profile's `cordis.patch.yml`:
|
|
|
|
|
- id: trustworthy
|
|
|
|
|
config:
|
|
|
|
|
enabled: true
|
|
|
|
|
disableAuth: false
|
|
|
|
|
disableAuth: true
|
|
|
|
|
forceClientLoopback: true
|
|
|
|
|
grantPairing: true
|
|
|
|
|
provideWebRuntime: true
|
|
|
|
@@ -197,7 +185,7 @@ Override any key in your profile's `cordis.patch.yml`:
|
|
|
|
|
| Key | Default | Effect |
|
|
|
|
|
|---|---|---|
|
|
|
|
|
| `enabled` | `true` | Master switch. `false` loads the plugin and changes nothing. |
|
|
|
|
|
| `disableAuth` | `false` | **Also** removes the browser-session check, so no launch token is needed at all. The default keeps it: exchange the token once and the 30-day cookie covers every rewritten request. `true` is the one that removes access control. |
|
|
|
|
|
| `disableAuth` | `true` | **Also** removes the browser-session check, so no launch token and no per-device pairing are needed. This is the default: reachability is the only access control. Set `false` to keep the token — exchange it once and the 30-day cookie covers every rewritten request. |
|
|
|
|
|
| `forceClientLoopback` | `true` | Grants the browser-side `isLoopback` flag so the settings and model surfaces act as they do on the host. |
|
|
|
|
|
| `grantPairing` | `true` | Provides the paired-device service that plugins like the Skill Center accept instead of a loopback peer. See below. |
|
|
|
|
|
| `provideWebRuntime` | `true` | Restores the `webRuntime` trust snapshot the harness stopped publishing in 0.2.1-alpha.2, so plugins that inject it (the sidebar) mount instead of answering 405. See below. |
|
|
|
|
@@ -211,17 +199,19 @@ Override any key in your profile's `cordis.patch.yml`:
|
|
|
|
|
Every key above only answers fences. `disableAuth` is the one that changes who
|
|
|
|
|
may talk to the harness at all.
|
|
|
|
|
|
|
|
|
|
- **`false` (default).** The request-identity fence is rewritten away, and the
|
|
|
|
|
- **`true` (default).** The session check is replaced too: `connection.admit`
|
|
|
|
|
returns the owner's own peer for every request, `authorizeIndex` admits the
|
|
|
|
|
index, and `authenticatedUrl` stops appending the token to the printed URL. No
|
|
|
|
|
token, no cookie, no login, nothing to type on a phone — and anyone who reaches
|
|
|
|
|
the port controls the agent. Use it behind an authenticating proxy, on a
|
|
|
|
|
network that is already trusted, or when the port is otherwise unreachable from
|
|
|
|
|
untrusted networks.
|
|
|
|
|
- **`false`.** The request-identity fence is still rewritten away, and the
|
|
|
|
|
browser-session check is left exactly as the harness built it. A new browser
|
|
|
|
|
shows `401` until the launch URL from the process log is opened once; that
|
|
|
|
|
exchange sets the 30-day cookie, and every later request — including the ones
|
|
|
|
|
this plugin rewrote the `Host` on — carries it. This is the posture to use on a
|
|
|
|
|
port that faces anything you do not fully control.
|
|
|
|
|
- **`true`.** The session check is replaced too: `connection.admit` returns the
|
|
|
|
|
owner's own peer for every request, `authorizeIndex` admits the index, and
|
|
|
|
|
`authenticatedUrl` stops appending the token to the printed URL. No token, no
|
|
|
|
|
cookie, no login — anyone who reaches the port controls the agent. Use it only
|
|
|
|
|
behind an authenticating proxy or on a network that is already trusted.
|
|
|
|
|
this plugin rewrote the `Host` on — carries it. Each new device still has to be
|
|
|
|
|
given the token, which is why it is not the default.
|
|
|
|
|
|
|
|
|
|
The implementation replaces `admit` because that is where every shape of the
|
|
|
|
|
gate ends up: releases up to `0.2.1-alpha.2` call `requestRejection` from inside
|
|
|
|
@@ -391,7 +381,8 @@ so a harness release that renames any of them will break it:
|
|
|
|
|
| the `webserver/index-inject` channel and the `ownsHost` marker | webserver + connection client |
|
|
|
|
|
|
|
|
|
|
The only one used conditionally is `connection.admit`: the bypass path replaces
|
|
|
|
|
it when `disableAuth: true`. The default posture does not touch it at all.
|
|
|
|
|
it when `disableAuth: true` (the default). With `disableAuth: false` the plugin
|
|
|
|
|
does not touch it at all.
|
|
|
|
|
|
|
|
|
|
Every one of those is probed before use. If a release reshapes the surface, the
|
|
|
|
|
plugin logs which capability it lost and carries on with the rest, rather than
|
|
|
|
@@ -417,9 +408,10 @@ now replaces `admit` itself and returns the owner's peer (`browserPeer` on this
|
|
|
|
|
release, `operator` before alpha.3); measured on a sandboxed rc.1 with a
|
|
|
|
|
non-loopback `Host`, the upgrade answered `101` with no cookie.
|
|
|
|
|
|
|
|
|
|
**The session check is now exercised by the default posture.** The plugin
|
|
|
|
|
rewrites `Host`/`Origin` to `127.0.0.1:<port>`, which is also the authority the
|
|
|
|
|
harness mints the session cookie for. Measured on the same sandbox, in order:
|
|
|
|
|
**The session check, when enabled, survives the rewrite.** The plugin rewrites
|
|
|
|
|
`Host`/`Origin` to `127.0.0.1:<port>`, which is also the authority the harness
|
|
|
|
|
mints the session cookie for. Measured on a sandbox, with `disableAuth: false`,
|
|
|
|
|
in order:
|
|
|
|
|
|
|
|
|
|
| Request (`Host: ai.milesward.dev`) | Result |
|
|
|
|
|
|---|---|
|
|
|
|
@@ -431,8 +423,9 @@ harness mints the session cookie for. Measured on the same sandbox, in order:
|
|
|
|
|
The cookie payload's `authority` is `127.0.0.1:<port>` — the rewritten one — so
|
|
|
|
|
the browser keeps the cookie for the public origin it opened, and the plugin's
|
|
|
|
|
rewrite presents the matching authority on every request afterwards. This is
|
|
|
|
|
what lets the 30-day session survive a Host rewrite, and it is why the default
|
|
|
|
|
no longer needs to remove the check.
|
|
|
|
|
what lets the 30-day session survive a Host rewrite, and it is why
|
|
|
|
|
`disableAuth: false` is a viable posture for anyone who would rather keep the
|
|
|
|
|
check.
|
|
|
|
|
|
|
|
|
|
The token rotates whenever the process restarts (`dsh web: …?token=…` in the
|
|
|
|
|
process log; the container entrypoint prints that line into the Docker log from
|
|
|
|
@@ -565,14 +558,14 @@ Three guards follow from that, and each has a test:
|
|
|
|
|
`isTrustedApiRequest`, before the `Origin` check and regardless of the
|
|
|
|
|
trusted-host list. No header rewrite can defeat it, and this plugin does not
|
|
|
|
|
try. It only guards requests a browser itself marks as cross-site, so it has
|
|
|
|
|
no effect on normal navigation or on the WebSocket. Set `disableAuth: true`
|
|
|
|
|
and the fence is skipped entirely.
|
|
|
|
|
no effect on normal navigation or on the WebSocket. With `disableAuth: true`
|
|
|
|
|
(the default) the gate that calls it is replaced, so it does not run either.
|
|
|
|
|
- Rewriting `Host` decides the **cookie authority** the harness derives from it
|
|
|
|
|
(`dsh-auth-<sha256(authority)>`). The launch token is exchanged for the
|
|
|
|
|
rewritten authority, so the resulting 30-day cookie matches every request the
|
|
|
|
|
plugin intercepts — that is what makes the default posture work. The
|
|
|
|
|
authority is the loopback bind (`127.0.0.1:<port>`), so changing `--port`
|
|
|
|
|
mints a new one and requires one new exchange. A session cookie you already
|
|
|
|
|
(`dsh-auth-<sha256(authority)>`). With `disableAuth: false` the launch token is
|
|
|
|
|
exchanged for the rewritten authority, so the resulting 30-day cookie matches
|
|
|
|
|
every request the plugin intercepts. The authority is the loopback bind
|
|
|
|
|
(`127.0.0.1:<port>`), so changing `--port` mints a new one and requires one new
|
|
|
|
|
exchange. A session cookie you already
|
|
|
|
|
hold under the public hostname is not reused; expect one re-login on enabling
|
|
|
|
|
the plugin.
|
|
|
|
|
- `Referer` is read by no layer of the harness, core or plugin. It is rewritten
|
|
|
|
@@ -683,6 +676,7 @@ remote client and always looks like it passes.
|
|
|
|
|
|
|
|
|
|
| Version | What changed |
|
|
|
|
|
|---|---|
|
|
|
|
|
| **0.5.0** | Restores `disableAuth: true` as the default: no launch token and no per-device pairing, which is what the plugin exists to remove. `0.4.0`'s keep-the-check posture remains available with `disableAuth: false`. No code change beyond the default and the documentation; validated on DSH `0.2.1-rc.2`, where the bypass still replaces `connection.admit`. |
|
|
|
|
|
| **0.4.0** | Keeps the browser-session check by default (`disableAuth: false`) and makes the 0.2.1-alpha.3 launch-token → 30-day-cookie flow survive the Host rewrite, including on a remote origin; the no-check posture moves to `disableAuth: true` and now replaces `connection.admit` as well, because the gate moved there. Declares `engines.dsh` (the 0.2.1-alpha.4 recommendation) and is validated on 0.2.1-alpha.4 and 0.2.1-rc.1. |
|
|
|
|
|
| **0.3.0** | Provides the `webRuntime` trust snapshot that `0.2.1-alpha.2` stopped publishing (`lib/web-runtime.js`, `provideWebRuntime`), so a plugin that injects it — the sidebar among them — mounts instead of answering 405 on every request. The value is read live from `connection.trustedHosts` plus the listener's bind address, and an existing provider is left alone. |
|
|
|
|
|
| 0.2.1 | Documentation and compatibility only — **no code change from 0.2.0**. Records the 0.2.1-alpha.1 validation above, and how to install so the market can update it. |
|
|
|
|
|