Correct cluster-resolution comments to match actual token flow · Entire
Correct cluster-resolution comments to match actual token flow
ea605a6→main·
toothbrush·1mo ago·2 files·+31 added/-20 removed
The resolver comments claimed not persisting a cluster binding prevented a "drive-by clone" from minting identity-bearing JWTs. That's false: the login JWT only ever goes to the resolved context's CoreURL, never to the cluster host, and the cluster host receives only a repo-scoped, audience-pinned token. Reframe not-persisting as the correctness choice it is (immediacy of auth use / context deletion, no host-controlled host->core mapping lingering), and align the auth-contexts audit comment.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
Sessions
ee378db97d3eView transcript
Changes
2
cmd/entire/cli
Mauth_context.go+6/-4
internal/entireclient/clusterdiscovery
Mresolve.go+25/-16
106 unmodified lines
// printClusterBindings lists any cluster_contexts bindings so users can
// audit which hosts auto-authenticate git operations with a stored
// context. A binding means future ops against that host mint
// identity-bearing JWTs without re-checking the host's /.well-known, so an
// unrecognised entry is worth revoking with `entire auth unbind`.
// audit which hosts skip /.well-known discovery and resolve straight to a
// stored context. A binding does not hand that host your login JWT — that
// only ever goes to the bound context's core, and the host receives a
// repo-scoped, audience-pinned token (see repocreds). What a binding pins
// is which core authenticates that host, so an entry you don't recognise
// is still worth revoking with `entire auth unbind`.
func printClusterBindings(w io.Writer) error {
bindings, err := auth.ClusterBindings()
if err != nil {
Mcmd/entire/cli/auth_context.go+6/-4
12 unmodified lines
// Resolution order:
//
// 1. Explicit `cluster_contexts[clusterHost]` binding in contexts.json
// pointing at an existing context — used as-is. Bindings are only
// created by deliberate action (`entire-core context bind`, or a
// teammate's tooling); this CLI never writes one implicitly.
// pointing at an existing context — used as-is. Bindings are
// created only by deliberate action; this helper never writes one.
//
// 2. Discovery via /.well-known/entire-cluster.json on the cluster
// itself, matched against existing local contexts by the
// advertised core_urls. The first advertised URL with a local
// context wins. This match is used for the current invocation only
// — we deliberately do NOT persist a cluster→context binding.
// Persisting it would let a single drive-by clone of an
// attacker-controlled host (e.g. via a malicious submodule whose
// /.well-known names the victim's real core) establish a durable,
// silent channel that re-mints identity-bearing JWTs on every
// future fetch. Re-evaluating the live /.well-known each time keeps
// the trust decision fresh and revocable.
// context wins. This match applies to the current invocation only:
// this helper does not persist a cluster→context binding.
//
// Resolving fresh on each call is a correctness choice, not a
// credential-safety one. The login JWT is only ever sent to the
// resolved context's CoreURL — a core the user already holds a local
// context for — never to clusterHost; the token clusterHost receives
// is repo-scoped and audience-pinned to clusterHost itself (see
// repocreds.exchange). A hostile clusterHost can at most steer us
// toward a core we already trust; it cannot introduce a new core or
// capture an identity-bearing token. What resolving fresh buys is
// immediacy: `entire auth use` and context deletion take effect on
// the next fetch with no stale binding to unbind, and no
// host→core mapping derived from a host-controlled /.well-known
// lingers in contexts.json.
//
// 3. No local context matches any advertised URL — return a
// fatal-ready error with the login hint listing the cluster's
// advertised issuers.
//
// We deliberately do NOT fall back to current_context for an unknown
// cluster host. The old fallback would silently use a staging
// context against a prod (entire.io) cluster — which
// then 400s as "unknown cluster_host" because the cluster's own
// registry doesn't know the host. The cluster's /.well-known is the
// authoritative answer to "which env am I in", so we ask it.
// cluster host. current_context can point at a different environment
// than clusterHost (e.g. a staging context against a prod cluster);
// the cluster then rejects the exchanged token with "unknown cluster_host"
// because its own registry doesn't know that core. The cluster's
// /.well-known is the authoritative answer to "which env am I in", so we
// ask it rather than guessing from the active context.
//
// debugf is optional; nil suppresses debug output.
func ResolveContextForCluster(ctx context.Context, configDir, clusterHost string, httpClient *http.Client, debugf DebugFunc) (*contexts.Context, error) {