My AI client can provision real servers now — over Krova Cloud's MCP server. The reason I sleep: scoped keys, a sandbox space, unreachable boxes, and a legible bill. Setup, guardrails, and the threat model inside.
I typed a sentence into my AI client: "stand up a 2-CPU box with Docker for the staging API, call it staging-api." It provisioned a real server, verified the boot, and reported back — SSH ready, no public ports exposed. Ten seconds of magic, then the feeling you're supposed to have: what did I just hand an LLM the ability to do?
The answer is the whole point of this post. Spoiler: it's not "trust the model." It's architecture.
The setup
Krova Cloud ships an MCP server, so Claude/Cursor/any MCP client can drive the API in plain English. Config is the standard shape:
{
"mcpServers": {
"krova": {
"command": "npx",
"args": ["-y", "@krovacloud/mcp"],
"env": {
"KROVA_API_KEY": "kro_sandbox_...",
"KROVA_SPACE_ID": "space_..."
}
}
}
}
Two gotchas the docs call out, both worth knowing before you debug ghosts:
- The space ID is the step everyone misses. The key authenticates you but doesn't say which space; without KROVA_SPACE_ID the server starts happily and then every call fails with "No spaceId provided." It's in your dashboard URL.
- Verify it's actually connected. Ask it to list your Cubes. Real Cubes with status and hourly cost = connected. A vague essay about what Krova is = the model answering from memory; check the MCP status panel, don't rephrase.
The guardrails (the reason I sleep)
- Sandbox space, its own key. Keys are scoped to a single space and carry your permissions there. The assistant lives in a space that holds nothing precious — per the docs' own advice, "use a separate space for anything experimental, so the key cannot reach production Cubes at all." Prod is managed by me, slowly, with my own hands.
- Approval prompts stay on. The tool surface includes delete_cube, restore_cube, delete_domain — an agent that can create can also remove. I read what a call will do before allowing it. The prompt is a feature, not friction.
- Machines with no address. Every Cube the assistant can create is a Firecracker microVM with no public IP by default — private NAT'd network, default-deny inbound. A rogue box is isolated and unreachable, not a fresh attack surface.
- An enumerable, cheap blast radius. One nightly audit answers "what is it running right now?":
krova context use sandbox # contexts = kubectl-style profiles per space
krova list --json \
| jq -r '.[] | select(.state=="running") | "\(.name)\t\(.createdAt)"'
# forgotten box? one word:
krova cubes delete staging-api-2
Billing is by the minute, so worst case is a forgotten box costing the price of a coffee — visible in one list, deleted in one word.
The wrong question
We keep asking "can we trust the model?" No. Not fully, not ever. Models misread; prompts inject; docs lie.
The right question: what's the worst thing this key can do? Mine can spin up isolated microVMs with no public address, in a sandbox space, billed by the minute, visible in one sentence. I haven't made the AI careful. I've made carelessness survivable.
The AI doesn't make the infrastructure safe. The infrastructure makes AI access safe.
The honest part
- Prompt injection is real. A malicious page my assistant reads could instruct it to provision nonsense or delete things in its space. The layers absorb it: scoped key, sandbox space, approvals, unreachable boxes. Four imperfect layers multiplying into something sleepable.
- The key is a credential. It acts as you in that space. Config file, never a repo; one key per machine; revoke the ones you're not using — deletion takes effect immediately.
- It won't do ops for you. The assistant provisions; backups, patching and restore drills are still mine. Isolation is the platform's job, operations remain the owner's.
The tool didn't change. The blast radius did.
The same assistant that's terrifying with AdministratorAccess and a production VPS is harmless with a scoped key and disposable, unreachable boxes. Give it a library card, not a master key.
Top comments (6)
The line I would underline is the one about making carelessness survivable rather than making the model careful. That is the same move as designing for recoverability instead of designing for correctness, and it holds up much better under load.
One place I would push: the approval prompt is carrying more weight in this design than the other three layers, and it is the only one that degrades with use. Scoped keys, private networking and per-minute billing behave the same on day 200 as on day one. The prompt depends on you reading it properly every time, and the failure mode is not that you stop caring. It is that after the fortieth create approval, the difference between a normal one and a strange one stops registering. I have watched review quality decay that way on ordinary pull requests, long before agents were in the picture.
What has worked better for me is making the boring approvals disappear so the unusual ones stand out. Auto-allow the calls that are provably reversible and cheap, and reserve the prompt for the ones that are not, like delete_domain. Have you thought about splitting the tool surface that way, or does the sandbox isolation make you comfortable letting the whole surface run unattended?
That push is correct , and it's in the post now. The blanket prompt is the only layer that degrades: keys, networking and billing behave the same on day 200, attention doesn't. After the fortieth identical create approval I'd be rubber-stamping too.
So the surface is split at the client's per-tool permission layer (Krova keys are space-scoped, not operation-scoped, so the client allowlist is the mechanism): auto-allow the provably reversible , create_cube, domain/mapping adds, snapshots, reads , where worst case is bounded by per-minute billing, throttled by the mutation rate limit, and caught by the nightly audit. The prompt stays on the irreversible ones: delete_cube, restore_cube, delete_domain.
And to answer your question directly: no, the sandbox alone doesn't make me comfortable running the whole surface unattended. It bounds the cost of a mistake, not the loss , the assistant could still delete work I needed. Making the boring approvals disappear so the unusual ones stand out is the right design. Attention is a budget.
Curious whether you apply the same split to human-facing tooling , I'd guess the fatigue math is identical for deploy approvals.
The fatigue math is identical. The split does not port cleanly though, and the reason it does not is worth naming, because it is what keeps approve everything alive in change management long after everyone has privately admitted it is theatre.
A deploy approval is doing two jobs at once. It is a control, and it is the record of who is accountable. Auto-allowing the reversible ones removes both, and the second one is what the organisation actually notices missing, so the proposal dies in review and nothing changes. The version that survives is narrower: the reversible changes still write the same audit row with the same named owner, they just do not stop and wait for someone to look at it. Same evidence, no interrupt. Once the record is separated from the interrupt, the argument stops being about whether anyone trusts the pipeline.
The harder difference is what reversible means on each side. On your tool surface it is a property of the operation, so create_cube is reversible and delete_domain is not, and an allowlist keyed on the name is sound. On a deploy it is a property of the change plus the state it touches. A config flip and a migration that rewrites a column are both one deploy, and rollback only works for one of them. So the trigger cannot be the service or the pipeline. What has held up for me is that forward only changes prompt and everything else rides, which in practice means migrations that drop or rewrite data, and changes to a contract somebody else already consumes.
One lever you do not have: batch size. Part of why the fortieth approval gets rubber stamped is volume, and part of it is that each one is too big to hold in your head, so a smaller change is genuinely easier to read rather than just faster to approve. That does quiet work on the same problem the allowlist does loudly. An agent gets none of it, since it reads the fortieth request exactly as carefully as the first and fails somewhere else entirely.
"Not trust the model, architecture" is the right frame, and the threat model section is doing the real work.
Two additions from running similar setups. First, scope the key per space AND per lifetime: a sandbox key that lives for months quietly becomes production-shaped as the sandbox accumulates state people care about. Second, the legible bill is a security control, not just a cost one. A provisioning loop that goes wrong shows up in spend before it shows up in server-shaped monitoring, so the budget alert doubles as anomaly detection you get for free.
Both additions are in the post now , they extend the frame rather than correct it, which is the best kind of comment.
Key lifetime: yes. The sandbox drifts toward production-shaped as it accumulates state, and a months-old key turns that drift into exposure. Mine rotates on a schedule now, and each rotation doubles as the "is anything precious in here yet?" check , if yes, it's not a sandbox anymore.
Bill-as-control is the one I'd underline. Spend is the fastest anomaly signal a broken provisioning loop produces. And the prepaid model makes it a fuse, not just a detector: balance hits zero and the Cubes auto power-off, so a runaway loop physically cannot spend past the credit. Detection and a hard cap from the same number.
Thanks , the threat model section is better with both in it.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.