# git-remote-entire: ENTIRE_TOKEN env override for CI / workload identity

`8d53bc4`→[main](/content/gh/entireio/cli/commits/main/index.html)·

toothbrush·1mo ago·3 files·+176 added/-18 removed

Let ENTIRE_TOKEN=<jwt> bypass contexts.json and the keyring entirely so
CI and workload-identity runners clone entire:// URLs without an
interactive login.

When set, derive the home-region core URL from the token's URL-shaped aud
claim (login/sa-session JWTs carry aud=<home-region URL>, which is what
STS routing keys on) and use the env token verbatim as the login JWT for
repo-scoped exchange. A token with no URL-shaped aud is a hard error, not
a silent fallback to context resolution.

tokens.ParseClaims already exposes Audience (normalised across string and
array forms), so no helper port was needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

## Sessions

2f3a007d8690View transcript

## Changes

3

- cmd

- entire/cli/auth

- Aenv_token.go+39

- Aenv_token_test.go+92

- git-remote-entire

- Mmain.go+45/-18

```
package auth

import (
	"fmt"
	"net/url"
	"strings"

"github.com/entireio/auth-go/tokens"
)

// EnvTokenVar is the environment variable that, when set, bypasses
// contexts.json and the keyring entirely: its value is used verbatim as the
// login JWT for repo-scoped token exchange. This is the CI / workload-identity
// path — a runner injects a short-lived login or sa-session JWT and clones
// without an interactive `entire login`.
const EnvTokenVar = "ENTIRE_TOKEN"

// CoreURLFromEnvToken derives the home-region core URL from an ENTIRE_TOKEN
// JWT's audience claim. Login and sa-session JWTs carry aud=<home-region URL>,
// which is what STS routing keys on — so we read aud, not iss (iss may be a
// regional core that can't mint the cross-region exchange).
//
// The aud claim is URL-shaped (scheme://host[/path]). It may be a single
// string or an array (RFC 7519 §4.1.3); ParseClaims normalises both to a
// slice. The first URL-shaped audience wins. A token with no URL-shaped aud
// is rejected with a clear error rather than silently falling back to context
// resolution, so a misconfigured CI token fails loudly.
func CoreURLFromEnvToken(rawToken string) (string, error) {
	claims, err := tokens.ParseClaims(rawToken)
	if err != nil {
		return "", fmt.Errorf("parse %s claims: %w", EnvTokenVar, err)
	}
	for _, aud := range claims.Audience {
		if u, err := url.Parse(aud); err == nil && (u.Scheme == "http" || u.Scheme == "https") && u.Host != "" {
			return strings.TrimRight(aud, "/"), nil
		}
	}
	return "", fmt.Errorf("%s must be a login or sa-session JWT whose aud is the home-region URL; found no URL-shaped audience claim", EnvTokenVar)
}
```

```
package auth

import (
	"encoding/base64"
	"encoding/json"
	"testing"

"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestCoreURLFromEnvToken(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name    string
		aud     any // nil omits the aud claim entirely
		want    string
		wantErr bool
	}{
		{
			name: "string aud",
			aud:  "https://core.us.entire.io",
			want: "https://core.us.entire.io",
		},
		{
			name: "string aud trailing slash trimmed",
			aud:  "https://core.us.entire.io/",
			want: "https://core.us.entire.io",
		},
		{
			name: "array aud picks first URL-shaped",
			aud:  []string{"entire-cli", "https://core.eu.entire.io"},
			want: "https://core.eu.entire.io",
		},
		{
			name:    "opaque string aud rejected",
			aud:     "some-opaque-audience",
			wantErr: true,
		},
		{
			name:    "array of opaque audiences rejected",
			aud:     []string{"aud-a", "aud-b"},
			wantErr: true,
		},
		{
			name:    "missing aud rejected",
			aud:     nil,
			wantErr: true,
		},
	}

for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			payload := map[string]any{"sub": "ci-runner"}
			if tc.aud != nil {
				payload["aud"] = tc.aud
			}
			raw, err := json.Marshal(payload)
			require.NoError(t, err)
			token := makeJWT(t, string(raw))

got, err := CoreURLFromEnvToken(token)
			if tc.wantErr {
				require.Error(t, err)
				assert.Contains(t, err.Error(), EnvTokenVar)
				return
			}
			require.NoError(t, err)
			assert.Equal(t, tc.want, got)
		})
	}
}

func TestCoreURLFromEnvToken_MalformedToken(t *testing.T) {
	t.Parallel()
	_, err := CoreURLFromEnvToken("not-a-jwt")
	require.Error(t, err)
	assert.Contains(t, err.Error(), EnvTokenVar)
}

func TestCoreURLFromEnvToken_RejectsAlgNone(t *testing.T) {
	t.Parallel()
	// alg:none with a URL-shaped aud must still be rejected at the parse layer.
	enc := base64.RawURLEncoding
	token := enc.EncodeToString([]byte(`{"alg":"none"}`)) + "." +
		enc.EncodeToString([]byte(`{"aud":"https://core.us.entire.io"}`)) + "."
	_, err := CoreURLFromEnvToken(token)
	require.Error(t, err)
	assert.Contains(t, err.Error(), EnvTokenVar)
}
```
