Running AI-Generated Software Safely
Executing untrusted generated code needed the same rigor as executing untrusted user code. Running model-written code changed the risk profile of the whole system. I had to assume the generated project was untrusted until proven otherwise and design the runtime around isolation, limits, supervision, and controlled preview access.
- Docker-backed execution
- Resource limits
- Process supervision
- Preview proxying
- The unexpected routing and lifecycle problems
The situation
The platform needed to install dependencies, run builds, and start dev servers for code the model just wrote, which is by definition unverified. In Running AI-Generated Software Safely, I wanted to capture the real operating situation rather than describe the problem in abstract terms. What mattered to me was the moment the existing approach stopped being sufficient in normal use, because that was usually the point where the next architectural requirement became obvious.
The naive solution
Run everything directly on the host for simplicity and speed. The naive option was attractive precisely because it was faster and easier to explain. I could have pushed further in that direction and probably improved the demo, but it would have optimised the visible symptom rather than the underlying system constraint.
The hidden constraint
Generated code could contain anything, including runaway processes, excessive resource use, or attempts to reach outside the project directory. This constraint was easy to miss early because it did not show up in the first successful run. It appeared once the workflow had to survive repetition, failure, another user, another provider, or a return visit, which is why I now look for these second-order requirements much earlier.
The decision
Run production-oriented workspace execution inside Docker, with dropped capabilities, no-new-privileges, and CPU/memory/process limits. I treated this as an architectural or product decision rather than a prompt tweak. The aim was to create a boundary I could reason about later, instead of relying on model behaviour to remain consistent as providers, templates, and user requests changed.
The implementation
Built a supervisor for dev servers per workspace (starting, running, crashed, restarting, stopped states), health polling, log draining, crash detection, and an authenticated preview proxy. I also tried to make the implementation concrete enough that the decision could be inspected in code and runtime behaviour. That meant explicit state, explicit policy, and observable transitions rather than a collection of hidden assumptions spread across prompts and handlers.
The tradeoff
Meaningfully more operational complexity than running dev servers directly, including container lifecycle management and port allocation. I did not treat the decision as free. In most cases I was deliberately accepting more engineering, state management, or operational complexity in exchange for a smaller failure surface and a product that could survive realistic use.
The evidence
Discovered and fixed a subtle failure mode where client-side routers (TanStack) misread a path-prefixed proxy URL as an application route and 404'd, solved by passing the proxy base path into the generated app at startup. I use this evidence as a practical signal, not as a claim that the architecture is universally correct. The useful question was whether the new design changed what the system could reliably do under the conditions that had previously broken it.
The reusable lesson
Treat "run what the model just wrote" as running untrusted code, with the same isolation, resource limits, and supervision you would apply to any multi-tenant execution environment. The part I would carry into another build is the decision rule behind the implementation, not the exact technology choice. Vendors and frameworks will change; the underlying question about state, control, isolation, feedback, or user trust is much more reusable.
The consultant's questions
- Where does AI-generated code actually execute, and what could it reach from there?
- What happens if a generated app runs away with CPU, memory, or network requests?
- What assumption in this part of the system would become dangerous if usage or tenant count increased?
- What evidence would tell me this decision needs to be revisited rather than defended indefinitely?