Skip to content

Keep sandbox inventory from exposing resolved secrets - #1154

Merged
phinze merged 1 commit into
mainfrom
phinze/mir_1765-miren-sandbox-list-json-bypasses-sensitive-env-var-handling
Sep 4, 2026
Merged

Keep sandbox inventory from exposing resolved secrets#1154
phinze merged 1 commit into
mainfrom
phinze/mir_1765-miren-sandbox-list-json-bypasses-sensitive-env-var-handling

Conversation

@phinze

@phinze phinze commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

miren sandbox list --json had a nasty surprise: it printed raw sandbox entities, including the resolved container environment. Secrets and all.

This gives sandbox listing its own RPC with a deliberately boring, allowlisted response. Table and JSON output now use the same safe inventory data. A new CLI only falls back to raw entities when it can tell that the server is too old to have the new RPC.

The projection is pretty bespoke, and I don’t think we should pretend it’s the final shape of this API. It fixes the real exposure without making us design generic filtered entity views at the same time. As more CLI commands move away from broad CRUD entity access, we can look for the common shape and add pagination where it actually becomes useful.

Compatibility also means carrying some of the old world for now. Older CLIs still use raw entities against new servers, and new CLIs use the raw fallback against old servers. Tightening broad entity access later may break that fallback, which feels like an acceptable way to finish the transition.

Closes MIR-1765

@coderabbitai

coderabbitai Bot commented Sep 3, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 19 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 3 included reviews currently available. Your 72 included PR review attempts over the past 7 days set your current allowance at 3 reviews per hour.

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 23079b73-51de-44a8-bee1-515c8b6e585e

📥 Commits

Reviewing files that changed from the base of the PR and between 82daec9 and fbed19b.

📒 Files selected for processing (16)
  • api/compute/compute.go
  • api/compute/compute_v1alpha/rpc.gen.go
  • api/compute/rpc.yml
  • blackbox/harness/app.go
  • blackbox/image_source_test.go
  • blackbox/sandbox_test.go
  • cli/commands/commands.go
  • cli/commands/sandbox_doc.go
  • cli/commands/sandbox_list.go
  • cli/commands/sandbox_list_test.go
  • components/coordinate/coordinate.go
  • docs/docs/command/sandbox-list.md
  • pkg/workloadroles/roles.go
  • pkg/workloadroles/roles_test.go
  • servers/sandbox/server.go
  • servers/sandbox/server_test.go

Comment @coderabbitai help to get the list of available commands.

@miren-code-agent miren-code-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🍪 biscuit: ⚠️ ready with caveats — auto-review, non-blocking

This draft fixes a real security problem: the old sandbox list --format json embedded compute_v1alpha.Sandbox directly into the response struct, which meant the full SandboxSpec — including resolved container environment variables and therefore secrets — could leak to any caller of that command. The fix is architecturally sound: a new servers/sandbox package acts as the explicit security boundary, a dedicated SandboxInfo RPC type carries only the safe inventory fields, the CLI renders from that allow-list, and tests assert (with canary values) that the secret-bearing data never makes it to the wire. That's the right shape for this kind of fix.

A few things I want to flag before this graduates to merge:

The reexportSandboxes.List panic is intentional but undocumented. rpc.gen.go line 330–332 panic("not implemented"). The generated Export() path is a code-gen pattern I'd expect to exist for completeness, and the Export() method itself isn't called anywhere in the current codebase. But a panic in a generated exported function with no comment explaining why it panics or what it's for is a landmine for the next person reading this. A // re-export is a no-op for generated clients; not called server-side comment would cost nothing.

sandboxTerminal is inconsistent with the legacy path. In listSandboxesFromEntities, the status is produced by ui.CleanStatus(string(sb.Status)). The sandboxTerminal function checks for "stopped" || "dead". In the new RPC server path (server.go line 120), the server strips the "status." prefix itself with strings.TrimPrefix. Both paths should produce normalized strings, so this should be fine in practice — but the assumption depends on ui.CleanStatus and strings.TrimPrefix("status.", …) being equivalent across all status values. There's no test asserting this equivalence, and if a new status value is added with a different prefix format, the dead-hiding logic could silently break for one path but not the other.

The fallback discriminator is tight and correct, but its narrowness is the whole game. serverPredatesSandboxInventory returns true only for rpc.ErrResolveLookup. The unit test (TestServerPredatesSandboxInventoryOnlyForLookupFailures) does a good job asserting what must not trigger the fallback (auth errors, unreachable errors, generic errors). The comment above the function is explicit. This is the most security-critical decision in the PR and it's the right call. Worth making sure the relevant RPC layer truly only returns ErrResolveLookup for "no such capability" and not for any auth-adjacent failure, but the test coverage for the negative cases here is exactly what it should look like.

cleanEntityID in servers/sandbox/server.go is a local duplicate. It handles a fixed prefix list (sandbox/, app_version/, app/, pool/) whereas ui.CleanEntityID (used everywhere else) presumably covers the same or broader set. Since the server is in a package that can't import ui without introducing a cycle, this might be necessary — but if the two ever diverge, the inventory will show inconsistently cleaned IDs. A comment linking them would help.

The test coverage for the core security guarantee (canary value must not appear in output) is present at three layers: server_test.go, sandbox_list_test.go, and blackbox/sandbox_test.go. That's the right defence-in-depth for a leak fix.

This is a draft and not yet merge-ready, but the substance of the change is sound. The caveats above are things worth addressing before this leaves draft — they're not blocking the design direction.


🍪 full review note · comment /biscuit review to run biscuit again.

Comment thread api/compute/compute_v1alpha/rpc.gen.go
Comment thread servers/sandbox/server.go Outdated
Comment thread cli/commands/sandbox_list.go
Sandbox entities carry resolved container environment, so returning them
from sandbox list made JSON a secret disclosure path. Serve an allowlisted
inventory projection through a dedicated capability instead, while letting
new clients fall back to entities only for servers that predate it.

This projection is intentionally narrow. It gives sandbox list an explicit
disclosure boundary now without prematurely designing the broader
replacement for CRUD entity access.
@phinze
phinze force-pushed the phinze/mir_1765-miren-sandbox-list-json-bypasses-sensitive-env-var-handling branch from aca6f6b to fbed19b Compare September 3, 2026 22:51
@phinze

phinze commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

/biscuit review

@miren-code-agent miren-code-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🍪 biscuit: ✅ ready to merge — auto-review, non-blocking

All three concerns from my previous review have been addressed — two substantively in code, one convincingly in argument.

rpc.gen.go panic comment — The author's point stands: the same generated panic("not implemented") exists in 153 other methods in the repo, and the right place for that documentation is the code generator, not a one-off annotation in this file. I accept that reasoning.

cleanEntityID divergence — The generic local copy is gone. The server now strips "app/" with a single strings.TrimPrefix inline where it's used (line 60 of server.go), which is narrower and honest about what it's doing rather than maintaining a partial reimplementation of ui.CleanEntityID. The divergence risk I raised is resolved at the source.

Status normalization equivalence — This is the cleanest fix of the three. Both the RPC path and the legacy entity path now funnel through newSandboxListEntry, which calls ui.CleanStatus unconditionally. sandboxTerminal only ever sees already-normalized strings. The structural invariant I wanted enforced by convention is now enforced by the code path itself. TestLegacySandboxListDropsExecutionSpec seeds a raw compute_v1alpha.DEAD status and asserts the dead sandbox is hidden by the default filter — that's exactly the regression test I asked for.

The core security guarantee — that sandboxListEntry is an allow-list that can never serialize SandboxSpec, and that the serverPredatesSandboxInventory fallback cannot be triggered by auth or unreachable errors — remains intact and is tested at three independent layers (server_test.go, sandbox_list_test.go, blackbox/sandbox_test.go). This is ready to graduate from draft to human review.


🍪 full review note · comment /biscuit review to run biscuit again.

@phinze
phinze marked this pull request as ready for review September 3, 2026 23:08
@phinze
phinze requested a review from a team as a code owner September 3, 2026 23:08

@evanphx evanphx left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice! I'll reconcile this new controller with changes in #1153 once it's merged.

@phinze
phinze merged commit edf61aa into main Sep 4, 2026
51 of 52 checks passed
@phinze
phinze deleted the phinze/mir_1765-miren-sandbox-list-json-bypasses-sensitive-env-var-handling branch September 4, 2026 13:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants