Gaps found in the 1.0 security review¶
What an independent review of hangar found before 1.0, and where each finding stands. It adds to what is not covered. This is part of the security model, and the review packet builds on it.
The review read hangar at 01c136c as an attacker would. It found one High, five Medium and five Low gaps in hangar.
Each is tracked in Jira. What remains open, with its owner and reason, is on
open findings and accepted risks.
Where each finding stands¶
| Finding | Severity | Ticket | Status |
|---|---|---|---|
| Approvals inside a turn on GKE and E2B | High | KAN-165, KAN-189 | Mitigated: refused under required, the default on E2B; the driver sidecar is off until its checks pass (KAN-186) |
| Codex rules in hardened turns | Medium | KAN-169 | Fixed at turn start; a file written during the turn is accepted |
| Login files written back from a sandbox | Medium | KAN-168, KAN-178 | Fixed for team logins; a person's own login is accepted |
The image ignores uv.lock, uses uv:latest, runs as root |
Medium | KAN-170 | Fixed |
| GitHub Actions pinned by tag | Medium | KAN-171 | Fixed |
| hangar's Google roles are project-wide | Medium | KAN-172 | Fixed in hangar; Tower's own registry is its half |
| The console: no CSP, the admin token in browser storage | Low (L-7) | KAN-174 | Fixed: a strict CSP, audited sinks, the token in sessionStorage; an HttpOnly cookie is accepted as later work |
| The control-plane pod's security context and NetworkPolicy | Low (L-8) | KAN-174, KAN-170 | Fixed; IAP stays off, with the reason |
GKE ships with open egress |
Low (L-9) | KAN-174 | Fixed: GKE ships with allowlist (#387) |
| Git grants in plain HTTP between nodes | Low (L-10) | KAN-180 | In Terraform, not yet applied: inter-node encryption |
| Tower is not consulted yet | Low (L-1) | KAN-77 to KAN-79, KAN-33 | Wired and off by default; release and landing are KAN-33 |
| Predictable custom session branches | Known gap 8 | KAN-174 | Fixed: checked against their creator |
| E2B buffers a command's whole output | Known gap 9 | KAN-174 | Closed: hangar's envd client keeps a bounded tail, re-tested |
| The gateway has no connection cap | Known gap 10 | KAN-174 | Fixed: a per-peer cap |
| NAT64 and 6to4 addresses pass the callback check | Q-2, Q-3 | KAN-174 | Fixed |
Approvals inside a turn on GKE and E2B (High)¶
On GKE and E2B there is no uid split. The driver, its run key and the harness settings belong to the same user as the agent. In an ask or deny-risky turn, the agent could allow itself or answer its own approval.
HANGAR_UID_SPLIT=required refuses those turns instead; auto turns are unaffected. The Kubernetes manifests set it. On E2B it is the default when the setting is unset (KAN-189), and hangar doctor flags an explicit auto.
The gateway, the egress proxy and the landing checks still hold. The driver sidecar lets them run on GKE; it is built and off until its cluster checks pass (KAN-186).
Codex rules in hardened turns (Medium)¶
Codex reads ~/.codex/rules, a repository's .codex/rules and MCP servers set to approve. The agent can write the first and last, and anyone who pushes can write the second. Any of them lets a call in an ask turn run unasked. This holds on Docker with the split too.
Fixed for files there when a turn starts. A Codex turn that asks won't start while either rules directory holds a .rules file. Nor will it while ~/.codex/config.toml sets an MCP tool to approve.
Accepted: a file the agent writes during the turn. Codex has no switch to ignore rules, so closing it means giving Codex a home directory hangar owns.
Login files written back from a sandbox (Medium)¶
When a sandbox releases a login file, hangar can store the file the sandbox returns, so a refreshed token survives. The sandbox writes that file, and the identity in it isn't verified.
Fixed for team logins: hangar never stores a file a sandbox returns for one. Storing a new login also ends any lease on the old one, so a sandbox can't write a stale file back (KAN-178).
Accepted for a person's own login. hangar stores a returned file only when it names the same account and its access tokens changed with its refresh token. A sandbox that refreshes the login, then swaps in another account's refresh token, still passes. Only that person's later sandboxes would be affected.
Closing it means hangar refreshing the login itself and checking the identity it gets back.
The control plane's image and identity (Medium)¶
Fixed. The image installs from uv.lock with hashes, pins every base image by digest and runs as uid 1000 (KAN-170). Every GitHub Action is pinned by commit SHA (KAN-171). hangar's Google identity builds with Cloud Build, writes only its own registry repository and a build-staging bucket, and has no project-wide storage role (KAN-172).
Low¶
- The console sends a strict Content-Security-Policy. An audit replaced the riskiest HTML sinks, and a test fails on a new one. The admin token is in
sessionStorage; anHttpOnlycookie needs CSRF checks first. - The control-plane pod runs non-root with a restricted security context. An ingress NetworkPolicy limits who reaches its ports (control-plane pod).
- GKE shipped with
openegress. It now ships withallowlist(#387); see the egress default. - Grants and keys cross the pod network in plain HTTP. GKE's inter-node encryption covers them once applied. It is in Terraform, not yet applied to the cluster (KAN-180).
- Tower decides only when governance is on. Tool calls, egress and secret requests can ask it; the default mode is
off. Job release and landing are KAN-33. hangar refuses an answer about a different approval.
These are tracked in KAN-174, with the review's notes on each known gap in what is not covered.