{"id":45533621,"url":"https://github.com/samn/gcp-authcalator","last_synced_at":"2026-02-23T01:33:16.284Z","repository":{"id":339065132,"uuid":"1154814784","full_name":"samn/gcp-authcalator","owner":"samn","description":null,"archived":false,"fork":false,"pushed_at":"2026-02-18T00:02:36.000Z","size":214,"stargazers_count":0,"open_issues_count":3,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-18T01:27:31.236Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/samn.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-02-10T20:05:25.000Z","updated_at":"2026-02-17T20:18:43.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/samn/gcp-authcalator","commit_stats":null,"previous_names":["samn/gcp-authcalator"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/samn/gcp-authcalator","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samn%2Fgcp-authcalator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samn%2Fgcp-authcalator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samn%2Fgcp-authcalator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samn%2Fgcp-authcalator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/samn","download_url":"https://codeload.github.com/samn/gcp-authcalator/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samn%2Fgcp-authcalator/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29734468,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-22T20:09:16.275Z","status":"ssl_error","status_checked_at":"2026-02-22T20:09:13.750Z","response_time":110,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-02-23T01:33:15.494Z","updated_at":"2026-02-23T01:33:16.249Z","avatar_url":"https://github.com/samn.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# gcp-authcalator\n\nA GCP auth escalator for containerized development environments.\nKeeps GCP credentials out of devcontainers and AI coding agents by proxying token requests through a host-side daemon with confirmation dialogs for production access.\n\n## Why\n\nModern IDEs encourage running AI coding agents in the same devcontainer the engineer works in. This is convenient — but it means every process inside the container, including unattended agents, has the same GCP credentials as the engineer. A single compromised dependency, a prompt-injection attack, or a malicious tool can silently use those credentials to write to production databases, decrypt secrets, or exfiltrate data.\n\nThe core problem is that `google.auth.default()` returns the engineer's full-privilege credentials to **any** process. There is no privilege boundary between the engineer's interactive session and automated tooling.\n\ngcp-authcalator solves this by keeping credentials on the host and making the container ask for them:\n\n1. A **token daemon** (`gate`) runs on the host and holds the engineer's Application Default Credentials. It mints short-lived, downscoped tokens via service account impersonation — never handing out the root credentials.\n2. A **metadata server emulator** (`metadata-proxy`) runs inside the container, serving those downscoped tokens transparently to all Google Cloud client libraries. No application code changes needed.\n3. **Production-level access requires explicit human confirmation** — a desktop dialog or terminal prompt on the host — so no automated process can silently escalate privileges.\n\nCredentials never enter Docker. The Unix socket is the only channel, and the host daemon controls what tokens are issued.\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────┐\n│ Host Machine                                │\n│                                             │\n│  ~/.config/gcloud/  ──▶  gcp-gate daemon    │\n│  (engineer creds)        (Unix socket)      │\n│                          ├─ confirmation UI │\n│                          └─ audit log       │\n└──────────────────┬──────────────────────────┘\n                   │ $XDG_RUNTIME_DIR/gcp-authcalator.sock\n┌──────────────────┴──────────────────────────┐\n│ devcontainer                                │\n│                                             │\n│  gcp-metadata-proxy (127.0.0.1:8173)        │\n│       ▲                                     │\n│       │ GCE_METADATA_HOST                   │\n│  app / agent / tests                        │\n│                                             │\n│  with-prod ──▶ temp proxy (random port)     │\n│                    ▲                        │\n│                    │ GCE_METADATA_HOST      │\n│               elevated process              │\n└─────────────────────────────────────────────┘\n```\n\n## Prerequisites\n\nBefore using gcp-authcalator, set up GCP IAM:\n\n1. **Create a service account** with limited permissions for development (e.g., `dev-runner@\u003cproject\u003e.iam.gserviceaccount.com`)\n2. **Grant developers** the `roles/iam.serviceAccountTokenCreator` role on that service account\n3. **Authenticate on the host** with `gcloud auth application-default login` so that Application Default Credentials (ADC) are available\n\nThe host-side `gate` daemon uses ADC to impersonate the service account via [`generateAccessToken`](https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateAccessToken), producing short-lived tokens (1-hour TTL).\n\n## Installation\n\n### From releases\n\nDownload a prebuilt binary from the [GitHub Releases](https://github.com/samn/gcp-authcalator/releases) page:\n\n| Platform     | Binary                         |\n| ------------ | ------------------------------ |\n| Linux x86_64 | `gcp-authcalator-linux-amd64`  |\n| macOS ARM64  | `gcp-authcalator-darwin-arm64` |\n\nEach release includes SHA256 checksums for verification.\n\n### From source\n\n```bash\nmise install\nbun install\nbun run build\n```\n\nThis produces a single compiled `gcp-authcalator` binary.\n\n## Configuration\n\nSettings can be provided via CLI flags, a TOML config file, or both.\nCLI flags take precedence over the config file, which takes precedence over defaults.\n\n### CLI flags\n\n```\n--project-id \u003cid\u003e          GCP project ID\n--service-account \u003cemail\u003e  Service account email to impersonate\n--socket-path \u003cpath\u003e       Unix socket path (default: $XDG_RUNTIME_DIR/gcp-authcalator.sock)\n-p, --port \u003cport\u003e          Metadata proxy port (default: 8173)\n-c, --config \u003cpath\u003e        Path to TOML config file\n```\n\n### TOML config file\n\n```toml\nproject_id = \"my-gcp-project\"\nservice_account = \"dev-runner@my-gcp-project.iam.gserviceaccount.com\"\n# socket_path defaults to $XDG_RUNTIME_DIR/gcp-authcalator.sock\n# (or ~/.gcp-authcalator/gcp-authcalator.sock if XDG_RUNTIME_DIR is unset)\nport = 8173\n```\n\nPass the file with `--config`:\n\n```bash\ngcp-authcalator gate --config config.toml\n```\n\n## Commands\n\n### `gate` — Host-side token daemon\n\nRuns on the **host machine**. Listens on a Unix domain socket and mints GCP access tokens.\n\n```bash\ngcp-authcalator gate \\\n  --project-id my-project \\\n  --service-account dev-runner@my-project.iam.gserviceaccount.com\n```\n\n**Required options:** `--project-id`, `--service-account`\n\n**API endpoints** (over Unix socket):\n\n| Endpoint                | Behavior                                                         |\n| ----------------------- | ---------------------------------------------------------------- |\n| `GET /token`            | Returns a dev-scoped access token (impersonated service account) |\n| `GET /token?level=prod` | Prompts for confirmation, then returns the engineer's own token  |\n| `GET /identity`         | Returns the authenticated user's email                           |\n| `GET /project-number`   | Returns the numeric GCP project ID                               |\n| `GET /universe-domain`  | Returns the GCP universe domain                                  |\n| `GET /health`           | Returns `{ \"status\": \"ok\", \"uptime_seconds\": N }`                |\n\n**Dev tokens** are minted by impersonating the configured service account. They are cached and re-minted when less than 5 minutes of lifetime remain.\n\n**Prod tokens** use the engineer's own ADC credentials. Before issuing a prod token, the daemon:\n\n1. Shows a desktop confirmation dialog (`osascript` on macOS, `zenity` on Linux)\n2. Falls back to a terminal prompt if no GUI is available\n3. Denies access if no interactive method is available\n\nProd token requests are rate-limited: one confirmation dialog at a time, a 5-second cooldown after denial, and a maximum of 5 attempts per minute.\n\n**Audit logging:** All token requests are logged as JSON lines to the runtime directory's `audit.log` (`$XDG_RUNTIME_DIR/audit.log` or `~/.gcp-authcalator/audit.log`).\n\n### `metadata-proxy` — Container-side metadata emulator\n\nRuns **inside the devcontainer**. Emulates the [GCE metadata server](https://cloud.google.com/compute/docs/metadata/overview) so that all Google Cloud client libraries transparently fetch tokens from the proxy.\n\n```bash\ngcp-authcalator metadata-proxy --project-id my-project\n```\n\n**Required options:** `--project-id`\n\nSet `GCE_METADATA_HOST=127.0.0.1:8173 GCE_METADATA_IP=127.0.0.1:8173` in the container environment so client libraries discover the proxy automatically.\n\n**Endpoints:**\n\n| Path                                                              | Response                               | `Metadata-Flavor: Google` required? |\n| ----------------------------------------------------------------- | -------------------------------------- | ----------------------------------- |\n| `GET /`                                                           | `200 ok` (detection ping)              | No                                  |\n| `GET /computeMetadata/v1/instance/service-accounts/default/token` | Token JSON                             | Yes                                 |\n| `GET /computeMetadata/v1/project/project-id`                      | Plain text project ID                  | Yes                                 |\n| `GET /computeMetadata/v1/project/numeric-project-id`              | Plain text numeric project ID          | Yes                                 |\n| `GET /computeMetadata/v1/universe/universe_domain`                | Plain text universe domain             | Yes                                 |\n| `GET /computeMetadata/v1/instance/service-accounts/default/email` | Plain text SA email                    | Yes                                 |\n| `GET /computeMetadata/v1/instance/service-accounts/default`       | SA info (JSON or directory listing)    | Yes                                 |\n| `GET /computeMetadata/v1/instance/service-accounts`               | SA listing (JSON or directory listing) | Yes                                 |\n\nEndpoints returning \"JSON or directory listing\" respond with JSON when `?recursive=true` is passed, and a text directory listing otherwise. This matches real GCE metadata server behavior.\n\nService account paths that use an email identifier (e.g., `.../service-accounts/sa@project.iam.gserviceaccount.com/token`) are automatically aliased to `default`, since the proxy serves a single set of credentials. This ensures compatibility with `gcloud` and other client libraries that resolve accounts by email.\n\nThe proxy fetches tokens from the `gate` daemon via the Unix socket and caches them locally, re-fetching when less than 5 minutes of lifetime remain.\n\n### `with-prod` — Elevation wrapper\n\nWraps a shell command with production-level GCP credentials. Runs **inside the devcontainer**.\n\n```bash\ngcp-authcalator with-prod -- python some/script.py\ngcp-authcalator with-prod -- gcloud sql instances list\ngcp-authcalator with-prod -- alembic upgrade head\n```\n\n**Required options:** `--project-id`\n\nThis command:\n\n1. Requests a prod token from `gate` (triggers a host-side confirmation dialog)\n2. Starts a temporary metadata proxy on a random port serving that token\n3. Creates an isolated `CLOUDSDK_CONFIG` directory so `gcloud` doesn't reuse cached credentials\n4. Strips credential-related environment variables (`GOOGLE_APPLICATION_CREDENTIALS`, `CLOUDSDK_AUTH_ACCESS_TOKEN`, etc.) to force the child through the proxy\n5. Spawns the wrapped command with `GCE_METADATA_HOST` and `GCE_METADATA_IP` pointing at the temporary proxy\n6. Forwards signals to the child process and propagates its exit code\n\nThe temporary proxy uses PID-based process restriction — only the wrapped command and its descendants can request tokens from it.\n\n### `kube-setup` — Patch kubeconfig for GKE\n\nOne-time setup command that patches your kubeconfig to use gcp-authcalator instead of `gke-gcloud-auth-plugin` for GKE cluster authentication.\n\n```bash\ngcp-authcalator kube-setup\n```\n\nThis command:\n\n1. Reads the kubeconfig (from `$KUBECONFIG` or `~/.kube/config`)\n2. Finds all users with `exec.command: gke-gcloud-auth-plugin` (including full paths)\n3. Replaces the exec section to point to `gcp-authcalator kube-token`\n4. Creates a backup at `\u003ckubeconfig\u003e.bak`\n5. Writes the patched kubeconfig back\n\nAfter patching, kubeconfig user entries will look like:\n\n```yaml\nusers:\n  - name: gke_project_region_cluster\n    user:\n      exec:\n        apiVersion: client.authentication.k8s.io/v1beta1\n        command: /absolute/path/to/gcp-authcalator\n        args: [\"kube-token\"]\n        installHint: \"Install gcp-authcalator or revert with: gcloud container clusters get-credentials \u003ccluster\u003e\"\n        provideClusterInfo: true\n```\n\nTo revert, re-run `gcloud container clusters get-credentials \u003ccluster\u003e`.\n\n### `kube-token` — kubectl credential plugin\n\nkubectl [exec credential plugin](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins) that fetches a token from the active metadata proxy and outputs an `ExecCredential` JSON for kubectl. You don't call this directly — kubectl invokes it automatically after running `kube-setup`.\n\n```bash\ngcp-authcalator kube-token\n```\n\nThe plugin reads `GCE_METADATA_HOST` from the environment (falls back to `127.0.0.1:8173`) and requests a token from that metadata proxy. This means it automatically picks up the correct token:\n\n- **Normal usage:** fetches a dev token from the default metadata proxy\n- **Under `with-prod`:** `GCE_METADATA_HOST` points to the temporary prod proxy, so kubectl transparently gets the prod token\n\nThe `expirationTimestamp` is set to ~1 second from now, which effectively disables kubectl's exec credential cache. This ensures concurrent kubectl processes (some normal, some under `with-prod`) always get the correct token. The metadata proxy already caches tokens, so the overhead is one fast localhost HTTP round-trip per kubectl API call.\n\n**Why not `gke-gcloud-auth-plugin`?** The GKE plugin caches tokens at `~/.kube/gke_gcloud_auth_plugin_cache` and ignores `CLOUDSDK_CONFIG`, so it keeps serving stale dev tokens even under `with-prod`.\n\n### `version` — Show version\n\nPrints the current version and exits.\n\n```bash\ngcp-authcalator version\ngcp-authcalator --version\n```\n\n## Devcontainer setup\n\nTo use gcp-authcalator in a devcontainer:\n\n1. **Host:** Start the `gate` daemon (e.g., in a devcontainer lifecycle script that runs on the host):\n\n   ```bash\n   gcp-authcalator gate --config /path/to/config.toml\n   ```\n\n2. **Mount the socket** into the container by adding to `devcontainer.json`.\n   The socket lives in a user-private directory — use `$XDG_RUNTIME_DIR` (typically `/run/user/$UID`) or `~/.gcp-authcalator/` if that's unset:\n\n   ```json\n   \"mounts\": [\n     \"source=${localEnv:XDG_RUNTIME_DIR}/gcp-authcalator.sock,target=${localEnv:XDG_RUNTIME_DIR}/gcp-authcalator.sock,type=bind\"\n   ]\n   ```\n\n   Make sure the container uses the same `--socket-path` as the host.\n\n3. **Container:** Start the metadata proxy (e.g., in a post-start script):\n\n   ```bash\n   gcp-authcalator metadata-proxy --project-id my-project \u0026\n   ```\n\n4. **Container:** Set the environment variables so client libraries discover the proxy:\n\n   ```json\n   \"remoteEnv\": {\n     \"GCE_METADATA_HOST\": \"127.0.0.1:8173\",\n     \"GCE_METADATA_IP\": \"127.0.0.1:8173\"\n   }\n   ```\n\n5. **Container (optional):** If you use `kubectl` with GKE, patch the kubeconfig so kubectl fetches tokens through gcp-authcalator instead of `gke-gcloud-auth-plugin`:\n\n   ```bash\n   gcloud container clusters get-credentials \u003ccluster\u003e --region \u003cregion\u003e --project \u003cproject\u003e\n   gcp-authcalator kube-setup\n   ```\n\n   This ensures `kubectl` works correctly under both normal and `with-prod` usage.\n\n## Security model\n\n### Threat model\n\ngcp-authcalator is designed for environments where a coding agent (or other untrusted automation) runs in the same devcontainer as the engineer. The goal is to ensure that **all privilege escalation requires human approval** and that **credentials are never directly accessible inside the container**.\n\n**Hard security boundaries:**\n\n- **Credentials never enter the container.** The host daemon holds ADC; the container only receives short-lived, downscoped tokens. Even if the container is fully compromised, the attacker gets only a dev service account token — not the engineer's identity.\n- **Cross-user isolation.** The Unix socket is set to `0600` (owner-only) and lives in a `0700` directory (`$XDG_RUNTIME_DIR` or `~/.gcp-authcalator/`). Processes running as other OS users cannot connect. **For strongest isolation, run coding agents as a separate OS user** — they will be unable to access the socket at all.\n- **Human-in-the-loop for production access.** Prod tokens require explicit confirmation via a desktop dialog (`osascript` on macOS, `zenity` on Linux) or terminal prompt on the host. If no interactive method is available, access is denied.\n- **Rate limiting** prevents automated brute-forcing of the confirmation flow: one dialog at a time, a 5-second cooldown after denial, and a maximum of 5 attempts per minute.\n\n**Best-effort protections** (defense in depth against same-user attacks):\n\n- **PID-based process restriction** on `with-prod` temporary proxies ensures only the intended process tree can request elevated tokens. This uses `/proc` introspection and is effective against casual abuse, but a sufficiently privileged same-user process could circumvent it.\n- **Environment isolation** in `with-prod` strips credential-related env vars (`GOOGLE_APPLICATION_CREDENTIALS`, `CLOUDSDK_AUTH_ACCESS_TOKEN`, etc.) and uses a temporary `CLOUDSDK_CONFIG` in the user-private runtime directory to prevent credential leakage around the proxy.\n- **Token files** are written with `0600` permissions in user-private directories, not passed via environment variables (which are readable via `/proc/*/environ`).\n- **Audit logging** records all token requests as JSON lines to the runtime directory, providing a trail for forensic review.\n- **Stale socket recovery** verifies socket ownership and refuses to follow symlinks, preventing TOCTOU races.\n\n**Limitations:**\n\n- A malicious process running as the **same user** with sufficient sophistication (e.g., `ptrace`, reading `/proc/*/mem`) can potentially extract tokens from a running process. Full same-user isolation requires OS-level sandboxing beyond what gcp-authcalator provides.\n- Once the engineer approves a prod token request, the elevated token is available to the approved process tree for its lifetime (~1 hour). gcp-authcalator cannot revoke it early.\n\n## Development\n\n### Setup\n\n```bash\nmise install\nbun install\nprek install\n```\n\n### Pre-commit checks\n\n```bash\nbun run format    # auto-fix formatting\nbun run lint      # run ESLint\nbun run typecheck # check types\nbun test          # run tests\n```\n\n### Building\n\n```bash\nbun run build                # build for current platform\nbun run build:linux-amd64    # cross-compile for Linux x86_64\nbun run build:darwin-arm64   # cross-compile for macOS ARM64\n```\n\nSee [docs/releasing.md](docs/releasing.md) for the release process.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamn%2Fgcp-authcalator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsamn%2Fgcp-authcalator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamn%2Fgcp-authcalator/lists"}