Skip to content

Releases

v0.6.0

Choose a tag to compare

@github-actions github-actions released this 06 Nov 18:55
e579c66

Prewarmed github.com/gke-demos/kode-gopher/sdk/gkeutil GKE diagnostic helpers (DNS control-plane dialing, Kubernetes object/secret scrubbing, and Drain log template clustering), the skills/gke-diagnostics agent skill pack, configurable OAUTH_SECRET_NAME in scripts/deploy-gke-server.sh for zero-downtime OAuth client rotation with rollback, and github.com/klauspost/compress v1.20.2 in the sandbox prewarm module. Redeploy to pick up the new server and sandbox images; the deploy script refreshes the warm pool automatically. Documentation: https://gke-demos.github.io/kode-gopher/.

Security

  • Bump github.com/klauspost/compress to v1.20.2 in the sandbox prewarm module. Resolves GO-2026-6865 (panic on two-block frames in github.com/klauspost/compress).

Added

  • Configurable OAUTH_SECRET_NAME in scripts/deploy-gke-server.sh. Sets the Kubernetes Secret name in codemode (default kode-gopher-oauth) mounted by --auth=oauth deployments. When creating a new Secret while kode-gopher-oauth already exists, the script seeds keyring and clients.json from kode-gopher-oauth so existing tokens and client_credentials clients stay valid while keeping kode-gopher-oauth untouched for rollback.
  • skills/gke-diagnostics agent skill pack for GKE incident triage and cloud capacity checks. Adds progressive-disclosure skill instructions (skills/gke-diagnostics/SKILL.md plus references/workload-bundle.md and references/gke-cloud-checks.md) adapted from go-steer/k8s-lookout and paired with github.com/gke-demos/kode-gopher/sdk/gkeutil: untrusted cluster-text prompt-injection guardrails, a triage decision tree, single-round-trip workload + ConfigMap/Secret/EndpointSlice edge + Drain log correlation, and cloud.google.com/go/logging/logadmin recipes for cluster-autoscaler-visibility (GCE_STOCKOUT vs. GCE_QUOTA_EXCEEDED vs. IP_SPACE_EXHAUSTED), GCE ZONE_RESOURCE_POOL_EXHAUSTED operation-start machine-type joins, and secondary Pod CIDR exhaustion math. (#82)
  • Prewarmed github.com/gke-demos/kode-gopher/sdk/gkeutil helper package. Snippets now have zero-tidy access (tidied: false, plus offline lookup_package_docs coverage) to three self-contained GKE diagnostic helpers distilled from go-steer/k8s-lookout without adding external module dependencies: DialClientset / DialRESTConfig / RESTConfigFromCluster (one-call GKE DNS control-plane client-go setup with IP+CA fallback), ScrubPodList / ScrubPod / ScrubNode / ScrubObject (stripping managedFields, applied-configuration snapshots, and Node.Status.Images while redacting literal env and Secret values and preserving secretKeyRef / configMapKeyRef names), and ClusterLogs / NewDrainer (Drain-style <*> log template clustering). (#81)

v0.5.0

Choose a tag to compare

@github-actions github-actions released this 05 Nov 14:53
2e657c0

Single-hop sandbox execution (POST /run-go), batched lookup_package_docs across the full prewarmed module graph, cross-call session scratch storage in /app/persist, multimodal MCP ImageContent attachments, opt-in read-only Google Cloud scope downscoping (--read-only / read_only), and compact result output. Redeploy to pick up the new server and sandbox images; the deploy script refreshes the warm pool automatically. Documentation: https://gke-demos.github.io/kode-gopher/.

Added

  • lookup_package_docs covers every module in the sandbox's prewarmed lockfile and accepts batched queries. Lookups now work for transitive modules already cached in the sandbox image — including k8s.io/api/* (core/v1, apps/v1, batch/v1), cloud.google.com/go/iam, cloud.google.com/go/civil, cloud.google.com/go/longrunning, google.golang.org/protobuf/types/known/*, google.golang.org/genproto/..., and google.golang.org/grpc/... — while still rejecting uncached sibling cloud.google.com/go/* service modules upfront. Callers can also pass queries (up to 10 {package, symbol?} items) to inspect multiple types in one sandbox round-trip. golang.org/x/oauth2/google, golang.org/x/sync/errgroup, google.golang.org/protobuf/types/known/timestamppb, k8s.io/api/apps/v1, and k8s.io/api/core/v1 are promoted to direct precompiled packages so snippets importing them never trigger go mod tidy. (#66)
  • Cross-call session scratch directory (/app/persist). Session.Reset now preserves /app/persist (pre-created and writable before each run, and ignored by go mod tidy) while continuing to wipe source, build, result, and forwarded credential files in /app between execute_go_code calls. Multi-turn investigations within an MCP session can cache intermediate API or query datasets on disk and return compact summaries instead of re-querying APIs or hitting the 256 KiB result limit. (#67)
  • Multimodal ImageContent attachments from sandbox runs. Snippets and full programs can write image/png, image/jpeg, and image/webp files under /app/.kode-gopher/attachments/ (up to 8 regular files and 2 MiB total raw bytes per run, verified by binary magic signature). execute_go_code fetches them, appends native MCP ImageContent blocks to CallToolResult.Content, and reports {name, mime_type, bytes} metadata on ExecuteGoCodeOutput.attachments (as well as in text output and kode-gopher exec), while surfacing symlinks, special files, unsupported formats, or oversized files in warnings. (#71)
  • Opt-in read-only Google Cloud credential downscoping (--read-only and execute_go_code read_only). kode-gopher exec --read-only, kode-gopher serve --read-only, and per-call execute_go_code read_only: true restrict the short-lived Google token served by the sandbox's metadata emulator to read-only OAuth scopes (cloud-platform.read-only, devstorage.read_only, bigquery.readonly, logging.read, monitoring.read, trace.readonly), reporting those scopes from the metadata emulator and read_only: true from gcp_auth_status when pinned server-wide. Supported for --credentials=service, --credentials=access-token with service_account / impersonated_service_account / external_account ADC JSON, and --auth=oauth --read-only (vault custody, sealed custody, and client_credentials). Modes that cannot narrow OAuth scopes (forwarded ADC files, GCE/GKE metadata ADC, authorized_user refresh tokens, or per-call read-only: true on a full-scope OAuth server) fail closed rather than forwarding full-scope credentials. Deploy note: enabling or disabling --read-only on an existing --auth=oauth server rejects in-flight sign-in cookies and previously issued kode-gopher access and refresh tokens so clients re-authenticate under the matching scope policy (deploys that keep --read-only unchanged leave in-flight sign-ins and tokens intact); under vault custody, use a dedicated vault auth provider (or revoke prior write grants) if users previously consented to write scopes on the same provider. (#70)

Changed

  • Compact MCP result text and result-projection guidance. execute_go_code now encodes the ── result ── block in CallToolResult.Content as compact single-line JSON instead of 2-space indented JSON (reducing text token overhead on nested slices and maps while keeping CLI kode-gopher exec output indented), and internal/prompts/system.md plus execute_go_code's tool description explicitly guide snippets to project and summarize responses rather than returning raw *pb or Kubernetes managedFields structs that exceed the 256 KiB result cap. (#69)
  • Single-hop sandbox execution (POST /run-go). sandbox-server now exposes POST /run-go to perform workdir reset (preserving /app/persist), multi-file writes via os.Root, build, credentialed execution, and .kode-gopher/result.json readback in one HTTP round-trip instead of four to six sequential /execute and /upload calls (eliminating tar archiving and extra shell spawns while keeping build isolated from runtime credentials and environment variables, and fetching .kode-gopher/attachments/ only when present). kode-gopher automatically falls back to the multi-hop protocol if connected to an older sandbox image that returns 404. (#68)

v0.4.1

Choose a tag to compare

@github-actions github-actions released this 01 Nov 21:48
v0.4.1
3503932

This release stops active sign-ins under the credential vault from going back to browser sign-in every hour. A client's hourly refresh used to force-refresh a Google token that was still valid, and the vault answers that by asking for consent, so the refresh failed. kode-gopher now never forces a token Google still accepts and waits out a nearly spent one (up to about 93 s) before the refresh token rotates, so a front end needs a request timeout of at least 2 minutes (the GKE overlay's Gateway allows 900 s). Ported from go-steer/steward#22 and #23.

Fixed

  • An active client's hourly refresh under the credential vault no longer fails or signs the user out. kode-gopher force-refreshed any vault token with under 5 minutes left, but the vault answers a forced refresh of a token that's still valid by asking for consent, and a client's refresh lands when the Google token has about a minute left (its access token stops a minute before the Google token). That refresh got server_error, which the MCP TypeScript SDK answers with a new browser sign-in, so users signed in again every hour. Now a vault token is forced only when Google's tokeninfo refuses it (400 invalid_token). A valid token is used while it has 75 seconds left at refresh, or 90 at sign-in; one with less is waited out (up to about 78 seconds at refresh and 93 at sign-in, cancelled if the client goes away) and the vault asked again. The wait runs before the refresh token is spent, so a client that gives up during it can retry with the same token. Two refreshes with one token at once still revoke the family, as before: kode-gopher has no reuse grace. A replayed refresh token now gets the vault work, and any wait, before it's caught, and while the vault or Google is failing it gets server_error rather than revoking its family. Consent to a forced refresh is checked once more with a plain retrieve before it signs anyone out. Deploy note: a proxy or load balancer in front of kode-gopher needs a request timeout of at least 2 minutes; the GKE Gateway the deploy script creates allows 900 seconds. Ported from go-steer/steward#23.
  • A refresh that fails for a transient reason gives back only its own rotation. A refresh moves its sign-in's refresh-token family on a generation first, and moves it back if it then fails with server_error, so the client's retry isn't taken for a reuse. The move back checked only the generation, so if it ever undid another refresh's rotation, the client's retry would rotate the family again and two refresh tokens of the next generation would both work. Each rotation now carries a random ID, and the move back undoes only the rotation with its own ID; a rotation delivered twice counts once. The in-memory replay cache of a single replica can't reach the bad case today, so nothing changes for a deployment now: this is internal hardening, and the contract a shared cache for multiple replicas will need. Ported from go-steer/steward#22.

v0.4.0

Choose a tag to compare

@github-actions github-actions released this 27 Oct 13:54
a5e0846

Google group allow lists on GKE across every Workspace edition, OAuth 2.1 and credential vault hardening ported from go-steer/steward's security review, configurable static IP and certificate names for multiple deployments in one project, and Go 1.26.10 with golang.org/x/net v0.60.0. Documentation: https://gke-demos.github.io/kode-gopher/.

Security

  • Go 1.26.10 and golang.org/x/net v0.60.0. The server, the sandbox image and the prewarmed modules move off go1.26.8 and x/net v0.59.0 (v0.58.0 in the sandbox image), which have known net/http and HTTP/2 vulnerabilities (GO-2026-6603, GO-2026-6610 to GO-2026-6613, GO-2026-6617). Go 1.26.10 also distrusts compromised intermediate certificates in crypto/x509 (GO-2026-6809). Redeploy to pick up the new server and sandbox images.
  • A client's name can no longer disguise its client ID or redirect host on kode-gopher's consent page. A dynamically registered or CIMD client chooses its own client_name, and the page showed it as name (client_id) in one element, so a name with a right-to-left override or a bidi isolate could visually reverse the client ID and the redirect host after it, and a short name could carry a fake (https://...) of its own. Names are now cleaned at registration, when a client is looked up (covering registrations made before the upgrade) and on the page: control and format characters, bidi controls, zero-width and other invisible, private-use and unassigned characters are dropped, whitespace and newlines collapse to one space, stacked accents are capped, and the 100-byte cut happens after cleaning. The page shows the name in an element of its own ("an MCP client calling itself ..."), and the CIMD client ID and the redirect host each in their own, with anything outside printable ASCII percent-encoded. A dynamically registered client gets a line saying nothing vouches for its name. Pre-registered clients' names are cleaned the same way in the log, and a nameless CIMD client's ID is logged escaped the same way. Ported from go-steer/steward#17.

Added

  • Group allow lists work on GKE. serve --oauth-groups-service-account (GROUPS_SERVICE_ACCOUNT for the deploy script) checks --oauth-allow-groups as a service account that kode-gopher impersonates for that call. That service account holds the Groups Reader admin role. kode-gopher's own Workload Identity principal has no email, so it can't be assigned an admin role. On Workspace editions without Cloud Identity's transitive membership check (Business Starter, Standard), kode-gopher walks nested groups with the membership list instead. (#36)
  • Multiple deployments in one GCP project. scripts/deploy-gke-server.sh (and scripts/smoketest-http.sh) now take ADDRESS_NAME and CERT_NAME environment variables, defaulting to kode-gopher-ip and kode-gopher-cert, and patch the Gateway accordingly so two clusters in the same project don't share the same global static IP or certificate name.

Fixed

  • Starting sign-in twice in one browser no longer fails both. Each start replaced the browser's sign-in cookie, so an earlier tab, or a copied link opened next to the client's own tab, ended on "Sign-in expired". The cookie is now kept across starts, and renewed when you approve kode-gopher's page, and on https it's named __Host-kg_auth so it can't be planted. Two sign-ins that both reach the vault's own consent screen still collide; finish one first. A sign-in in progress during the upgrade fails once, since the cookie's name changes. When a sign-in can't continue, the page and the server log now say why: the link is unreadable, it's more than 10 minutes old, this browser didn't start it, or this browser started a different one.
  • Group allow lists: three fixes from the review of go-steer/steward's copy of this code. An address with an apostrophe was checked as if it had none, so o'brien@ got in when obrien@ was the member. Addresses with a quote or backslash are now matched exactly against the group's member list. On editions with the transitive membership check, a group deleted and recreated under the same address refused its members for up to an hour, because kode-gopher remembered the old group. A "not found" now looks the group up again first. And a group's member list is read for at most 100 pages (up to 20,000 members); past that the check is incomplete and fails with server_error, instead of paging without end.
  • A recreated allow-listed group is looked up again on a 403 too, and on every edition. The fix above for a group deleted and recreated under the same address covered only a 404 from the transitive membership check. kode-gopher remembers a group's Cloud Identity name for an hour, and Cloud Identity can answer the old name with 403 "Permission denied ... (or it may not exist)" rather than 404. A 403 from the transitive check sent kode-gopher to the member-list walk, which failed on the old name, and on editions without the transitive check (or for an address with a quote or backslash) the walk's 403 or 404 was never retried. Members got server_error at sign-in and refresh, never a refusal, until the name expired. Now a 403 or 404 about the group itself on a remembered name, on either path, looks the group up again once and retries. A 403 from the transitive check on a freshly looked-up name still means the edition lacks it, a 5xx or 429 never counts as a stale name, and a check costs at most one extra lookup. Still not covered: a member list cached under the old name answers as the old group did for up to 5 minutes, and a recreated nested group gives server_error to accounts not found elsewhere until its name expires. Ported from go-steer/steward#16.
  • A failed allow-list check during a token refresh no longer signs the user out. The refresh token was marked used before the check, so after a transient failure (server_error) the client's retry was refused as a replay. Transient failures now leave the token usable.
  • A busy credential vault no longer signs people out. When the vault answered a token refresh with pending, or with nothing usable, kode-gopher treated it as a grant only the user could repair and answered invalid_grant, so the client signed the user out. It now answers server_error and the refresh token still works for the retry; at sign-in it's server_error, not access_denied. The vault's API reference says to retry a pending answer. Ported from go-steer/steward#20.
  • A nearly spent vault token no longer signs people out. A vault token with under 5 minutes left is force-refreshed, and the vault answers a forced refresh of a token that's still valid by asking for consent, which was invalid_grant at refresh and access_denied at sign-in. When forcing a valid token brings no better one, short of a refusal, that token is now used if it has 3 minutes left, and otherwise the answer is server_error. Ported from go-steer/steward#20.
  • A vault token Google refuses is treated as refused, after one forced refresh. A 400 invalid_token from Google's tokeninfo was server_error, however often the client retried. The token is now force-refreshed once, since it may have expired before the vault's expireTime. If the vault then asks for consent, sign-in goes through the vault's consent screen and a refresh answers invalid_grant; if Google refuses the new token too, sign-in answers access_denied and a refresh invalid_grant. Other tokeninfo failures stay server_error, including a 400 without Google's invalid_token, from a proxy, say. A vault error that quotes the forced token back has the token replaced before it's logged. Ported from go-steer/steward#20.
  • A vault or Google outage during sign-in is reported as one. The client was told access_denied, as if the user had been refused, so it gave up. It now gets server_error, and trying again can work. A grant only the user can repair (consent declined, another account's grant, the Google Cloud scope left unticked) is still access_denied. Found in go-steer/steward's review of its port of this code.
  • Sign-in errors say less, and say it in plain ASCII. A refusal by the allow list no longer names the account in the redirect to the client, and an error Google returns at sign-in is logged instead of copied into it. Every error_description, from /authorize, /token or /register, keeps to the characters OAuth allows, so a quote or a non-ASCII character in the text it quotes can't trip up a client reading it. Found in go-steer/steward's review.
  • A request naming more than one resource is refused. /authorize and /token checked only the first resource and ignored the rest; they now answer invalid_target, as RFC 8707 expects of a server that issues tokens for one resource. Found in go-steer/steward's review.
  • A reused refresh token now signs that sign-in out. Using a refresh token a second time used to fail only that request, so if someone else had copied the token and refreshed first, they kept a working chain of tokens. Now reusing a token revokes every token descended from the same sign-in, and the user signs in again. A client that sends two refreshes at once with the same token is signed out too. The server also remembers one entry per sign-in instead of one per refresh. Refresh tokens issued before the upgrade keep working. Found by go-steer/steward's review of this code.
  • A changed Google email address now needs a new sign-in. kode-gopher kept using the email from sign-in, and the allow-list decision made on it, for as long as the client kept refreshing. Now a refresh notice...
Read more

v0.3.1

Choose a tag to compare

@github-actions github-actions released this 06 Oct 09:14
v0.3.1
10408cf

A security patch: the sandbox image's dependencies are updated to fix six known vulnerabilities. Redeploy to pick up the new sandbox image; the deploy script replaces the warm pool's old sandboxes itself.

Security

  • The sandbox image's dependencies are patched and scanned. The prewarmed modules every snippet builds against carried six known vulnerabilities (gRPC, OpenTelemetry SDK, golang.org/x/text). They're bumped to fixed versions: gRPC v1.83.2, OpenTelemetry v1.45.0, x/text v0.41.0. CI now runs govulncheck (package level, with the image's Go version) and go mod tidy -diff on internal/prewarm too. Before, it only checked the root module.

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 03 Oct 08:55
v0.3.0
4c03972

Sandboxes on a shared server now reach only an allowlist of hosts, and deploys are more reliable. Upgrading a shared server needs one cluster change first: gcloud container clusters update <cluster> --location <location> --enable-fqdn-network-policy. On Autopilot, GKE then restarts the nodes over a few hours. Documentation: https://gke-demos.github.io/kode-gopher/.

Security

  • Sandboxes on a shared server reach only an allowlist of hosts. They used to reach any public IP address. The gke-server overlays now allow DNS, plus port 443 to *.googleapis.com, the Go module proxy and the cluster region's GKE control-plane endpoints, through a GKE FQDN network policy (manifests/components/egress-allowlist). EGRESS_ALLOW adds hosts. The cluster needs --enable-fqdn-network-policy, and scripts/deploy-gke-server.sh checks for it. Local use (kind, the plain gke overlay) is unchanged.

Fixed

  • Secret changes now reach kode-gopher on redeploy. It reads its Secret only at startup, and the deploy script didn't restart it when only the Secret changed. So a rotated static token, a new CLIENTS_FILE or a new Google client file had no effect until something else changed the pod. The deploy script now hashes the Secret into the pod template.
  • A redeploy without CLIENTS_FILE keeps the pre-registered clients. It used to reset them to none, unlike the keyring and Google client file, which it kept.
  • Deploying a new sandbox image reaches new sessions straight away. agent-sandbox doesn't replace a warm pool's unclaimed sandboxes when the template changes, so after an upgrade new sessions kept getting old-image sandboxes. scripts/deploy-gke-server.sh now replaces them when their image differs from the template's, and waits for one on the new image, through the new scripts/refresh-warm-pool.sh. The kind scripts use it too. (#41)

v0.2.0

Choose a tag to compare

@github-actions github-actions released this 02 Oct 21:43
v0.2.0
c156795

Precompiled clients for troubleshooting, and local sandboxes that no longer outlive a killed process. Documentation: https://gke-demos.github.io/kode-gopher/.

Added

  • Cloud Logging, Monitoring and Trace clients are precompiled in the sandbox image (cloud.google.com/go/logging/logadmin, monitoring/apiv3/v2, trace/apiv1), so troubleshooting snippets build in seconds without go mod tidy. lookup_package_docs now serves docs for any package in a precompiled module, including the *pb request types (monitoringpb, tracepb) that their calls need, and the model is told those packages are compiled too. (#27)

Fixed

  • A killed local kode-gopher no longer leaks its sandbox. Sandbox claims from serve over stdio and from exec now carry the same renewed lease as the HTTP server's (--claim-lease, 10 minutes by default). A process that dies without a clean shutdown loses its sandbox within one lease, instead of holding it forever. serve --persistent and exec --keep stay lease-free, so their sandboxes outlive the process. Local users need permission to patch SandboxClaims. (#34)

v0.1.1

Choose a tag to compare

@github-actions github-actions released this 02 Oct 17:28
v0.1.1
33c738c

The first published release of kode-gopher: an MCP server (and CLI) that runs Go an AI agent writes against the real Google Cloud SDKs in a sandboxed Kubernetes pod, locally over stdio or as a shared server in GKE with Google sign-in. Pre-alpha. It's 0.1.0, whose release failed before publishing, plus the signing fix below; the full feature list is in the 0.1.0 notes. Documentation: https://gke-demos.github.io/kode-gopher/.

Fixed

  • Release signing works with cosign 3. The checksums.txt signature is now a Sigstore bundle, checksums.txt.sigstore.json, replacing checksums.txt.sig and checksums.txt.pem. Verify it with cosign verify-blob --bundle; see docs/release-process.md. The v0.1.0 release failed at this step before anything was published. The release workflow's dry run now signs and verifies too.