Ostgate security overview

Ostgate is a native macOS app that reaches Compute Engine VMs through Google's Identity-Aware Proxy (IAP). This page is for the people who approve it: network and security engineers, and the security teams of Google Cloud partners. It says where traffic goes, which identity it uses, where secrets are kept, what listens on the Mac, what we receive, and how the app is signed.

By Ostgate · Updated

In short

  • No Ostgate server is in the data path. Google API calls and IAP tunnels go from the Mac directly to Google.
  • Google's IAP and your IAM decide what can be reached. Ostgate acts as the signed-in Google user; it has no service account and no access policy of its own.
  • Refresh tokens and saved passwords are kept in the macOS Keychain, per account. SSH user keys are generated in the Secure Enclave as P-256 and cannot be exported.
  • Tunnel listeners bind 127.0.0.1 only. Each connection is traced to one process and admitted by the tunnel's access level; anything that cannot be traced is refused.
  • In-app SSH verifies the VM's host key: against the keys the VM publishes, or a key you accepted on first use after seeing its fingerprint.
  • No telemetry, analytics or crash reporting. The licence server receives a hardware hash, a device public key and your licence key, nothing about your Google Cloud.
  • Releases are signed with a Developer ID certificate (team C83FBCDMHY), built with the hardened runtime, and notarized by Apple.

Where traffic goes

Every connection the app makes is TLS. These are all of its destinations:

DestinationWhat travelsWhen
Google sign-in, in your browser Google's own consent screen; the authorisation code returns to Ostgate on 127.0.0.1. Adding or re-authorising an account.
Google's token and userinfo endpoints The code exchange, refresh-token grants, and your email address. Sign-in, then when an access token is about to expire.
Google Cloud APIs API calls authorised by your own access token. Browsing projects and instances, and before each connection.
IAP relay, tunnel.cloudproxy.app One secure WebSocket per TCP connection, carrying the SSH, RDP or tunnel bytes, with your access token. Every session, tunnel and forward.
Licence server, license.ostgate.app Hardware hash, device public key, licence key. No Google data. At launch and every few hours.

From the relay on, the path is Google's: IAP checks your IAM permission on the instance and connects to the VM's port from 35.235.240.0/20, which your VPC firewall has to admit (see IAP TCP forwarding: firewall rule and IAM). The VM needs no external IP and no Ostgate agent. IAP's relay protocol is described on the home page.

A forward through a bastion is one SSH session over that tunnel: the bastion VM, not the Mac, opens the connection to the private address. The same holds for the Cloud Run proxy, which reaches *.run.app from a VM of the service's project.

Identity and permissions

Sign-in

  • A Desktop-type OAuth client, your system browser, a loopback redirect to 127.0.0.1 on a random port, PKCE and a state check. No embedded web views and no custom URI schemes.
  • The redirect listener accepts only the redirect carrying the attempt's state; any other request gets an empty 400 and the sign-in keeps waiting, so another local process cannot end it.
  • Ostgate never reads gcloud's files or Application Default Credentials. This OAuth flow is its only credential source.
  • Access tokens are held in memory only and refreshed within five minutes of expiry. When Google refuses the refresh token, for example after you revoke it or when your Workspace session control requires reauthentication, Ostgate stops refreshing it and asks you to sign in again; sessions already open keep running.

Scopes

Ostgate requests three OAuth scopes. Google shows them on its consent screen, and Ostgate refuses a sign-in in which any of them was left unticked.

ScopeWhat it is used for
openid Identifies which Google account completed the sign-in, so several signed-in accounts stay apart as separate profiles.
email Reads the account's email address: it determines your SSH username (the OS Login username, or one derived from the address when the key goes through instance metadata) and identifies the account in the app.
https://www.googleapis.com/auth/cloud-platform Calls Compute Engine, Resource Manager, OS Login, IAP TCP forwarding, Cloud SQL Admin, Cloud Run Admin, Cloud Logging and, only when you pick a key or password from it, Secret Manager on your behalf. Secret Manager accepts only this scope and the IAP relay publishes no narrower one, so one scope covers every call.

The privacy policy lists every Google API method Ostgate calls and the action of yours that triggers it.

IAM

Every call and every tunnel runs with the signed-in user's own IAM permissions, so Ostgate reaches nothing that account could not already reach with gcloud. The roles that matter:

  • roles/iap.tunnelResourceAccessor for any connection: SSH, RDP, files, tunnels and forwards. IAP refuses a tunnel without it.
  • roles/compute.viewer to list instances.
  • roles/compute.osLogin (or osAdminLogin for sudo) to SSH to instances that use OS Login.
  • roles/compute.instanceAdmin.v1 to SSH to instances without OS Login, set a Windows password, and start or stop instances.

The full list, including Cloud SQL, Cloud Run, Cloud Logging and Secret Manager, is in Prepare your Google Cloud project. To limit who reaches what, scope the IAP role to projects or instances and add IAM conditions, such as an end date or a destination port; Google evaluates them when the tunnel opens.

Secrets and keys

Where each secret lives

  • Refresh tokens: Keychain generic-password items, one per account under that account's namespace, accessible after first unlock on this device only and not synchronised to iCloud. Never in preference files, other files or logs.
  • SSH user keys: one per account, generated in the Secure Enclave as P-256 and used as ecdsa-sha2-nistp256. The private key cannot be exported by any process, Ostgate included; the Keychain holds only the Enclave's encrypted handle to it. A software ed25519 key in the Keychain is used only on a Mac without a Secure Enclave.
  • Windows passwords: Set Windows Password uses the mechanism Google documents. Ostgate writes an RSA-2048 public key to the instance's windows-keys metadata; Google's guest agent creates or resets the account and writes the new password, encrypted to that key, to serial port 4; Ostgate decrypts it on the Mac. It stays in memory unless you choose to save it, and a saved password goes to the Keychain with the same attributes as the refresh token.
  • SSH passwords, for VMs that log in with one: saved to the Keychain only when you ask and the server accepted it; otherwise held in memory for that session.
  • Secret Manager: a password or key you set to come from Secret Manager is never stored. Only the secret's name is kept in the connection settings; the value is read from Google when a connection starts, held in memory for that connection and never logged.
  • Your own key files (Connect As…): read when the connection starts and held in memory. Ostgate does not store the key or its passphrase.
  • Database credentials: Ostgate does not ask for, read or store them. A Cloud SQL tunnel carries the connection your own client makes.

Google API responses are not cached on disk: the app's HTTP session has no URL cache.

How the SSH key reaches a VM

Ostgate decides per connection the way gcloud compute ssh does: the instance's enable-oslogin metadata, else the project's, else OS Login is off. Windows VMs never use OS Login.

  • OS Login: the public key is imported to your OS Login profile with a one-hour expiry, and you log in as your OS Login username.
  • Instance metadata: the key is added to that instance's ssh-keys only, never to project metadata, with a 24-hour expiry. Every entry that is not Ostgate's is kept unchanged, and expired google-ssh entries are removed, as gcloud does. This needs compute.instances.setMetadata.
  • Connect As… with your own key publishes nothing.

What listens on the Mac

Tunnels, forwards and Cloud Run proxies listen on 127.0.0.1 and nothing else. On every accepted connection Ostgate finds the one process holding the other end of that exact loopback connection (address and port on both sides), then applies the access level set for that tunnel or forward:

Access levelAdmitsDefault for
Ostgate only Ostgate's own process. SSH, RDP, custom ports and bastion forwards you set up yourself.
My processes Processes whose effective user ID is yours. Database and web presets, Cloud SQL tunnels and Cloud Run proxies.
Any local process… Every process on the Mac. Never; only after you confirm a shared tunnel.
  • A connection that matches no process, or more than one, is refused. An unprivileged app cannot read another macOS user's or root's processes, so those are refused at every level except Any local process.
  • The boundary of My processes is your macOS user account: your other login sessions, sandboxed apps and any malware running as you are admitted. Choose Ostgate only where that matters.
  • A tunnel saved as Any local process and set to start automatically starts again after a relaunch without asking a second time.
  • A refusal shows on the tunnel's row, for example “Refused nc — only Ostgate may connect”, and is logged with the process name and ID only.
  • During sign-in, the OAuth redirect listener described above runs on 127.0.0.1 until the sign-in ends.
  • ssh from Terminal through Ostgate opens no listener: ssh starts Ostgate as its ProxyCommand and talks to it over standard input and output.

Ostgate changes files outside the app only when you ask: the SSH integration adds one block to ~/.ssh/config, and the Hosts tool and host-name aliases for forwards edit /etc/hosts after macOS asks for an administrator's approval.

Verifying the VM

SSH host keys

Every in-app SSH connection, terminal tabs and bastion forwards alike, checks the server's host key in this order:

  1. If the VM publishes its host keys in guest attributes, the presented key must be one of them.
  2. Otherwise it must equal the key you accepted on first use for that instance.
  3. Otherwise Ostgate asks, showing the project, zone, instance, key type and SHA256: fingerprint, and stores the key only if you accept.

A mismatch stops the connection with “Host key mismatch”: the VM was rebuilt or the connection was intercepted. Accepted host keys are public keys, stored per account. For ssh from Terminal, OpenSSH checks its own known_hosts.

Remote Desktop

Ostgate does not check the RDP server certificate: Windows presents a self-signed certificate, and the address Ostgate connects to is its own loopback listener. With Network Level Authentication on, the default, Ostgate offers only CredSSP and refuses a VM that selects a weaker protocol, so the password is not sent; CredSSP binds the authentication to the TLS key, so it cannot be relayed to the real VM. Restricted Admin mode, off by default, keeps the password off the VM. With NLA turned off, the password is sent over TLS to an endpoint whose certificate is not checked.

What Ostgate receives

The app contacts the licence server when it starts and every few hours, and sends only what the licence check needs:

  • a hardware hash: a SHA-256 hash of the Mac's hardware identifier, so a trial or key is counted once per Mac. The identifier itself is not sent;
  • the public half of a device key created in the Mac's Secure Enclave, which signs each request. The private half never leaves the Secure Enclave, and there is no software fallback;
  • the licence key you entered, if any.

The server stores the hardware hash, the device public key, when the trial started, which Mac a key is activated on, the name the key was issued to, the key's end date if it has one, and when each was last seen. It receives no Google account, project or instance names and no session contents. It runs on Cloudflare Workers; Cloudflare processes request details such as your IP address to deliver the requests, and the licence server does not store the IP address.

  • A signed lease lets Ostgate work offline for up to 72 hours after the last successful check. A Mac that has never reached the licence server cannot start connections.
  • The licence gates new connections only. Open sessions keep running when a trial or key ends, and tunnel reconnects never wait for the licence server.
  • Licences are sold by Paddle.com as reseller and Merchant of Record; payment details go to Paddle, not to us.
  • The app has no telemetry, analytics or crash reporting. This website sets no cookies and runs no analytics.

Details, retention and your rights are in the privacy policy.

Hosts to allow

For a corporate firewall or egress filter: the app connects out on TCP 443 to these hosts.

HostUsed forNeeded
tunnel.cloudproxy.app IAP relay, secure WebSocket Every connection
accounts.google.com Sign-in, opened in your browser Adding an account
oauth2.googleapis.com Token exchange and refresh Always
openidconnect.googleapis.com Account email Sign-in
compute.googleapis.com Instances, metadata, host keys, serial output Always
cloudresourcemanager.googleapis.com Project search Browsing projects
oslogin.googleapis.com OS Login profile and keys SSH to OS Login VMs
sqladmin.googleapis.com Cloud SQL instance list Cloud SQL
run.googleapis.com Cloud Run services and their IAM policy Cloud Run
logging.googleapis.com A VM's audit log entries Event log
secretmanager.googleapis.com Secrets you pick Only with Secret Manager
license.ostgate.app Trial and licence check Always

Behind a corporate proxy: Ostgate has no proxy setting of its own and uses the macOS system proxy settings (System Settings › Network › Details › Proxies) for Google's APIs, the IAP relay and the licence server. The relay's WebSocket works through an HTTP CONNECT proxy (tested 7 October 2026). Not tested yet: proxies that ask for a user name and password, and proxies that inspect TLS; those need the proxy to pass WebSocket upgrades to tunnel.cloudproxy.app and, for inspection, a root certificate the Mac trusts.

Open in Cloud Console and the links in About open console.cloud.google.com and ostgate.app in your browser, not in the app. Context-aware access levels that require a device certificate are not supported yet.

Signing and updates

  • Releases are signed with a Developer ID Application certificate of team C83FBCDMHY, with the hardened runtime and a secure timestamp. The app and the disk image are each notarized by Apple, with the ticket stapled.
  • The app is not sandboxed: a sandboxed build cannot bind its tunnel and sign-in listeners.
  • Each release is built after the full test suite passes; the release build refuses to sign or notarize if any check fails. Only the latest release receives fixes.
  • The disk image and its SHA-256 are published as GitHub Releases of ostgate/ostgate-iap-app.
  • The SSH libraries resolve from mirrors we control rather than upstream tags that can move, and every bundled third-party licence ships with the app.
  • Ostgate does not update itself. A new version is a new disk image on the releases page, listed in the release notes.

To check a download:

shasum -a 256 Ostgate-<version>.dmg
spctl -a -vv -t open --context context:primary-signature Ostgate-<version>.dmg
codesign -dv /Applications/Ostgate.app 2>&1 | grep TeamIdentifier

Compare the hash with the one in the release notes on GitHub. The last command prints TeamIdentifier=C83FBCDMHY.

Reporting a vulnerability

Report a security problem privately to support@ostgate.app with “Security” in the subject, not as a public issue. Include the Ostgate version (Ostgate › About Ostgate), your macOS version and the steps to reproduce. The policy is in SECURITY.md in the releases repository.

Questions

Does any traffic pass through Ostgate's servers?

No. Google API calls and IAP tunnels go from your Mac directly to Google. The only Ostgate server the app contacts is the licence server at license.ostgate.app, which receives a hardware hash, the public half of a Secure Enclave device key and the licence key you entered, if any. It never receives your Google account, projects, instances or session traffic.

Can other users or apps on the Mac use my tunnels?

Only at the access level set for that tunnel. Ostgate only admits Ostgate itself; My processes admits processes running as your macOS user; Any local process admits every process on the Mac and needs a confirmation. A connection that cannot be traced to exactly one process is refused, and processes of other macOS users are refused unless you chose Any local process.

What happens to credentials when I remove an account?

Remove from This App stops the account's sessions, tunnels and forwards, then deletes its refresh token, SSH key and saved passwords from the Keychain, together with the host keys it trusted and its settings. It changes nothing on Google Cloud: keys already published expire by themselves, after one hour through OS Login and 24 hours in instance metadata. To invalidate the refresh token at Google as well, remove Ostgate on your Google Account's third-party access page.

Can we restrict which projects and VMs Ostgate can reach?

Yes, through IAM. Ostgate has no access policy of its own: it acts as the signed-in Google account and reaches exactly what that account's IAM allows. Grant roles/iap.tunnelResourceAccessor only on the projects or instances people may reach, and narrow it with IAM conditions, such as an end date or a destination port.

Does Ostgate use gcloud or its credentials?

No. Ostgate never reads gcloud's files or Application Default Credentials. Every account signs in through Ostgate's own OAuth flow in your browser, and its refresh token is kept in the macOS Keychain.

Is the source code available?

No. Ostgate is closed source. The public repository ostgate/ostgate-iap-app holds the signed releases, the README and the security policy, not the code.

Try it against your own policy. Ostgate runs on Apple Silicon Macs with macOS 26.3 or later, with a 7-day trial and no sign-up. Project setup is in Prepare your Google Cloud project.