Documentation
¶
Overview ¶
Package credential provides credential management for Moat.
Package credential provides credential management for Moat.
Package credential provides secure credential storage and retrieval.
Package credential provides secure credential storage and retrieval.
Index ¶
- Constants
- Variables
- func DefaultEncryptionKey() ([]byte, error)
- func DefaultStoreDir() string
- func GenerateAccessTokenPlaceholder(accountID string) string
- func GenerateIDTokenPlaceholder(accountID string) string
- func GrantNameForStoreKey(key Provider) string
- func ImpliedDependencies(grants []string) []string
- func IsKnownProvider(p Provider) bool
- func IsOAuthToken(token string) bool
- func ListProfiles() ([]string, error)
- func RegisterDynamicProvider(p Provider)
- func RegisterImpliedDeps(provider Provider, fn func() []string)
- func StoreDirForProfile(profile string) string
- func ValidateGrant(grant string) error
- func ValidateProfile(name string) error
- type AnthropicAuth
- type Bundle
- type BundleConfigurer
- type ClaudeCodeCredentials
- type ClaudeOAuthCredentials
- type ClaudeOAuthToken
- type Credential
- type FileStore
- func (s *FileStore) AddSSHMapping(mapping SSHMapping) error
- func (s *FileStore) Delete(provider Provider) error
- func (s *FileStore) Get(provider Provider) (*Credential, error)
- func (s *FileStore) GetSSHMappings() ([]SSHMapping, error)
- func (s *FileStore) GetSSHMappingsForHosts(hosts []string) ([]SSHMapping, error)
- func (s *FileStore) IsExcluded(provider Provider) (bool, error)
- func (s *FileStore) List() ([]Credential, error)
- func (s *FileStore) Owner(provider Provider) (*FileStore, error)
- func (s *FileStore) RemoveSSHMapping(host string) error
- func (s *FileStore) SSHSource(host string) (string, error)
- func (s *FileStore) Save(cred Credential) error
- func (s *FileStore) Source(provider Provider) (string, error)
- type HeaderReplacement
- type OAuthCredentialInfo
- type OpenAIAuth
- type Provider
- type ProviderCredential
- type ProxyConfigurer
- type ResponseTransformer
- type SSHMapping
- type Scope
- type Store
Constants ¶
const ( // ClaudeCodeKeychainService is the macOS Keychain service name for Claude Code credentials. ClaudeCodeKeychainService = "Claude Code-credentials" // ClaudeCredentialsFile is the relative path to Claude's credentials file. ClaudeCredentialsFile = ".claude/.credentials.json" )
const AnthropicAPIKeyPlaceholder = "sk-ant-api03-moatProxyInjectedPlaceholder0000000000000000000000000000000000000000000000000000000000000000000"
AnthropicAPIKeyPlaceholder is a placeholder that looks like a valid Anthropic API key. Some tools validate the API key format locally before making requests. Using a valid-looking placeholder bypasses these checks while still allowing the proxy to inject the real key at the network layer.
const ClaudeOAuthPlaceholder = "sk-ant-oat01-moat-proxy-injected-placeholder-not-a-real-token"
ClaudeOAuthPlaceholder is a placeholder that looks like a valid Claude Code OAuth token. Claude Code checks the sk-ant-oat prefix to decide whether the session is OAuth-authenticated. Without this prefix, it may skip OAuth-specific code paths that determine account capabilities (for example, 1M context window access).
The proxy intercepts all Anthropic HTTPS traffic and injects the real token via Authorization headers, so this placeholder never reaches Anthropic's servers.
const CopilotTokenPlaceholder = "github_pat_11MOATPROXYINJECTEDPLACEHOLDER000000000000000000000000000000000000000000"
CopilotTokenPlaceholder is a placeholder that looks like a GitHub fine-grained personal access token. GitHub Copilot CLI rejects classic PAT-shaped tokens locally, but accepts fine-grained PATs, GitHub CLI OAuth tokens, and Copilot CLI OAuth tokens. The real credential is injected by the proxy.
const GeminiAPIKeyPlaceholder = "AIzamoatProxyInjectedPlaceholder0000000"
GeminiAPIKeyPlaceholder is a placeholder that looks like a valid Gemini API key. Some tools validate the API key format locally before making requests. Using a valid-looking placeholder bypasses these checks while still allowing the proxy to inject the real key at the network layer.
const GitHubTokenPlaceholder = "ghp_moatProxyInjectedPlaceholder000000000000"
GitHubTokenPlaceholder is a placeholder that looks like a valid GitHub personal access token.
The gh CLI validates token format locally before making requests. Real GitHub PATs use the format ghp_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX (ghp_ prefix + 36 base62 characters, 40 total).
This format-valid placeholder is necessary because: - gh CLI checks GH_TOKEN format before making API calls - Tools like Claude Code's footer use gh CLI to fetch PR status - Without a valid-looking token, gh CLI rejects it and may prompt for auth
The proxy intercepts all GitHub HTTPS traffic and injects the real token via Authorization headers, so this placeholder never reaches GitHub's servers.
const JWTPlaceholder = "" /* 135-byte string literal not displayed */
JWTPlaceholder is a basic placeholder JWT for format validation only. Use GenerateIDTokenPlaceholder for Codex CLI which needs account_id in claims.
const MetaKeyBaseURL = "base_url"
MetaKeyBaseURL is the metadata key for the API endpoint a credential authenticates against, when that is not the provider's own API. An `anthropic` credential carrying this key is a gateway key: runs using it get ANTHROPIC_BASE_URL pointed here and the key injected for this host.
const MetaKeyTokenSource = "token_source"
MetaKeyTokenSource is the metadata key for recording how a token was obtained. Provider packages define the values (e.g., "cli", "env", "pat").
const OpenAIAPIKeyPlaceholder = "sk-moat-proxy-injected-placeholder-0000000000000000000000000000000000000000"
OpenAIAPIKeyPlaceholder is a placeholder that looks like a valid OpenAI API key. Some tools validate the API key format locally before making requests. Using a valid-looking placeholder bypasses these checks while still allowing the proxy to inject the real key at the network layer.
const ProxyInjectedPlaceholder = "moat-proxy-injected"
ProxyInjectedPlaceholder is a placeholder value for credentials that will be injected by the Moat proxy at runtime. The actual credential never reaches the container; instead, the proxy intercepts requests and adds the real Authorization header. This placeholder signals to tools that a credential is expected without exposing the actual value.
Variables ¶
var ActiveProfile string
ActiveProfile is the credential profile to use. When set, credentials are stored in a profile-specific subdirectory (~/.moat/credentials/profiles/<name>/). Set via --profile flag or MOAT_PROFILE environment variable. Empty string means use the default (unscoped) credential store. Not safe for concurrent use; set once during CLI initialization.
var ErrDecrypt = errors.New("decrypting credential")
ErrDecrypt is returned (wrapped) when a stored credential cannot be decrypted — usually because the encryption key changed. Match it with errors.Is rather than inspecting the error string.
var ErrNotFound = errors.New("credential not found")
ErrNotFound is returned (wrapped) when no stored credential exists for a provider. Match it with errors.Is rather than inspecting the error string.
Functions ¶
func DefaultEncryptionKey ¶
DefaultEncryptionKey retrieves the encryption key from secure storage. Uses system keychain when available, falls back to file-based storage.
func DefaultStoreDir ¶
func DefaultStoreDir() string
DefaultStoreDir returns the credential store directory for the active profile. When ActiveProfile is set, returns <moat-home>/credentials/profiles/<name>/. Otherwise returns the default <moat-home>/credentials/. See config.GlobalConfigDir for how MOAT_HOME overrides the default ~/.moat location.
func GenerateAccessTokenPlaceholder ¶
GenerateAccessTokenPlaceholder creates a JWT-formatted access token placeholder. The Codex CLI also validates the access_token as a JWT and extracts claims from it. This placeholder mirrors the structure of a real OpenAI access token.
func GenerateIDTokenPlaceholder ¶
GenerateIDTokenPlaceholder creates a JWT-formatted placeholder with the account_id embedded in the claims. This is required for Codex CLI which extracts the chatgpt_account_id from the id_token's JWT claims locally before making API calls.
The generated JWT has: - Header: {"alg":"RS256","typ":"JWT"} - Payload: {"sub":"moat-placeholder","exp":<future>,"https://api.openai.com/auth":{"chatgpt_account_id":"<account_id>"}} - Signature: placeholder (not cryptographically valid)
The signature is not valid, but Codex CLI only decodes the claims - it doesn't verify the signature locally (that's done server-side).
func GrantNameForStoreKey ¶ added in v0.8.0
GrantNameForStoreKey is the inverse of StoreKeyForGrant, for display. It lets output name a credential the way the user would grant or revoke it instead of exposing an internal store key.
func ImpliedDependencies ¶
ImpliedDependencies returns the dependencies implied by a list of grants. For example, a "github" grant implies "gh" and "git" dependencies.
func IsKnownProvider ¶
IsKnownProvider returns true if the provider is a known credential provider.
func IsOAuthToken ¶
IsOAuthToken returns true if the token appears to be a Claude Code OAuth token.
This uses a prefix-based heuristic: OAuth tokens from Claude Code start with "sk-ant-oat" (Anthropic OAuth Token). This prefix format is based on observed token structure as of 2025. If Anthropic changes their token format in the future, this function may need to be updated.
Note: API keys typically start with "sk-ant-api" for comparison.
func ListProfiles ¶ added in v0.3.0
ListProfiles returns the names of all credential profiles. Does not include the default (unscoped) profile.
func RegisterDynamicProvider ¶ added in v0.3.0
func RegisterDynamicProvider(p Provider)
RegisterDynamicProvider registers an additional provider at runtime. This is used by config-driven providers to extend the known provider list. Must be called during initialization only (before concurrent access).
func RegisterImpliedDeps ¶
RegisterImpliedDeps registers an implied dependencies function for a provider. This is typically called from init() functions in provider packages.
func StoreDirForProfile ¶ added in v0.6.1
StoreDirForProfile returns the credential store directory for an explicit profile, independent of the process-global ActiveProfile. An empty profile returns the default (unscoped) store. Use this when the current process's active profile may differ from the profile a credential belongs to — most importantly the shared proxy daemon, which serves runs from many profiles and must scope each run's token refresh to that run's own profile.
func ValidateGrant ¶
ValidateGrant validates a grant string and returns an error if invalid. Grants must be a known provider, optionally with a scope suffix (e.g., "github:repo").
func ValidateProfile ¶ added in v0.3.0
ValidateProfile checks if a profile name is valid. Profile names must start with an alphanumeric character and contain only alphanumeric characters, hyphens, and underscores.
Types ¶
type AnthropicAuth ¶
type AnthropicAuth struct {
HTTPClient *http.Client // Optional; uses http.DefaultClient if nil
// APIURL allows overriding the endpoint for testing.
APIURL string
}
AnthropicAuth handles Anthropic API key authentication.
func (*AnthropicAuth) CreateCredential ¶
func (a *AnthropicAuth) CreateCredential(apiKey string) Credential
CreateCredential creates a Credential from a validated API key.
func (*AnthropicAuth) PromptForAPIKey ¶
func (a *AnthropicAuth) PromptForAPIKey() (string, error)
PromptForAPIKey prompts the user to enter their Anthropic API key.
func (*AnthropicAuth) ValidateKey ¶
func (a *AnthropicAuth) ValidateKey(ctx context.Context, apiKey string) error
ValidateKey validates an Anthropic API key by making a minimal API request. Returns nil if the key is valid, or an error describing the problem.
type Bundle ¶ added in v0.8.0
type Bundle struct {
ID string
Grant string
Scope Scope
Replacements []HeaderReplacement
RequireAll bool
}
Bundle groups multiple headers that must be replaced atomically.
type BundleConfigurer ¶ added in v0.8.0
BundleConfigurer is implemented by proxy contexts that support atomic, request-scoped multi-header credentials.
type ClaudeCodeCredentials ¶
type ClaudeCodeCredentials struct{}
ClaudeCodeCredentials handles extraction of Claude Code OAuth credentials.
func (*ClaudeCodeCredentials) CreateCredentialFromOAuth ¶
func (c *ClaudeCodeCredentials) CreateCredentialFromOAuth(token *ClaudeOAuthToken) Credential
CreateCredentialFromOAuth creates a Moat Credential from Claude Code OAuth token.
func (*ClaudeCodeCredentials) GetClaudeCodeCredentials ¶
func (c *ClaudeCodeCredentials) GetClaudeCodeCredentials() (*ClaudeOAuthToken, error)
GetClaudeCodeCredentials attempts to retrieve Claude Code OAuth credentials. It tries the following sources in order: 1. macOS Keychain (if on macOS) 2. ~/.claude/.credentials.json file
Returns the credentials if found, or an error describing what went wrong.
func (*ClaudeCodeCredentials) HasClaudeCodeCredentials ¶
func (c *ClaudeCodeCredentials) HasClaudeCodeCredentials() bool
HasClaudeCodeCredentials checks if Claude Code credentials are available.
type ClaudeOAuthCredentials ¶
type ClaudeOAuthCredentials struct {
ClaudeAiOauth *ClaudeOAuthToken `json:"claudeAiOauth,omitempty"`
}
ClaudeOAuthCredentials represents the OAuth credentials stored by Claude Code.
type ClaudeOAuthToken ¶
type ClaudeOAuthToken struct {
AccessToken string `json:"accessToken"`
RefreshToken string `json:"refreshToken"`
ExpiresAt int64 `json:"expiresAt"` // Unix timestamp in milliseconds
Scopes []string `json:"scopes"`
SubscriptionType string `json:"subscriptionType,omitempty"`
RateLimitTier string `json:"rateLimitTier,omitempty"`
}
ClaudeOAuthToken represents an individual OAuth token from Claude Code.
func (*ClaudeOAuthToken) ExpiresAtTime ¶
func (t *ClaudeOAuthToken) ExpiresAtTime() time.Time
ExpiresAtTime returns the expiration time as a time.Time.
func (*ClaudeOAuthToken) IsExpired ¶
func (t *ClaudeOAuthToken) IsExpired() bool
IsExpired returns true if the token has expired.
type Credential ¶
type Credential struct {
Provider Provider `json:"provider"`
Token string `json:"token"`
Scopes []string `json:"scopes,omitempty"`
ExpiresAt time.Time `json:"expires_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
Metadata map[string]string `json:"metadata,omitempty"` // Provider-specific extra data
}
Credential represents a stored credential.
func (*Credential) GetCreatedAt ¶
func (c *Credential) GetCreatedAt() time.Time
GetCreatedAt implements ProviderCredential.
func (*Credential) GetExpiresAt ¶
func (c *Credential) GetExpiresAt() time.Time
GetExpiresAt implements ProviderCredential.
func (*Credential) GetMetadata ¶
func (c *Credential) GetMetadata() map[string]string
GetMetadata implements ProviderCredential.
func (*Credential) GetProvider ¶
func (c *Credential) GetProvider() string
GetProvider implements ProviderCredential.
func (*Credential) GetScopes ¶
func (c *Credential) GetScopes() []string
GetScopes implements ProviderCredential.
func (*Credential) GetToken ¶
func (c *Credential) GetToken() string
GetToken implements ProviderCredential.
type FileStore ¶
type FileStore struct {
// contains filtered or unexported fields
}
FileStore implements Store using encrypted files.
func NewFileStore ¶
NewFileStore creates a file-based credential store. key must be 32 bytes for AES-256.
func NewProfileStore ¶ added in v0.9.0
NewProfileStore opens a profile with its explicitly configured ancestors. Save always writes locally; refreshes use LockRefreshStore to retain ownership.
func (*FileStore) AddSSHMapping ¶
func (s *FileStore) AddSSHMapping(mapping SSHMapping) error
AddSSHMapping adds or updates an SSH host-to-key mapping.
func (*FileStore) Get ¶
func (s *FileStore) Get(provider Provider) (*Credential, error)
Get retrieves a credential for the given provider.
func (*FileStore) GetSSHMappings ¶
func (s *FileStore) GetSSHMappings() ([]SSHMapping, error)
GetSSHMappings returns all SSH host-to-key mappings.
func (*FileStore) GetSSHMappingsForHosts ¶
func (s *FileStore) GetSSHMappingsForHosts(hosts []string) ([]SSHMapping, error)
GetSSHMappingsForHosts returns mappings for the specified hosts.
func (*FileStore) IsExcluded ¶ added in v0.9.0
IsExcluded reports whether this store locally excludes a grant from inheritance, including future grants added to its parent.
func (*FileStore) List ¶
func (s *FileStore) List() ([]Credential, error)
List returns all stored credentials.
func (*FileStore) Owner ¶ added in v0.9.0
Owner returns the physical store supplying a credential. Errors, including exclusions and unreadable local credentials, never fall through to a parent.
func (*FileStore) RemoveSSHMapping ¶
RemoveSSHMapping removes an SSH mapping for a host.
func (*FileStore) SSHSource ¶ added in v0.9.0
SSHSource returns the profile supplying a host mapping.
func (*FileStore) Save ¶
func (s *FileStore) Save(cred Credential) error
Save stores a credential encrypted on disk.
type HeaderReplacement ¶ added in v0.8.0
HeaderReplacement describes one exact placeholder-to-secret replacement.
type OAuthCredentialInfo ¶
OAuthCredentialInfo holds information extracted from an OAuth credential for creating credential files.
type OpenAIAuth ¶
type OpenAIAuth struct {
HTTPClient *http.Client // Optional; uses http.DefaultClient if nil
// APIURL allows overriding the endpoint for testing.
APIURL string
}
OpenAIAuth handles OpenAI API key authentication.
func (*OpenAIAuth) CreateCredential ¶
func (a *OpenAIAuth) CreateCredential(apiKey string) Credential
CreateCredential creates a Credential from a validated API key.
func (*OpenAIAuth) PromptForAPIKey ¶
func (a *OpenAIAuth) PromptForAPIKey() (string, error)
PromptForAPIKey prompts the user to enter their OpenAI API key.
func (*OpenAIAuth) ValidateKey ¶
func (a *OpenAIAuth) ValidateKey(ctx context.Context, apiKey string) error
ValidateKey validates an OpenAI API key by making a minimal API request. Returns nil if the key is valid, or an error describing the problem.
type Provider ¶
type Provider string
Provider identifies a credential provider (github, aws, etc.)
const ( ProviderGitHub Provider = "github" ProviderAWS Provider = "aws" ProviderAnthropic Provider = "anthropic" ProviderClaude Provider = "claude" ProviderCodex Provider = "codex" ProviderOpenAI Provider = "openai" // ProviderCodexSubscription is intentionally not a public grant name. // Keeping a versioned storage key prevents older binaries from treating a // refresh-token bundle as an OpenAI API key. ProviderCodexSubscription Provider = "codex-subscription-v1" ProviderGemini Provider = "gemini" ProviderNpm Provider = "npm" ProviderGraphite Provider = "graphite" ProviderMeta Provider = "meta" ProviderLunaRoute Provider = "lunaroute" )
func KnownProviders ¶
func KnownProviders() []Provider
KnownProviders returns a list of all known credential providers.
func ParseGrantProvider ¶
ParseGrantProvider extracts the provider from a grant string. Grants can be "provider" or "provider:scope" format. For example, "github:repo" returns ProviderGitHub.
func StoreKeyForGrant ¶ added in v0.8.0
StoreKeyForGrant maps a user-facing grant name to the key its credential is stored under. They differ only where a grant deliberately keeps an internal, versioned identity: the Codex subscription is stored as ProviderCodexSubscription so an older binary cannot mistake a refresh-token bundle for an OpenAI API key.
type ProviderCredential ¶
type ProviderCredential interface {
GetProvider() string
GetToken() string
GetScopes() []string
GetExpiresAt() time.Time
GetCreatedAt() time.Time
GetMetadata() map[string]string
}
ProviderCredential is a minimal interface for provider.Credential conversion. This allows passing credentials to the new provider package without import cycles.
type ProxyConfigurer ¶
type ProxyConfigurer interface {
// SetCredential sets an Authorization header for a host.
SetCredential(host, value string)
// SetCredentialHeader sets a custom header for a host.
SetCredentialHeader(host, headerName, headerValue string)
// SetCredentialWithGrant sets a credential header with grant info for logging.
SetCredentialWithGrant(host, headerName, headerValue, grant string)
// AddExtraHeader adds an additional header to inject for a host.
AddExtraHeader(host, headerName, headerValue string)
// AddResponseTransformer registers a response transformer for a host.
// Transformers are called in registration order after the response is received.
AddResponseTransformer(host string, transformer ResponseTransformer)
// RemoveRequestHeader removes a client-sent header before forwarding.
// Used when injected credentials conflict with client headers.
RemoveRequestHeader(host, headerName string)
// SetTokenSubstitution replaces placeholder tokens with real tokens in
// URL paths, Authorization headers, and request bodies for a specific host.
// Body substitution is limited to 64KB requests to avoid memory issues.
SetTokenSubstitution(host, placeholder, realToken string)
}
type ResponseTransformer ¶
ProxyConfigurer is the interface for configuring proxy credentials. This avoids importing the proxy package directly. ResponseTransformer modifies HTTP responses for a host. It receives the request and response as any to avoid circular dependencies. Cast to *http.Request and *http.Response in the transformer implementation. Returns the modified response and true if transformed, or original response and false otherwise.
Note on body handling: Transformers are called BEFORE body capture for logging. If you need to inspect the response body, read it within the transformer and return a new response with a fresh body reader. The original body is not rewound after reading.
type SSHMapping ¶
type SSHMapping struct {
Host string `json:"host"`
KeyFingerprint string `json:"key_fingerprint"`
KeyPath string `json:"key_path,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
SSHMapping maps a host to an SSH key fingerprint.
type Store ¶
type Store interface {
Save(cred Credential) error
Get(provider Provider) (*Credential, error)
Delete(provider Provider) error
List() ([]Credential, error)
}
Store defines the credential storage interface.
func LockRefreshStore ¶ added in v0.9.0
LockRefreshStore pins operations to the owning profile and locks that credential across the re-read, token exchange, and save. Save and Delete use the same lock, so CLI grants and revocations cannot interleave with refresh. The caller must release the lock when finished; the returned store must not be retained or used after release.