fix(remote): stop persisting the daemon bearer token, and authenticate forced-reconnect release correctly - #1648
Conversation
|
Size Report
Startup median (7 runs, lower is better):
Top changed chunks:
|
|
Reviewed exact head |
f0586db to
d79bf78
Compare
|
Fixed at Root cause confirmed:
Red run for the new regression (env-merging read restored, tokenless profile A, differing endpoints, B supplied through That is the leak itself rather than a proxy for it: a release request was issued, carrying the env-sourced new token against the old endpoint. After the fix no request is issued and the unreleasable-lease notice surfaces instead. You were also right about why the existing coverage missed it — the old unreleasable-old-token test supplied B through flags with an empty env, so it never took the production environment path. The new fourth test is the only one that does; the other three were re-checked and still pin what they claim (test 1 uses a file-declared token and never depended on the merge; tests 2 and 3 involve no env var).
|
|
Re-reviewed exact head P1 — the previous config file is trusted after it may have changed identity. Validate the old file’s saved hash/endpoint provenance before trusting its token (or use an equivalent endpoint-bound source), and add a same-path A→B regression proving no release request sends B to A. The current test uses distinct immutable old/new paths, so it cannot catch this. Fallow and FreeRange both failed before checkout on GitHub action-download 500/503 outages; all other substantive checks are green. |
|
Re-reviewed exact head 5968975. P1 remains: an unchanged remote-config file is trusted by hash even when explicit CLI flags overrode its endpoint and token for the previous connection. Supported repro: profile P declares endpoint B and token B; initial connect uses CLI endpoint A and token A, so state records A; later connect --force re-reads unchanged P, hash-matches it, classifies B as A own credential, and sends token B to endpoint A during lease release. resolveConnectProviderProfile explicitly merges profile flags first and CLI flags second, so hash equality proves file identity, not that its token was the effective credential for the stored endpoint. Require normalized declared endpoint equality even on the hash-match branch, fail closed when the profile declares no endpoint, and add this unchanged-profile CLI-override regression asserting no release request sends B to A. No device evidence applies; this is remote auth and state logic. No readiness label. |
|
Fixed at Root cause, confirmed. Rule 1's claim is "this token provably belongs to endpoint A because A's own config file declared it." Red run for your exact repro (one path, edited in place between connect and Token B, addressed to old.example. That is the leak itself, not a proxy for it. With the fix no release request is issued at all and the unreleasable-lease notice surfaces instead. The fix, and the one judgement call in it. The previous file must now still vouch for the previous endpoint, by either of two independent facts:
I want to flag (2) explicitly rather than bury it, because hash-only would have been the simpler rule. The common benign edit is a rotated credential in an otherwise unchanged profile — still endpoint A's own credential, and refusing it would orphan a lease on every key rotation. So endpoint equality, not the fact of an edit, is what separates rotation from re-pointing. If you'd rather be strict and accept the orphaned-lease warning on any edit, that's a one-line change — say so and I'll make it hash-only. Endpoint comparison runs both sides through Two regressions, not one: the same-path A→B case above, and a rotated-credential-same-endpoint case that must stay green — because an edit-detecting rule that breaks rotation would look correct and quietly orphan leases in production.
Generated by Claude Code |
|
Re-review: current head is still 5968975, so the latest P1 remains. The follow-up above addresses the earlier re-pointed-file repro, not the unchanged-profile CLI-override repro. In previousConfigStillSpeaksForPreviousEndpoint, a matching file hash returns true before declaredDaemonBaseUrl is compared with the stored previous daemon base URL. Supported failure: profile P unchanged declares endpoint B and token B; the initial connection uses explicit CLI endpoint A and token A, so state records endpoint A plus P's hash; connect --force later reads token B from P, the hash branch approves it, and releaseRemoteConnectionLease sends B to endpoint A. Hash equality proves file identity, not that the file supplied the effective endpoint or credential after CLI precedence. Require the normalized declared endpoint to equal the stored previous endpoint even on the hash-match branch, fail closed when no endpoint is declared, and add this exact unchanged-profile CLI-override regression. No ready label; CodeQL is queued and other visible checks are passing. |
ADR 0007 requires generated connection profiles to strip daemon and Metro bearer tokens; only the Metro half was honored. `connect` was writing the daemon bearer token into the 0600 connection-state file, and every later command read it back out. Stop writing `authToken` into `RemoteConnectionState['daemon']` and resolve it at each reader from the existing flag -> environment (AGENT_DEVICE_DAEMON_AUTH_TOKEN) -> remote-config-profile chain instead, matching src/cli/auth-session.ts's precedence. Behavior change: a user who ran `connect --daemon-auth-token <value>` and relied on later commands picking the token back up from the state file will now get an auth failure. They must export AGENT_DEVICE_DAEMON_AUTH_TOKEN, set daemonAuthToken in their remote config, or pass --daemon-auth-token on each command. website/docs/docs/remote-proxy.md is updated to show the supported env-var workflow.
…vious endpoint's own credential connect --force released the previous connection's lease using the new connection's ambient daemonAuthToken instead of the previous endpoint's own credential, and swallowed the resulting auth failure — silently orphaning the old lease when replacing a connection with a differently-authenticated one. Resolve the release token from the previous connection's own remote-config profile first, fall back to the ambient token only when the two connections share the same daemon endpoint, and otherwise skip the release and surface an actionable notice (tenant, run id, lease id, endpoint) through the existing connect notice channel instead of hiding the failure.
…e's own token resolvePreviousOwnDaemonAuthToken read the previous connection's profile through resolveRemoteConfigProfile, which folds AGENT_DEVICE_DAEMON_AUTH_TOKEN (and other env defaults) into the result. When the previous config file declared no token and the new connection's credential came from that same global env var, it was misclassified as belonging to the previous endpoint and sent there on forced-reconnect release — recreating the credential leak the prior fix was meant to close, just via env instead of --daemon-auth-token. Read the previous profile with the new readRemoteConfigFile (a provenance- preserving, file-only load with no ambient env/CLI merging), so only a token the previous config file itself declares can satisfy rule 1. Rules 2 and 3 are unchanged.
…point Rule 1 reads the previous connection's own config file to recover a credential that provably belongs to the previous endpoint. It re-read `previous.remoteConfigPath` and trusted whatever token that file holds *now* — but a config path is routinely reused, so "connect to A from ./remote.json, re-point ./remote.json at B, connect --force" classified B's token as A's own and sent it to A during lease release. Same cross-endpoint leak the env-merge fix closed, arriving through the file instead of the environment. The file must now still vouch for the previous endpoint, by either of two independent facts: its bytes still hash to the `remoteConfigHash` recorded at connect time (so it is literally the declaration that stood up the previous connection), or — if it changed — it still declares the same daemon base URL. The second is what keeps an ordinary credential rotation releasing its lease instead of orphaning one; endpoint equality, not the fact of an edit, is what separates rotation from re-pointing. Endpoint comparison runs both sides through `buildRemoteConnectionDaemonState`, the same normalizer that produced the stored `daemon.baseUrl`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Rva4YGtSCAKJqH5PbpcCU
9493ee0 to
8fca921
Compare
|
Reconciled the remaining endpoint-provenance finding at exact head Red proof: with an unchanged profile declaring endpoint B/token B and stored previous endpoint A created through a CLI override, the pre-fix path attempted A's lease release using B's token. A matching file hash proved only unchanged bytes, not that the profile endpoint/token were effective for the stored connection. The fix removes the hash-only shortcut: a previous profile token is eligible only when its normalized declared endpoint equals the stored previous endpoint. Same-endpoint credential rotation remains supported; missing or different endpoint provenance fails closed without sending the credential, and reconnect still surfaces the existing actionable notice. Validation after rebasing onto |
Summary
Stop persisting the remote daemon bearer token in connection state, as required by ADR 0007, and make forced reconnect cleanup authenticate only when the credential is demonstrably owned by the endpoint being released.
The forced reconnect path releases the previous lease. It now:
An unchanged profile hash is not sufficient provenance by itself. CLI flags may have overridden the profile endpoint during the previous connection, so the profile's token cannot be sent to that stored endpoint unless the profile also declares that endpoint. This preserves benign same-endpoint token rotation while failing closed across endpoints.
The bearer-token persistence removal is intentionally breaking: later commands must resolve the credential from
AGENT_DEVICE_DAEMON_AUTH_TOKEN,daemonAuthTokenin the remote config, or--daemon-auth-token. The remote-proxy documentation is updated accordingly.Validation
The final regression was proven red before the fix: an unchanged profile declaring endpoint B was associated with stored endpoint A after a CLI override, and
connect --forcesent B's token to A. The production fix removes the hash-only shortcut and requires normalized declared-endpoint equality.pnpm check:affected --run: all runnable checks passed after rebasing ontoorigin/main.This path is remote connection state/authentication logic, not device-facing, so simulator or physical-device evidence does not apply.