Grants reference
Grants provide credentials to container runs. Each grant type injects authentication for specific hosts. Credentials are stored encrypted on your host machine and injected at the network layer by Moat’s TLS-intercepting proxy. The container process does not have direct access to raw tokens.
Store a credential with moat grant <provider>, then use it in runs with --grant <provider> or in moat.yaml.
Grant types
| Grant | Hosts matched | Header injected | Credential source |
|---|---|---|---|
github | api.github.com, github.com | Authorization: Bearer ... | gh CLI, GITHUB_TOKEN/GH_TOKEN, or PAT prompt |
claude | api.anthropic.com | Authorization: Bearer ... | claude setup-token or imported OAuth |
anthropic | api.anthropic.com | x-api-key: ... | API key from console.anthropic.com |
openai | api.openai.com, chatgpt.com, *.openai.com | Authorization: Bearer ... | OPENAI_API_KEY or prompt |
gemini | generativelanguage.googleapis.com (API key) or cloudcode-pa.googleapis.com (OAuth) | x-goog-api-key: ... (API key) or Authorization: Bearer ... (OAuth) | Gemini CLI OAuth, GEMINI_API_KEY, or prompt |
graphite | api.graphite.com, *.graphite.com | Authorization: token ... | GRAPHITE_TOKEN, GT_TOKEN, or prompt |
meta | graph.facebook.com, graph.instagram.com | Authorization: Bearer ... | META_ACCESS_TOKEN or prompt |
npm | Per-registry (e.g., registry.npmjs.org, npm.company.com) | Authorization: Bearer ... | .npmrc, NPM_TOKEN, or manual |
aws | All AWS service endpoints | AWS credential_process (STS temporary credentials) | IAM role assumption via STS |
ssh:<host> | Specified host only | SSH agent forwarding (not HTTP) | Host SSH agent (SSH_AUTH_SOCK) |
mcp-<name> | Host from MCP server url field | Configured per-server header | Interactive prompt |
gitlab | gitlab.com, *.gitlab.com | PRIVATE-TOKEN: ... | GITLAB_TOKEN, GL_TOKEN, or prompt |
brave-search | api.search.brave.com | X-Subscription-Token: ... | BRAVE_API_KEY, BRAVE_SEARCH_API_KEY, or prompt |
elevenlabs | api.elevenlabs.io | xi-api-key: ... | ELEVENLABS_API_KEY or prompt |
linear | api.linear.app | Authorization: ... | LINEAR_API_KEY or prompt |
vercel | api.vercel.com, *.vercel.com | Authorization: Bearer ... | VERCEL_TOKEN or prompt |
sentry | sentry.io, *.sentry.io | Authorization: Bearer ... | SENTRY_AUTH_TOKEN or prompt |
datadog | *.datadoghq.com | DD-API-KEY: ... | DD_API_KEY, DATADOG_API_KEY, or prompt |
Run moat grant providers to list all providers, including any custom providers you’ve added.
GitHub
CLI command
moat grant github
No flags. The command automatically detects your credential source.
Credential sources (in order of preference)
- gh CLI — Uses the token from
gh auth tokenif the GitHub CLI is installed and authenticated - Environment variable — Falls back to
GITHUB_TOKENorGH_TOKENif set - Personal Access Token — Interactive prompt for manual PAT entry
What it injects
The proxy injects an Authorization: Bearer <token> header for requests to:
api.github.comgithub.com
The container receives GH_TOKEN set to a format-valid placeholder so the gh CLI works without prompting.
Refresh behavior
Tokens sourced from gh auth token or environment variables are refreshed every 30 minutes. PATs entered manually are static.
moat.yaml
grants:
- github
Example
$ moat grant github
Found gh CLI authentication
Use token from gh CLI? [Y/n]: y
Validating token...
Authenticated as: octocat
GitHub credential saved
$ moat run --grant github ./my-project
Anthropic / Claude
Anthropic credentials are split into two separate grant types:
claude— OAuth tokens from Claude Pro/Max subscriptions. Restricted by Anthropic’s ToS to Claude Code only. UsesAuthorization: Bearerauth.anthropic— API keys fromconsole.anthropic.com. Works with any tool or agent. Usesx-api-keyauth.
Each command grants its own credential type. Both can coexist and moat grant list shows both.
CLI commands
moat grant claude # OAuth token (for moat claude / Claude Code)
moat grant anthropic # API key (for any agent or tool)
No flags.
moat grant claude
Presents a menu of OAuth token sources:
- Claude subscription — Runs
claude setup-tokento obtain a long-lived OAuth token. Requires a Claude Pro/Max subscription and the Claude CLI installed. - Existing OAuth token — Paste a token from a previous
claude setup-tokenrun. - Import existing credentials — Imports OAuth tokens from your local Claude Code installation.
Stored as claude.enc.
moat grant anthropic
Prompts for an API key directly, or uses ANTHROPIC_API_KEY from the environment.
Stored as anthropic.enc.
What it injects
The proxy injects credentials for requests to api.anthropic.com:
claudegrant:Authorization: Bearer <token>with OAuth beta flag. Container receivesCLAUDE_CODE_OAUTH_TOKENplaceholder.anthropicgrant:x-api-key: <key>. Container receivesANTHROPIC_API_KEYplaceholder.
Refresh behavior
OAuth tokens imported from a local Claude Code installation do not auto-refresh. When the token expires, run a Claude Code session on your host to refresh it, then re-import with moat grant claude.
API keys do not expire.
moat claude grant resolution
When you run moat claude, the credential is selected automatically:
- If
claudeexists, use it (preferred for Claude Code) - If only
anthropicexists, use it as fallback - If neither exists, error with instructions to run
moat grant claude
Using both grants
You can grant both and use them together. This is useful when Claude Code needs its OAuth token and sub-tools in the same container need an API key:
moat run --grant claude --grant anthropic ./my-project
moat.yaml
grants:
- claude # OAuth token for Claude Code
- anthropic # API key for any tool
Backward compatibility
Existing users with an OAuth token stored under anthropic (the old single-provider model) are auto-migrated: moat claude detects the OAuth token prefix, copies it to claude.enc, and removes the old anthropic entry.
Examples
# Grant an OAuth token for Claude Code
$ moat grant claude
Choose authentication method:
1. Claude subscription (OAuth token)
Runs 'claude setup-token' to get a long-lived token.
2. Existing OAuth token
3. Import existing Claude Code credentials
Enter choice [1-3]: 1
Running 'claude setup-token' to obtain authentication token...
Credential saved to ~/.moat/credentials/claude.enc
# Grant an API key for general use
$ moat grant anthropic
Enter your Anthropic API key: ••••••••
Validating API key...
API key is valid.
Credential saved to ~/.moat/credentials/anthropic.enc
# Use Claude Code (picks up OAuth token automatically)
$ moat claude ./my-project
# Use both in a single run
$ moat run --grant claude --grant anthropic ./my-project
OpenAI
CLI command
moat grant openai
No flags.
Credential sources
- Environment variable — Uses
OPENAI_API_KEYif set - Interactive prompt — Prompts for an API key
What it injects
The proxy injects an Authorization: Bearer <token> header for requests to api.openai.com, chatgpt.com, and *.openai.com.
The container receives OPENAI_API_KEY set to a format-valid placeholder so OpenAI SDKs work without prompting.
Refresh behavior
API keys do not expire or refresh.
moat.yaml
grants:
- openai
Example
$ moat grant openai
Enter your OpenAI API key.
You can find or create one at: https://platform.openai.com/api-keys
API Key: ••••••••
Validating API key...
API key is valid.
OpenAI API key saved to ~/.moat/credentials/openai.enc
$ moat codex ./my-project
Gemini
CLI command
moat grant gemini
No flags. The command detects whether Gemini CLI is installed and presents options accordingly.
Credential sources
- Gemini CLI OAuth (recommended) — Imports refresh tokens from a local Gemini CLI installation. Requires Gemini CLI installed and authenticated.
- API key — Enter an API key directly or set
GEMINI_API_KEYin your environment.
What it injects
Gemini routes to different API backends depending on authentication method:
- API key mode: The proxy injects an
x-goog-api-key: <key>header for requests togenerativelanguage.googleapis.com. The container receivesGEMINI_API_KEYset to a placeholder value. - OAuth mode: The proxy injects
Authorization: Bearer <token>for requests tocloudcode-pa.googleapis.comand handles token substitution foroauth2.googleapis.com. The container receives a placeholderoauth_creds.jsonin~/.gemini/.
Refresh behavior
OAuth tokens are automatically refreshed by the proxy. Google OAuth tokens expire after 1 hour; Moat refreshes 15 minutes before expiry (every 45 minutes).
API keys do not expire.
moat.yaml
grants:
- gemini
Example
$ moat grant gemini
Choose authentication method:
1. Import Gemini CLI credentials (recommended)
2. Gemini API key
Enter choice [1 or 2]: 1
Found Gemini CLI credentials.
Validating refresh token...
Refresh token is valid.
Gemini credential saved to ~/.moat/credentials/gemini.enc
$ moat gemini ./my-project
npm
CLI command
moat grant npm
moat grant npm --host=<registry-host>
Flags
| Flag | Description |
|---|---|
--host HOSTNAME | Specific registry host (e.g., npm.company.com) |
Without --host, the command auto-discovers registries from ~/.npmrc and the NPM_TOKEN environment variable.
Credential sources
.npmrcfile — Parses~/.npmrcfor//host/:_authToken=entries and@scope:registry=routing- Environment variable — Falls back to
NPM_TOKENfor the default registry (registry.npmjs.org) - Manual entry — Interactive prompt for a token
What it injects
The proxy injects an Authorization: Bearer <token> header for requests to each registered npm registry host. Each host gets its own credential — multiple registries are supported in a single grant.
The container receives a generated .npmrc at ~/.npmrc with:
- Real scope-to-registry routing (npm needs this to resolve scoped packages)
- Placeholder tokens (
npm_moatProxyInjected00000000) — the proxy replacesAuthorizationheaders at the network layer
Refresh behavior
npm tokens are static and do not refresh. If a token expires, revoke and re-grant.
Stacking
Multiple moat grant npm --host=<host> calls merge into a single credential. Each call adds or replaces the entry for that host. All registries are injected together at runtime.
moat.yaml
grants:
- npm
Example
$ moat grant npm
Choose authentication method:
1. Import from .npmrc / environment
Found registries: registry.npmjs.org (default), npm.company.com (@myorg)
To import a single registry, use: moat grant npm --host=<host>
2. Enter token manually
Enter choice [1-2]: 1
Validating...
✓ registry.npmjs.org — authenticated as "jsmith"
✓ npm.company.com — authenticated as "jsmith"
Credential saved to ~/.moat/credentials/npm.enc
$ moat run --grant npm -- npm whoami
jsmith
Graphite
CLI command
moat grant graphite
No flags.
Credential sources
- Environment variable — Uses
GRAPHITE_TOKENorGT_TOKENif set - Interactive prompt — Prompts for a token with instructions to visit
https://app.graphite.com/activate
What it injects
The proxy injects an Authorization: token <token> header for requests to api.graphite.com. Note: Graphite uses the token prefix, not Bearer.
The container receives a config file at ~/.config/graphite/user_config with a placeholder token so the Graphite CLI (gt) works without prompting.
Implied dependencies
Granting graphite automatically adds graphite-cli, node, and git as container dependencies. These are installed during image build.
Refresh behavior
Graphite tokens are static and do not refresh.
moat.yaml
grants:
- graphite
Example
$ moat grant graphite
Enter a Graphite auth token.
To get your token:
1. Visit https://app.graphite.com/activate
2. Sign in and copy the token
3. Paste it below
Token: ••••••••
Validating token...
Token validated successfully
$ moat run --grant graphite ./my-project
Meta
CLI command
moat grant meta
No flags.
Credential sources
- Environment variable — Uses
META_ACCESS_TOKENif set - Interactive prompt — Prompts for an access token
The grant command validates the token against Meta’s Graph API before saving.
What it injects
The proxy injects an Authorization: Bearer <token> header for requests to:
graph.facebook.comgraph.instagram.com
No environment variables or config files are set inside the container.
Environment variables
| Variable | Purpose |
|---|---|
META_ACCESS_TOKEN | Access token — any Meta token type (see below) |
META_APP_ID | Facebook app ID — needed for token refresh |
META_APP_SECRET | Facebook app secret — needed for token refresh |
All three can be set as environment variables or entered interactively. META_ACCESS_TOKEN is required. META_APP_ID and META_APP_SECRET are optional — the grant flow prompts for them after the token, and you can press Enter to skip.
Token types and refresh
The grant flow accepts any Meta access token. What happens next depends on which kind of token you provide and whether you also provide app credentials (app ID + app secret).
Long-lived user tokens with app credentials are the recommended setup. The token expires after ~60 days, but Moat exchanges it for a fresh one once per day, keeping it valid indefinitely. Generate a long-lived token by exchanging a short-lived one through the Graph API Explorer or your app’s OAuth flow, and provide your app ID and secret during moat grant meta. If a token is compromised, revoke it immediately in the Meta developer dashboard — do not rely on expiry.
Short-lived user tokens (from Graph API Explorer or an OAuth login flow) expire in 1—2 hours. If you provide app credentials, Moat exchanges the short-lived token for a long-lived one on the first refresh cycle, then refreshes it daily. This is a valid way to bootstrap — you do not need to exchange the token yourself first. Without app credentials, the token expires quickly and there is no way to extend it. This can provide additional security at the cost of capping the amount of time your agent will be able to run independently.
System user tokens never expire. They are created in Business Manager > System Users > Generate Token and are typically reserved for business owners or internal infrastructure. Because they never expire and cannot be scoped per-session, a leaked system user token has no automatic damage window. Prefer long-lived user tokens with app credentials for delegated access and agent use.
Summary:
| Token type | App credentials provided? | What happens |
|---|---|---|
| Long-lived user | Yes | Exchanged for a fresh long-lived token once per day. Recommended. |
| Long-lived user | No | Works for ~60 days, then expires. |
| Short-lived user | Yes | Exchanged for a long-lived token on first refresh, then refreshed daily. |
| Short-lived user | No | Expires in 1—2 hours. Requests fail after that. |
| System user | No | Token never expires. No refresh needed. |
| System user | Yes | Unnecessary but harmless. Refresh is a no-op. |
API version
The provider uses Meta Graph API v25.0 by default. Override with the META_API_VERSION environment variable:
META_API_VERSION=v26.0 moat grant meta
moat.yaml
grants:
- meta
Examples
User token with app credentials (recommended):
$ moat grant meta
Enter a Meta access token.
To create one:
1. Go to https://developers.facebook.com/tools/explorer/
2. Select your app and generate a token with the required permissions
Access token: ••••••••
Authenticated as: Jane Developer
Optional: provide app ID and app secret to enable automatic token refresh.
Press Enter to skip.
App ID (or Enter to skip): ••••••••
App secret: ••••••••
Token refresh enabled
$ moat run --grant meta ./my-project
Via environment variables:
$ META_ACCESS_TOKEN=EAAx... META_APP_ID=123456 META_APP_SECRET=abc123 moat grant meta
Using token from META_ACCESS_TOKEN environment variable
Authenticated as: Jane Developer
Found META_APP_ID and META_APP_SECRET for token refresh
$ moat run --grant meta ./my-project
AWS
CLI command
moat grant aws --role <ARN> [flags]
Flags
| Flag | Description | Default |
|---|---|---|
--role ARN | IAM role ARN to assume (required) | — |
--region REGION | AWS region for API calls | From AWS config |
--session-duration DURATION | Session duration (e.g., 1h, 30m, 15m) | 15m |
--external-id ID | External ID for cross-account role assumption | — |
--aws-profile PROFILE | AWS shared config profile for role assumption (falls back to AWS_PROFILE env var) | — |
Credential source
Moat uses your host AWS credentials to call sts:AssumeRole. Your host must have valid AWS credentials (via aws configure, environment variables, or instance profile), and the target role must have a trust policy allowing your host identity to assume it.
If you use named AWS profiles, pass --aws-profile (or set AWS_PROFILE) at grant time. The profile is stored with the credential so the proxy daemon uses the correct source identity for role assumption, regardless of the daemon’s own environment.
What it injects
AWS credentials use credential_process rather than HTTP header injection:
- The role ARN and configuration are stored (not temporary credentials)
- When a run starts, Moat configures
AWS_CONFIG_FILEin the container with acredential_processentry - The
credential_processcommand calls back to the proxy, which assumes the role and returns fresh temporary credentials - The AWS SDK automatically calls the credential process when credentials expire
Note: The
credential_processmechanism is accessible inside the container. Credentials are temporary (STS), sessions are short (default 15 minutes), and permissions are scoped to the assumed role.
Refresh behavior
The AWS SDK handles credential refresh automatically via credential_process. Each call to the process assumes a fresh role session with the configured duration.
moat.yaml
grants:
- aws
Note: AWS-specific options (role, region, session duration, external ID) are configured at grant time with
moat grant aws, not inmoat.yaml. Themoat.yamlgrants field only specifies which grant types to use for a run.
Example
$ moat grant aws \
--role arn:aws:iam::123456789012:role/AgentRole \
--region us-west-2 \
--session-duration 30m
Assuming role: arn:aws:iam::123456789012:role/AgentRole
Region: us-west-2
Session duration: 30m0s
Role assumed successfully
AWS credential saved
$ moat run --grant aws ./my-project
SSH
CLI command
moat grant ssh --host <hostname>
Flags
| Flag | Description |
|---|---|
--host HOSTNAME | Host to grant SSH access to (required) |
Credential source
Uses your host’s SSH agent (SSH_AUTH_SOCK). The SSH agent must be running with keys loaded.
What it injects
SSH grants work differently from other grants. Instead of injecting HTTP headers, Moat proxies SSH agent requests:
- An SSH agent proxy starts inside the container
- The proxy connects to your host’s SSH agent
- Key listing and signing requests are forwarded, but only for keys mapped to the granted host
- Private keys never enter the container
Refresh behavior
SSH agent requests are forwarded in real time. No refresh mechanism is needed.
moat.yaml
grants:
- ssh:github.com
The host name is part of the grant identifier, separated by a colon.
Example
$ moat grant ssh --host github.com
Using key: user@host (SHA256:...)
Granted SSH access to github.com
$ moat run --grant ssh:github.com -- git clone git@github.com:my-org/my-project.git
MCP
CLI command
moat grant mcp <name>
The <name> argument matches the MCP server name in moat.yaml. The credential is stored as mcp-<name>.
Credential source
Interactive prompt for a credential value (hidden input).
What it injects
The proxy injects the credential into the HTTP header specified by auth.header in the MCP server configuration. Injection occurs for requests matching the host in the MCP server’s url field.
Refresh behavior
MCP credentials are static. Revoke and re-grant to update them.
moat.yaml
MCP grants are referenced in the top-level mcp: field, not in grants::
mcp:
- name: context7
url: https://mcp.context7.com/mcp
auth:
grant: mcp-context7
header: CONTEXT7_API_KEY
Example
$ moat grant mcp context7
Enter credential for MCP server 'context7': ••••••••
MCP credential 'mcp-context7' saved
$ moat claude ./my-project
Using grants in runs
Via CLI flags
Pass --grant one or more times:
moat run --grant github ./my-project
moat run --grant github --grant anthropic ./my-project
moat run --grant ssh:github.com ./my-project
Via moat.yaml
List grants in the grants field:
grants:
- github
- anthropic
- openai
- npm
- ssh:github.com
Grants from CLI flags are merged with those in moat.yaml.
Multiple grants
Combine any number of grants in a single run:
grants:
- anthropic
- github
- ssh:github.com
- aws
moat claude --grant github --grant ssh:github.com ./my-project
Each grant type injects credentials independently. The proxy matches requests by host and injects the appropriate headers.
Managing grants
List stored grants
moat grant list
moat grant list --json
# List grants in a specific profile
moat grant list --profile work
Revoke a grant
moat revoke github
moat revoke claude
moat revoke anthropic
moat revoke npm
moat revoke ssh:github.com
moat revoke mcp-context7
# Revoke from a specific profile
moat revoke github --profile work
This deletes the encrypted credential file. Future runs cannot use the credential until you grant it again.
Credential profiles
Profiles maintain separate sets of credentials. Use one profile for personal projects and another for work.
Setting a profile
Set the active profile with the --profile global flag or the MOAT_PROFILE environment variable:
# Grant a credential to a profile
moat grant github --profile work
# Use profile credentials in a run
moat run --grant github --profile work
# List profile credentials
moat grant list --profile work
# Set via environment variable
export MOAT_PROFILE=work
moat grant github
moat run --grant github
Storage layout
Profile credentials are stored separately from the default credential store:
~/.moat/credentials/ # Default (no profile)
~/.moat/credentials/profiles/
work/ # "work" profile
personal/ # "personal" profile
Each profile has its own isolated set of encrypted credential files. Granting a credential in one profile does not affect another.
Profile names
Profile names must start with a letter or digit and contain only letters, digits, hyphens, and underscores.
Valid: work, my-project, team_alpha, prod1
Invalid: -leading-dash, has spaces, uses.dots
Credential storage
Credentials are stored encrypted in ~/.moat/credentials/ (or ~/.moat/credentials/profiles/<name>/ when using a profile). See Credential management for encryption and storage details.
Config-driven providers
The providers from gitlab through datadog in the grant types table are defined as YAML configurations shipped with the binary. They work the same as the Go-implemented providers — credentials are stored encrypted and injected at the network layer — but are defined declaratively.
Use them the same way:
moat grant gitlab
moat run --grant gitlab ./my-project
Custom providers
Add your own providers by creating YAML files in ~/.moat/providers/. Each file defines a provider with host matching rules, header injection, and credential sources. See Provider YAML reference for the full schema.
Listing providers
List all available providers (built-in, packaged, and custom) with:
moat grant providers
moat grant providers --json
Related pages
- Credential management — How credential injection works conceptually
- Security model — Threat model and security properties
- CLI reference — Full CLI command reference, including
moat grantsubcommands - moat.yaml reference — All
moat.yamlfields, includinggrantsandmcp - Provider YAML reference — Schema for YAML-defined credential providers