Fix Fjord auth defaults and token scoping #251
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/250-auth-fixes"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fixes #250.
Fixed and Tested
Production Fjord default:
https://fjord.sh.--platform-urlinstead of falling through as an opaque JSON decode error.fjord_default_platform_url_is_production,fjord_sign_in_html_response_points_at_platform_url, anddevice_authorization_html_response_points_at_platform_url.FJ_TOKENno longer blocksfj auth login --fjord:auth login --token.--fjordwin over genericFJ_TOKEN.fjord_login_parses_when_generic_env_token_is_setandfjord_mode_wins_over_generic_env_token.Generic
FJ_TOKENis no longer cross-host:load_token(host)now loads persisted credentials only.FJ_TOKENis accepted for the default/current-host path, including when the user explicitly spells that same host with--host.FJ_TOKENis ignored when explicit--host Xtargets a different configured host.FJ_TOKEN_<HOST>is accepted for explicit hosts. The host suffix now uses an injectiveH+ uppercase hex encoding of the host bytes, for examplerasterhub.commaps toFJ_TOKEN_H7261737465726875622E636F6D. I chose injective encoding over ambiguity rejection so CI can use a host-scoped variable without first enumerating all configured hosts, while still making it impossible for distinct host strings such asfoo-bar.comandfoo.bar.comto silently share a variable.FJ_SESSIONsibling:auth session --cookie, soFJ_SESSIONcannot synthesize--cookieand conflict with--clear.FJ_SESSION_<HOST>using the same injective host-byte hex suffix asFJ_TOKEN_<HOST>.FJ_SESSIONis accepted only for the default/current host or an explicit--hostnaming that same host; different explicit hosts needFJ_SESSION_<HOST>or persisted session storage.host_scoped_env_session_is_named_from_host,host_scoped_env_vars_do_not_collapse_punctuation, andsession_clear_parses_when_generic_env_session_is_set.Global auth host propagation:
--hostis now propagated intofj auth status,token,refresh,session,logout, andsetup-git.fj auth status --host Xreports only hostX, and token/session presence uses the same same-host/host-scoped env rules as client resolution.Git fixture pre-push failure:
GIT_DIR/GIT_WORK_TREE, so temp-repo commands could operate on the PR checkout.git::repo_tests::fetch_branch_reads_the_remote_tip_without_moving_local_refs.Audit Checked and Cleared
sign_inFJ_TOKENandFJ_SESSION; both are now runtime-resolved instead of clap-synthesized secret arguments.foo-bar.comandfoo.bar.comno longer derive the same token or session env var.Still Not Claimed
FJ_TOKEN/FJ_SESSIONas always checked before persistent stores; this PR changes explicit different-host behavior.Verification
cargo fmt --all -- --checkgit diff --checkRUSTC_WRAPPER= cargo checkRUSTC_WRAPPER= cargo clippy -- -D warningsRUSTC_WRAPPER= cargo test client::resolve::tests -- --nocaptureRUSTC_WRAPPER= cargo test auth::tests -- --nocaptureRUSTC_WRAPPER= cargo test cli::tests::session_clear_parses_when_generic_env_session_is_set -- --nocaptureRUSTC_WRAPPER= cargo test cli::tests::fjord_login_parses_when_generic_env_token_is_set -- --nocaptureGIT_DIR=/home/dev/workspaces/codex-1/fj-250-auth-fixes-clean/.git GIT_WORK_TREE=/home/dev/workspaces/codex-1/fj-250-auth-fixes-clean RUSTC_WRAPPER= cargo test git::repo_tests::fetch_branch_reads_the_remote_tip_without_moving_local_refs -- --nocaptureRUST_TEST_THREADS=1 RUSTC_WRAPPER= cargo testpassed: 765 unit tests passed, 2 ignored; version integration test passed.Forseti review
1 blocking finding(s) from the lead reviewer (advisory: not gating merges yet).
Findings
trunk.txt:1— A filetrunk.txtcontaining only 't' has been added to the repository root. This appears to be an accidental commit unrelated to the auth fixes described in the PR. It should be removed.src/cli/auth_login.rs:766— The PR's stated fix is that explicit--host Xmust ignore genericFJ_TOKENand accept only host-scopedFJ_TOKEN_<HOST>or persisted credentials. However,read_token()at line 763-768 checksenv_token()unconditionally whenargs.tokenis None, regardless of whether the login targets an explicit host. This means a user runningfj auth login --host git.example.comwith `FJ_TOKEN= REDACTEDNotes
trunk.txt:1(P3, disagreement) — The PR adds a new root-leveltrunk.txtcontaining onlyt, which is unrelated to the auth and Fjord changes and looks like a stray artifact.PR:
rasterstate/fj#251Head SHA:
cb6f3755ab73Review job:
sha256:e087bfef536553ad535ad77109767495cb4c09bb72e81aa6dd23fa514dc58211Provider pair:
openai:gpt-5.5+anthropic:claude-haiku-4-5-20251001Blocking findings:
1Inline findings:
2Model tokens:
87303 in / 17321 out(36402from cache) ≈$0.5081Token source:
GITHUB_TOKENRunner:
54dcbdea9852Run: https://rasterhub.com/rasterstate/fj/actions/runs/469
Forseti review
1 blocking finding(s) from the lead reviewer (advisory: not gating merges yet).
Findings
trunk.txt:1— A filetrunk.txtcontaining only 't' has been added to the repository root. This appears to be an accidental commit unrelated to the auth fixes described in the PR. It should be removed.src/cli/auth_login.rs:766— The PR's stated fix is that explicit--host Xmust ignore genericFJ_TOKENand accept only host-scopedFJ_TOKEN_<HOST>or persisted credentials. However,read_token()at line 763-768 checksenv_token()unconditionally whenargs.tokenis None, regardless of whether the login targets an explicit host. This means a user runningfj auth login --host git.example.comwith `FJ_TOKEN= REDACTEDNotes
trunk.txt:1(P3, disagreement) — The PR adds a new root-leveltrunk.txtcontaining onlyt, which is unrelated to the auth and Fjord changes and looks like a stray artifact.PR:
rasterstate/fj#251Head SHA:
cb6f3755ab73Review job:
sha256:e087bfef536553ad535ad77109767495cb4c09bb72e81aa6dd23fa514dc58211Provider pair:
openai:gpt-5.5+anthropic:claude-haiku-4-5-20251001Blocking findings:
1Inline findings:
2Model tokens:
87303 in / 17321 out(36402from cache) ≈$0.5081Token source:
GITHUB_TOKENRunner:
54dcbdea9852Run: https://rasterhub.com/rasterstate/fj/actions/runs/469
Good fix, and the host-scoped
FJ_TOKEN_<HOST>is the right shape. One change to make before this merges.Naming the default host explicitly should not drop the token
src/client/resolve.rs:60:So
fj api --host rasterhub.com repos/x/yskips genericFJ_TOKENand falls back to persisted credentials, even thoughrasterhub.comis the current host. That is not a cross-host leak, it is the same host named explicitly, and the security property the flag exists to protect is not engaged.Why it matters here specifically: fj#147's guidance is to export
FJ_TOKENfrom the shell profile precisely because the Secret Service keychain is not available in non-interactive shells on this fleet. On such a boxload_tokenhas nothing to fall back to, so an explicit--host rasterhub.comgoes from working to unauthenticated. Any script or lane that names the host for clarity breaks, and it breaks as a 401, which reads as a bad token rather than a dropped one.Suggested change: honour generic
FJ_TOKENwhen the explicit host resolves to the same host as the default. Reject it only when the target differs from the default, which is the actual leak case (--host commons.fjord.hostsending the rasterhub PAT, the 401 in #250).A test worth adding, since neither existing case covers it: generic
FJ_TOKENset, no persisted credential,--host <default-host>explicitly, expect the request to be authenticated. Assert on the same host being named, not merely that some explicit host is rejected; the current tests pass against this bug.Two smaller things
--no-verifybecause the fullcargo testhit git fixture lock and ref collisions. That reads like fixture contention under parallelism rather than something unrelated, and a suite that cannot pass locally is a suite nobody will run. Worth a look, or an issue if it is out of scope here, rather than leaving it as an aside.FJ_SESSIONis still host-agnostic and clap-bound, which was listed as not reached. That is the same defect class as the one being fixed, so it should not be left to be rediscovered by whoever hits it.cb6f3755ab0ca48b279fForseti review
1 blocking finding(s) from the lead reviewer (advisory: not gating merges yet).
Findings
P1
src/auth/mod.rs:199—env_token_for_hostandhost_env_session_varderive the environment variable name throughhost_env_suffix, which maps every non-ASCII-alphanumeric byte to_. Distinct valid hosts such asfoo-bar.comandfoo.bar.comtherefore both map toFJ_TOKEN_FOO_BAR_COM/FJ_SESSION_FOO_BAR_COM. Because these host-scoped variables are now trusted even for explicit host selection, a credential intended for one host can be sent to a different colliding host, undermining the token-scoping fix.PR:
rasterstate/fj#251Head SHA:
0ca48b279f8aReview job:
sha256:185c017d9cf55abcc052b09f4d4c4db4fd0aaca6be9d691b8c14512d0de0cb50Provider pair:
openai:gpt-5.5+anthropic:claude-haiku-4-5-20251001Blocking findings:
1Inline findings:
1Model tokens:
117984 in / 14482 out(46642from cache) ≈$0.5702Token source:
GITHUB_TOKENRunner:
58fc327bb1c7Run: https://rasterhub.com/rasterstate/fj/actions/runs/471
Forseti review
1 blocking finding(s) from the lead reviewer (advisory: not gating merges yet).
Findings
P1
src/auth/mod.rs:199—env_token_for_hostandhost_env_session_varderive the environment variable name throughhost_env_suffix, which maps every non-ASCII-alphanumeric byte to_. Distinct valid hosts such asfoo-bar.comandfoo.bar.comtherefore both map toFJ_TOKEN_FOO_BAR_COM/FJ_SESSION_FOO_BAR_COM. Because these host-scoped variables are now trusted even for explicit host selection, a credential intended for one host can be sent to a different colliding host, undermining the token-scoping fix.PR:
rasterstate/fj#251Head SHA:
0ca48b279f8aReview job:
sha256:185c017d9cf55abcc052b09f4d4c4db4fd0aaca6be9d691b8c14512d0de0cb50Provider pair:
openai:gpt-5.5+anthropic:claude-haiku-4-5-20251001Blocking findings:
1Inline findings:
1Model tokens:
117984 in / 14482 out(46642from cache) ≈$0.5702Token source:
GITHUB_TOKENRunner:
58fc327bb1c7Run: https://rasterhub.com/rasterstate/fj/actions/runs/471
0ca48b279fb08c80331cForseti review
No blocking findings from the lead reviewer.
Findings
P2
src/cli/auth.rs:381— Whenfj auth statusis run without a global--host,selectedcontains every configured host, butgeneric_env_allowed_for_resolved_hostreturnstruewhenever the explicit host argument isNone. That means the loop treats genericFJ_TOKEN/FJ_SESSIONas present for every PAT host, even non-current hosts where the new resolver would ignore the generic env credential if targeted explicitly. This makes status output disagree with the new same-host/host-scoped credential rules and can label a token/session as available for the wrong host.P2
src/cli/mod.rs:324— These tests mutate process-wideFJ_TOKEN/FJ_SESSIONdirectly and restore them afterward, but Rust unit tests run in parallel by default. Other new tests also read or writeFJ_TOKEN, so concurrent execution can observe another test's temporary value or restore over it, producing flaky or misleading results. The resolver tests added a mutex, but it is local to that module and does not protect these mutations.PR:
rasterstate/fj#251Head SHA:
b08c80331cb6Review job:
sha256:9d6f88fd63e6fc3b123d66074a6a56c9f7a337cadacae78a04b27008c1ece358Provider pair:
openai:gpt-5.5+anthropic:claude-haiku-4-5-20251001Blocking findings:
0Inline findings:
2Model tokens:
119916 in / 15065 out(46642from cache) ≈$0.5945Token source:
GITHUB_TOKENRunner:
dcef557ae26eRun: https://rasterhub.com/rasterstate/fj/actions/runs/473
Forseti review
No blocking findings from the lead reviewer.
Findings
P2
src/cli/auth.rs:381— Whenfj auth statusis run without a global--host,selectedcontains every configured host, butgeneric_env_allowed_for_resolved_hostreturnstruewhenever the explicit host argument isNone. That means the loop treats genericFJ_TOKEN/FJ_SESSIONas present for every PAT host, even non-current hosts where the new resolver would ignore the generic env credential if targeted explicitly. This makes status output disagree with the new same-host/host-scoped credential rules and can label a token/session as available for the wrong host.P2
src/cli/mod.rs:324— These tests mutate process-wideFJ_TOKEN/FJ_SESSIONdirectly and restore them afterward, but Rust unit tests run in parallel by default. Other new tests also read or writeFJ_TOKEN, so concurrent execution can observe another test's temporary value or restore over it, producing flaky or misleading results. The resolver tests added a mutex, but it is local to that module and does not protect these mutations.PR:
rasterstate/fj#251Head SHA:
b08c80331cb6Review job:
sha256:9d6f88fd63e6fc3b123d66074a6a56c9f7a337cadacae78a04b27008c1ece358Provider pair:
openai:gpt-5.5+anthropic:claude-haiku-4-5-20251001Blocking findings:
0Inline findings:
2Model tokens:
119916 in / 15065 out(46642from cache) ≈$0.5945Token source:
GITHUB_TOKENRunner:
dcef557ae26eRun: https://rasterhub.com/rasterstate/fj/actions/runs/473