Auth Model¶
lgtmaybe supports seven hosted providers plus local ollama, and an
openai-compatible escape hatch for any server speaking the OpenAI /v1 wire
format (DeepSeek's API, llama.cpp, LM Studio, vLLM). The design principle is no
static cloud credentials: cloud providers use ambient, short-lived tokens;
key-based SaaS providers (openai, anthropic, openrouter, zai) require an API key that
stays in secrets rather than being committed to config. Azure straddles both:
it prefers ambient Azure AD (Entra) credentials but accepts a resource key. It
always needs the resource endpoint (AZURE_API_BASE), since each Azure OpenAI
deployment lives at its own URL.
Why keyless for cloud¶
Static credentials — an AWS access key pair or a GCP service-account JSON file — have a fixed lifetime and broad scope. If they leak from a CI log, a secrets manager misconfiguration, or a compromised runner, an attacker retains access until the key is manually rotated.
OIDC (AWS), Workload Identity Federation (GCP), and federated credentials on an Entra app (Azure) issue tokens that:
- Expire in minutes, not years
- Are scoped to a single workflow run
- Cannot be extracted from CI logs (they are never set as static environment variables)
- Are tied to the specific repository and branch via the OIDC claim set
The exchange is the same shape on all three clouds — no secret ever enters the workflow:
sequenceDiagram
participant W as GitHub Actions run
participant O as GitHub OIDC issuer
participant STS as Cloud token service<br/>(AWS STS / GCP STS / Entra)
participant M as Model API<br/>(Bedrock / Vertex / Azure OpenAI)
W->>O: request identity token (id-token: write)
O-->>W: signed JWT with repo + branch claims
W->>STS: exchange JWT for cloud credentials
STS-->>W: short-lived, scoped credentials
W->>M: lgtmaybe review calls with ambient creds
For these reasons, lgtmaybe treats Bedrock and Vertex as ambient-credential
only providers. There is no --api-key flag for them. Azure defaults to the
same keyless path (GitHub OIDC → Entra, via azure-identity's
DefaultAzureCredential) but additionally accepts a resource key for teams that
can't yet adopt federation.
Chain of responsibility¶
When lgtmaybe starts, it resolves credentials for the selected provider using a chain:
- Provider identity — which provider was chosen?
- Ambient cloud creds — for Bedrock and Vertex, the chain stops here. If no ambient creds exist, lgtmaybe fails immediately with a clear "how to auth this provider" message. It does not fall back to a key.
- API key — for openai, anthropic, openrouter, and zai (GLM / Zhipu AI),
lgtmaybe reads the key from the environment (
OPENAI_API_KEY,ANTHROPIC_API_KEY,OPENROUTER_API_KEY,ZAI_API_KEY). The--api-keyflag can override this at the CLI.zaialso accepts an optional--api-base/ZAI_API_BASEoverride for the China / coding-plan GLM endpoint; without it litellm's nativezai/route targets the international endpoint. - Azure — always needs the resource endpoint (
AZURE_API_BASEor--api-base). For the credential it prefers a key when one is present (AZURE_API_KEY/--api-key); otherwise it goes keyless, minting an Azure AD token from ambient creds (GitHub OIDC federation in CI, or a localaz login/ managed identity). If neither a key nor an ambient credential is available, lgtmaybe fails with a message naming both options. - None — ollama requires no credentials. Only
--api-baseis needed to reach the local or remote server. - openai-compatible — always needs the endpoint (
--api-baseorOPENAI_COMPATIBLE_API_BASE), since the whole point is to choose your own. The key is optional: hosted endpoints like DeepSeek take one (--api-key/OPENAI_COMPATIBLE_API_KEY), while local servers (llama.cpp, LM Studio, vLLM) need none — lgtmaybe sends a harmless placeholder key when you supply none, because the underlying OpenAI client rejects an empty one.
Provider auth summary¶
| Provider | Credential type | How to supply |
|---|---|---|
| openai | API key | OPENAI_API_KEY env var or --api-key |
| anthropic | API key | ANTHROPIC_API_KEY env var or --api-key |
| openrouter | API key | OPENROUTER_API_KEY env var or --api-key |
| zai | API key (+ optional endpoint) | ZAI_API_KEY env var or --api-key (GLM / Zhipu AI); optional ZAI_API_BASE / --api-base for the China / coding-plan endpoint |
| bedrock | Ambient AWS creds | GitHub OIDC role or local ~/.aws; IAM requires only bedrock:InvokeModel* |
| vertex | Ambient GCP creds | GitHub WIF or local ADC (gcloud auth application-default login) |
| azure | Ambient Azure AD creds (keyless) or API key, + endpoint | GitHub OIDC → Entra federated credential, or local az login / managed identity; or AZURE_API_KEY. Always with AZURE_API_BASE / --api-base |
| ollama | None | --api-base pointing to the local or remote server |
| openai-compatible | Optional key, + endpoint | --api-base / OPENAI_COMPATIBLE_API_BASE (e.g. https://api.deepseek.com/v1 or http://localhost:8000/v1); key via --api-key / OPENAI_COMPATIBLE_API_KEY, or none for keyless local servers |
Least-privilege IAM¶
For Bedrock, the minimum IAM policy is bedrock:InvokeModel and (if streaming)
bedrock:InvokeModelWithResponseStream on the specific model ARN. No other AWS
permissions are needed or requested.
For Vertex, roles/aiplatform.user on the project is sufficient.
roles/editor or roles/owner must not be used.
For Azure (keyless), the Entra app needs only the Cognitive Services OpenAI User role on the Azure OpenAI resource — enough to call deployments, not to manage them — plus a federated credential scoped to your repository. No owner or contributor role is required.
API keys in secrets, not config¶
For openai, anthropic, and openrouter, the key must live in a GitHub Actions
secret (or an equivalent secret store). It must never be committed to
.lgtmaybe.yml or any other file in the repository. lgtmaybe does not log or
display key values.