Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ Moat runs AI coding agents in isolated containers with credential injection, net

Moat is pre-1.0. The CLI interface and `moat.yaml` schema may change between minor versions. Breaking changes are listed under **Breaking** headings below.

## Unreleased

### Added

- **gcloud credential provider** — authenticate the Google Cloud CLI and client libraries inside a moat sandbox without leaking refresh tokens or service account keys. The host daemon mints short-lived access tokens via Application Default Credentials and serves them through an emulated GCE metadata server. Use `moat grant gcloud` then `moat run --grant gcloud`.

## v0.5.0 — 2026-04-07

v0.5 hardens network isolation and introduces operation-level policy enforcement on MCP tool calls and HTTP traffic. Host traffic is now blocked by default in every network policy mode — including `permissive` — and must be opted into per-port with `network.host`. Keep policy integration adds allow/deny/redact rules for MCP tool calls and REST API requests, with starter packs for common services and an LLM response policy that evaluates `tool_use` blocks before forwarding to the container. The credential-injecting proxy is now also available as a standalone `gatekeeper` binary that runs without the moat runtime. Other additions include multi-credential per host, custom base images, OAuth grants for MCP servers, sandbox-local MCP servers, and global mounts in `~/.moat/config.yaml`.
Expand Down
8 changes: 8 additions & 0 deletions cmd/moat/cli/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cli

import (
"context"
"net/http"
"os"
"os/signal"
"path/filepath"
Expand Down Expand Up @@ -71,6 +72,13 @@ func runDaemon(_ *cobra.Command, _ []string) error {
return rc.ToProxyContextData(), true
})

// Wire the gcloud direct resolver for metadata requests that bypass HTTP_PROXY.
// Python's google-auth uses bare http.client for GCE detection, so when
// GCE_METADATA_HOST points at the proxy, these arrive without Proxy-Authorization.
p.SetGCloudDirectResolver(func() http.Handler {
return apiServer.Registry().FindGCloudHandler()
})

// Wire network request logging. The proxy is shared across runs, so
// the logger routes to per-run storage using the RunID from request context.
var storeMu sync.Mutex
Expand Down
21 changes: 21 additions & 0 deletions cmd/moat/cli/grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/majorcontext/moat/internal/credential"
"github.com/majorcontext/moat/internal/provider"
"github.com/majorcontext/moat/internal/providers/aws"
"github.com/majorcontext/moat/internal/providers/gcloud"
"github.com/spf13/cobra"
"golang.org/x/term"
)
Expand All @@ -25,6 +26,14 @@ var (
awsProfile string
)

// gcloud grant flags - these need to be passed to the gcloud provider
var (
gcloudProject string
gcloudScopes string
gcloudImpersonateSA string
gcloudKeyFile string
)

var grantCmd = &cobra.Command{
Use: "grant <provider>",
Short: "Grant a credential for use in runs",
Expand All @@ -48,6 +57,7 @@ Examples:
moat grant anthropic # Grant Anthropic API key (for any agent)
moat grant github # Grant GitHub access
moat grant aws --role=arn:aws:... # Grant AWS access via IAM role
moat grant gcloud --project my-project # Grant Google Cloud access
moat grant github --profile myproject # Grant GitHub access in a profile
moat grant providers # List all available providers
moat run my-agent . --grant github # Use credential in a run
Expand All @@ -63,6 +73,12 @@ func init() {
grantCmd.Flags().StringVar(&awsSessionDuration, "session-duration", "", "Session duration (default: 15m, max: 12h)")
grantCmd.Flags().StringVar(&awsExternalID, "external-id", "", "External ID for role assumption")
grantCmd.Flags().StringVar(&awsProfile, "aws-profile", "", "AWS shared config profile for role assumption (falls back to AWS_PROFILE env var if not set)")

// gcloud flags
grantCmd.Flags().StringVar(&gcloudProject, "project", "", "Google Cloud project ID")
grantCmd.Flags().StringVar(&gcloudScopes, "scopes", "", "OAuth scopes (comma-separated, default: cloud-platform)")
grantCmd.Flags().StringVar(&gcloudImpersonateSA, "impersonate-service-account", "", "Service account email to impersonate via IAM")
grantCmd.Flags().StringVar(&gcloudKeyFile, "key-file", "", "Path to service account key file")
}

// saveCredential stores a credential and returns the file path.
Expand Down Expand Up @@ -128,6 +144,11 @@ Options:
ctx = aws.WithGrantOptions(ctx, awsRole, awsRegion, awsSessionDuration, awsExternalID, awsProfile)
}

// For gcloud, pass the CLI flags via context
if providerName == "gcloud" {
ctx = gcloud.WithGrantOptions(ctx, gcloudProject, gcloudScopes, gcloudImpersonateSA, gcloudKeyFile)
}

provCred, err := prov.Grant(ctx)
if err != nil {
return err
Expand Down
103 changes: 103 additions & 0 deletions docs/content/guides/14-gcloud.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
---
title: "Google Cloud"
navTitle: "Google Cloud"
description: "Use gcloud CLI and Google Cloud client libraries inside a Moat sandbox."
keywords: ["moat", "gcloud", "google cloud", "gcp", "credentials", "metadata server"]
---

# Google Cloud

Moat injects Google Cloud credentials into containers through a GCE metadata server emulator. The gcloud CLI, `google-cloud-*` client libraries, and any tool that uses Application Default Credentials (ADC) work without configuration changes inside the container.

No long-lived credentials (refresh tokens, service account keys) enter the container.

## Prerequisites

Configure Application Default Credentials on the host:

```bash
gcloud auth application-default login
```

Or set `GOOGLE_APPLICATION_CREDENTIALS` to a service account JSON key file.

## Grant a credential

```bash
moat grant gcloud --project my-project
```

The `--project` flag sets the GCP project ID for API calls. If omitted, Moat checks `GOOGLE_CLOUD_PROJECT`, `CLOUDSDK_CORE_PROJECT`, then `gcloud config get-value project`.

### Service account impersonation

```bash
moat grant gcloud \
--project my-project \
--impersonate-service-account deploy@my-project.iam.gserviceaccount.com
```

The host's credentials are used to impersonate the specified service account via IAM. The container receives tokens scoped to the impersonated identity.

### Explicit key file

```bash
moat grant gcloud \
--project my-project \
--key-file /path/to/service-account.json
```

The key file stays on the host. The daemon reads it to mint access tokens — the file is never mounted into the container.

### Custom scopes

```bash
moat grant gcloud \
--project my-project \
--scopes https://www.googleapis.com/auth/bigquery,https://www.googleapis.com/auth/cloud-platform
```

Use `--scopes` to override the default OAuth2 scope (`https://www.googleapis.com/auth/cloud-platform`) with a comma-separated list of specific scopes.

## Run with gcloud access

```bash
moat run --grant gcloud ./my-project
```

Or in `moat.yaml`:

```yaml
grants:
- gcloud
```

## How it works

1. `moat grant gcloud` stores the project ID and credential configuration (not tokens) in the encrypted credential store
2. When a run starts, the proxy daemon creates a metadata server emulator for that run
3. The container's `HTTP_PROXY` routes requests to `metadata.google.internal` through the proxy
4. The proxy intercepts these requests and serves access tokens from the emulator
5. Access tokens are cached and refreshed 5 minutes before expiry

This is the same pattern Google Cloud uses on GCE instances and Cloud Run — the container behaves as if it is running on Google Cloud infrastructure.

## Verifying inside the container

```bash
# Check the metadata server responds
curl -s -H "Metadata-Flavor: Google" \
http://metadata.google.internal/computeMetadata/v1/project/project-id

# Fetch an access token
curl -s -H "Metadata-Flavor: Google" \
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token

# Use gcloud normally
gcloud projects list --limit=1
```

## Limitations

- **ID tokens** (`audience=...` parameter) are not supported. The metadata emulator returns 404 for identity token requests.
- **Workload Identity Federation** from non-GCP external sources works if the host's ADC file is configured for it, but there is no dedicated CLI UX.
1 change: 1 addition & 0 deletions docs/content/reference/02-moat-yaml.md
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,7 @@ grants:

| Format | Description |
|--------|-------------|
| `gcloud` | Google Cloud (gcloud CLI + client libraries) |
| `github` | GitHub API |
| `anthropic` | Anthropic API |
| `openai` | OpenAI API |
Expand Down
79 changes: 78 additions & 1 deletion docs/content/reference/04-grants.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: "Grants reference"
navTitle: "Grants"
description: "Complete reference for Moat grant types: supported providers, host matching, credential sources, and configuration."
keywords: ["moat", "grants", "credentials", "github", "anthropic", "aws", "ssh", "openai", "npm", "graphite", "meta", "facebook", "instagram", "gitlab", "brave-search", "elevenlabs", "linear", "vercel", "sentry", "datadog"]
keywords: ["moat", "grants", "credentials", "github", "anthropic", "aws", "gcloud", "google cloud", "gcp", "ssh", "openai", "npm", "graphite", "meta", "facebook", "instagram", "gitlab", "brave-search", "elevenlabs", "linear", "vercel", "sentry", "datadog"]
---

# Grants reference
Expand All @@ -24,6 +24,7 @@ Store a credential with `moat grant <provider>`, then use it in runs with `--gra
| `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 |
| `gcloud` | GCE metadata emulation (not header injection) | Access token via metadata endpoint | Application Default Credentials, service account key, or impersonation |
| `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 |
Expand Down Expand Up @@ -596,6 +597,82 @@ AWS credential saved
$ moat run --grant aws ./my-project
```

## Google Cloud (gcloud)

### CLI command

```bash
moat grant gcloud [flags]
```

### Flags

| Flag | Description | Default |
|------|-------------|---------|
| `--project ID` | GCP project ID | From `gcloud config get-value project` |
| `--key-file PATH` | Path to a service account JSON key file on the host | -- |
| `--impersonate-service-account EMAIL` | Service account to impersonate via IAM | -- |
| `--scopes SCOPES` | Comma-separated OAuth scopes | `https://www.googleapis.com/auth/cloud-platform` |

### Credential source

Moat uses your host's Application Default Credentials to mint short-lived access tokens. Your host must have valid credentials configured via one of:

1. **`gcloud auth application-default login`** — user OAuth credentials
2. **`GOOGLE_APPLICATION_CREDENTIALS`** — path to a service account JSON key
3. **`--key-file`** — explicit service account key passed at grant time
4. **Service account impersonation** — `--impersonate-service-account` delegates via IAM

### What it injects

Google Cloud credentials use metadata server emulation rather than HTTP header injection:

1. The project ID and configuration are stored at grant time (not access tokens)
2. When a run starts, the proxy serves a GCE metadata server emulator
3. The container's `HTTP_PROXY` routes requests to `metadata.google.internal` through the proxy
4. The gcloud CLI and all Google client libraries fetch access tokens from the emulated metadata endpoint
5. Access tokens are short-lived and refreshed automatically by the proxy daemon

No long-lived credentials (refresh tokens, service account keys) enter the container.

### Container environment

The following environment variables are set in the container:

| Variable | Value |
|----------|-------|
| `GOOGLE_CLOUD_PROJECT` | Configured project ID |
| `CLOUDSDK_CORE_PROJECT` | Configured project ID |

### Refresh behavior

The proxy daemon caches access tokens and refreshes them 5 minutes before expiry. Token refresh is transparent to the container.

### moat.yaml

```yaml
grants:
- gcloud
```

> **Note:** gcloud-specific options (project, key file, impersonation, scopes) are configured at grant time with `moat grant gcloud`, not in `moat.yaml`.

### Example

```bash
$ moat grant gcloud --project my-project

gcloud credential saved

$ moat run --grant gcloud ./my-project
```

### Troubleshooting

**"no host credentials found"** — Run `gcloud auth application-default login` on the host, or set `GOOGLE_APPLICATION_CREDENTIALS` to a service account key file.

**"refresh token revoked or expired"** — Re-authenticate with `gcloud auth application-default login`.

## SSH

### CLI command
Expand Down
Loading
Loading