<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://docs.aibox-demo.egroup.hu/blog</id>
    <title>AI-in-a-Box Blog</title>
    <updated>2026-04-11T00:00:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://docs.aibox-demo.egroup.hu/blog"/>
    <subtitle>AI-in-a-Box Blog</subtitle>
    <icon>https://docs.aibox-demo.egroup.hu/img/favicon.svg</icon>
    <entry>
        <title type="html"><![CDATA[Building a Self-Hosted AI Platform from Scratch]]></title>
        <id>https://docs.aibox-demo.egroup.hu/blog/building-sovereign-ai</id>
        <link href="https://docs.aibox-demo.egroup.hu/blog/building-sovereign-ai"/>
        <updated>2026-04-11T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Most AI platforms assume you are comfortable sending your data to someone else's servers. For a growing number of organizations, that assumption is wrong. Regulated industries, defense contractors, research labs, and privacy-conscious companies need AI capabilities where no data leaves their network. That is the problem AI-in-a-Box was built to solve.]]></summary>
        <content type="html"><![CDATA[<p>Most AI platforms assume you are comfortable sending your data to someone else's servers. For a growing number of organizations, that assumption is wrong. Regulated industries, defense contractors, research labs, and privacy-conscious companies need AI capabilities where no data leaves their network. That is the problem AI-in-a-Box was built to solve.</p>
<blockquote>
<p><strong>Current architecture:</strong> This post describes the product thesis and early
architecture. The current service map, auth model, subagent model, and receipt
system are documented in the <a class="" href="https://docs.aibox-demo.egroup.hu/reference/architecture">Architecture reference</a>,
<a class="" href="https://docs.aibox-demo.egroup.hu/reference/auth">Authentication reference</a>, and
<a class="" href="https://docs.aibox-demo.egroup.hu/reference/audit-trail">Audit Trail reference</a>.</p>
</blockquote>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-sovereign-ai-thesis">The sovereign AI thesis<a href="https://docs.aibox-demo.egroup.hu/blog/building-sovereign-ai#the-sovereign-ai-thesis" class="hash-link" aria-label="Direct link to The sovereign AI thesis" title="Direct link to The sovereign AI thesis" translate="no">​</a></h2>
<p>"Sovereign AI" is not a marketing term. It describes a concrete constraint: inference, data storage, and agent orchestration can run on infrastructure you own and control. A strict deployment can keep model calls local; the default developer path can also route through external providers such as OpenRouter when the operator configures that route.</p>
<p>This matters for three reasons:</p>
<ol>
<li class="">
<p><strong>Regulatory compliance.</strong> HIPAA, PCI-DSS, ITAR, and similar frameworks impose strict controls on where data can be processed. Sending patient records or classified documents to an external LLM endpoint is a non-starter.</p>
</li>
<li class="">
<p><strong>Latency and availability.</strong> If your AI workflows depend on an external API, you inherit that API's outage schedule. A self-hosted inference stack on local GPUs gives you low-latency inference with no dependency on someone else's uptime.</p>
</li>
<li class="">
<p><strong>Cost at scale.</strong> API pricing works fine for prototyping. At thousands of requests per hour, running your own vLLM instance on commodity hardware is dramatically cheaper.</p>
</li>
</ol>
<p>The design principle is "local-first, not local-only." The platform can optionally route requests to external providers like OpenRouter, but only when an administrator explicitly enables it and only through a policy-gated external gateway that logs every outbound call.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture-overview">Architecture overview<a href="https://docs.aibox-demo.egroup.hu/blog/building-sovereign-ai#architecture-overview" class="hash-link" aria-label="Direct link to Architecture overview" title="Direct link to Architecture overview" translate="no">​</a></h2>
<p>AI-in-a-Box is a microservices platform. Each subsystem is an independent service with its own API, communicating via REST and async events. Here is the high-level layout:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">User -&gt; Frontend (React/Vite) -&gt; API Gateway (Go)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  -&gt; Agent Runtime (Python)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    -&gt; Guardrail Service (input check)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    -&gt; Memory Service (context retrieval)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    -&gt; Inference Router -&gt; vLLM / External Gateway (OpenRouter)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    -&gt; Guardrail Service (output check)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    -&gt; Memory Service (store new memories)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  -&gt; API Gateway -&gt; Frontend (streamed response via SSE)</span><br></div></code></pre></div></div>
<p>The key services:</p>
<ul>
<li class="">
<p><strong>API Gateway</strong> (Go): OIDC authentication, rate limiting, SSE streaming, audit logging. Every request gets a correlation ID and tenant extraction from JWT claims.</p>
</li>
<li class="">
<p><strong>Agent Runtime</strong> (Python, OpenAI Agents SDK): The brain. Handles chat, typed tool dispatch, <code>SKILL.md</code> loading, and <code>Delegate</code>-based subagent dispatch.</p>
</li>
<li class="">
<p><strong>Inference Router</strong> (Go): A lightweight proxy between the agent runtime and inference backends. Maintains a model registry mapping public model IDs such as <code>google/gemma-4-E4B-it</code> or <code>openai/gpt-5.4</code> to backends such as <code>vllm</code> and <code>openrouter</code>, health-checks backends, and exposes a unified OpenAI-compatible API.</p>
</li>
<li class="">
<p><strong>Guardrail Service</strong> (Python): Constitutional AI layer with pluggable backends. Input guardrails handle jailbreak detection, PII redaction, and topic control. Output guardrails enforce content policy and flag hallucinations. Policies are scoped hierarchically: global, then tenant, then per-agent. Lower levels can only restrict, never relax.</p>
</li>
<li class="">
<p><strong>Memory Service</strong> (Python): Typed memory powered by Mem0 and Qdrant. Four memory types (user, feedback, project, reference) are stored as vector embeddings in per-tenant Qdrant collections. The message buffer lives in Redis as a sliding window. Memories are automatically extracted from conversations and retrievable via semantic search.</p>
</li>
<li class="">
<p><strong>Code Sandbox</strong>: Per-session Docker containers with hardened security (all capabilities dropped, PID limits, no network, non-root user). Optional GPU passthrough for ML workloads.</p>
</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-makes-it-different">What makes it different<a href="https://docs.aibox-demo.egroup.hu/blog/building-sovereign-ai#what-makes-it-different" class="hash-link" aria-label="Direct link to What makes it different" title="Direct link to What makes it different" translate="no">​</a></h2>
<p>There are other self-hosted AI tools. Dify provides visual workflow building. Open WebUI gives you a chat interface. LocalAI wraps inference. AI-in-a-Box is not trying to replace any one of these. It integrates with Dify for visual workflows and runs its own inference via vLLM. The difference is in what it adds on top:</p>
<p><strong>Multi-tenant isolation from day one.</strong> Every database table has a <code>tenant_id</code> column. Vector collections are per-tenant. Memory scopes are per-tenant. The gateway extracts tenant identity from OIDC tokens. This is not an afterthought bolted onto a single-user system.</p>
<p><strong>Enterprise compliance built in.</strong> The audit service produces hash-chained, HMAC-signed immutable logs suitable for SOC2, HIPAA, and PCI-DSS audits. Every agent action, every guardrail decision, every external API call is logged with a tamper-evident chain.</p>
<p><strong>Agent orchestration, not just chat.</strong> The agent runtime supports adaptive single-agent conversations, <code>Delegate</code>-based typed subagents for focused independent work, and visual workflows via Dify. Agents have typed memories and load skills on demand using the <code>SKILL.md</code> format.</p>
<p><strong>Inference flexibility.</strong> The inference router supports vLLM for local GPU serving and policy-gated external providers via OpenRouter. Models are pre-staged from MinIO or local storage, supporting fully air-gapped deployments with no need to download from HuggingFace at runtime.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="deployment-model">Deployment model<a href="https://docs.aibox-demo.egroup.hu/blog/building-sovereign-ai#deployment-model" class="hash-link" aria-label="Direct link to Deployment model" title="Direct link to Deployment model" translate="no">​</a></h2>
<p>The platform deploys two ways:</p>
<p><strong>Docker Compose</strong> for single-node or small team setups. One <code>docker-compose.yml</code> brings up all services plus infrastructure (PostgreSQL, Redis, Qdrant, MinIO, Keycloak, Langfuse). Works on a single machine with one or more GPUs.</p>
<p><strong>Helm Charts</strong> for Kubernetes. Same container images, Kubernetes-native: Deployments with HPAs, GPU scheduling via NVIDIA device plugin, persistent volumes, Ingress, ConfigMaps, and optional service mesh for mTLS.</p>
<p>The smallest useful deployment is a single machine with an RTX 3070 or better, running a 2B-7B parameter model via vLLM. The largest is a multi-node Kubernetes cluster with dedicated vLLM instances per tenant.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-comes-next">What comes next<a href="https://docs.aibox-demo.egroup.hu/blog/building-sovereign-ai#what-comes-next" class="hash-link" aria-label="Direct link to What comes next" title="Direct link to What comes next" translate="no">​</a></h2>
<p>The platform is functional today for chat, multi-agent workflows, RAG, code execution, web search, and memory. The next priorities are:</p>
<ul>
<li class="">Model management UI for uploading and registering models</li>
<li class="">Skill marketplace for sharing SKILL.md packages across tenants</li>
<li class="">Voice and multimodal input support</li>
<li class="">Automatic memory summarization and compression for long-running agents</li>
</ul>
<p>The code is open source under MIT. If you are building sovereign AI infrastructure, we would like to hear what you need.</p>]]></content>
        <author>
            <name>AI-in-a-Box Team</name>
            <uri>https://github.com/Mapika/ai-in-a-box</uri>
        </author>
        <category label="architecture" term="architecture"/>
        <category label="sovereignty" term="sovereignty"/>
        <category label="platform" term="platform"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Claude Code-Style Agents: Sub-Agent Spawning and Skill Loading]]></title>
        <id>https://docs.aibox-demo.egroup.hu/blog/claude-code-style-agents</id>
        <link href="https://docs.aibox-demo.egroup.hu/blog/claude-code-style-agents"/>
        <updated>2026-04-10T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Claude Code has a pattern we wanted to replicate: when a task is complex enough, the main agent spawns a focused sub-agent with a custom system prompt and a curated set of tools. The sub-agent does its work and returns results to the parent. This keeps the main agent's context clean and lets specialized work happen in isolation.]]></summary>
        <content type="html"><![CDATA[<p>Claude Code has a pattern we wanted to replicate: when a task is complex enough, the main agent spawns a focused sub-agent with a custom system prompt and a curated set of tools. The sub-agent does its work and returns results to the parent. This keeps the main agent's context clean and lets specialized work happen in isolation.</p>
<p>We built this into AI-in-a-Box's agent runtime using the OpenAI Agents SDK.</p>
<blockquote>
<p><strong>Current runtime model:</strong> This post is historical. The current implementation
uses a main agent with a <code>Delegate</code> tool and subagent definitions in
<code>deploy/config/subagents/*.md</code>; the old YAML handoff team model has been
removed. Use the <a class="" href="https://docs.aibox-demo.egroup.hu/reference/agents">Agents reference</a> and
<a class="" href="https://docs.aibox-demo.egroup.hu/tutorials/multi-agent">Multi-Agent tutorial</a> for current behavior.</p>
</blockquote>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-current-delegate-tool">The current Delegate tool<a href="https://docs.aibox-demo.egroup.hu/blog/claude-code-style-agents#the-current-delegate-tool" class="hash-link" aria-label="Direct link to The current Delegate tool" title="Direct link to The current Delegate tool" translate="no">​</a></h2>
<p>The current mechanism is the <code>Delegate</code> tool. The main agent calls it when a
task benefits from dedicated instructions rather than handling everything
inline. The tool takes a short description, a configured <code>subagent_type</code>, and
the prompt the child agent should execute.</p>
<p>The implementation has changed since the early prototype below. Today the tool
shape is:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">Delegate(description="short label", subagent_type="researcher", prompt="&lt;task brief&gt;")</span><br></div></code></pre></div></div>
<p>The early prototype used a <code>delegate_task</code> helper that built subagents from
tool-category strings:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">create_delegate_tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">config</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> tenant_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> user_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> memory_client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">"""Create a tool that spawns sub-agents with custom system prompts."""</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> agents </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> Runner</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)"># Pre-build tool pools so sub-agents can select what they need</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    sandbox_tools </span><span class="token operator">=</span><span class="token plain"> create_sandbox_tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">config</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sandbox_url</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> tenant_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    tool_pools </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token string" style="color:rgb(255, 121, 198)">"memory"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> create_memory_tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">memory_client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> tenant_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> user_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token string" style="color:rgb(255, 121, 198)">"search"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> create_search_tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">config</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">search_url</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token string" style="color:rgb(255, 121, 198)">"code"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> sandbox_tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token string" style="color:rgb(255, 121, 198)">"knowledge"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> create_knowledge_tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">config</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">knowledge_url</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> tenant_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">@function_tool</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">delegate_task</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">task</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> approach</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> tools_needed</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">"""Delegate a complex task to a focused sub-agent."""</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        requested </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">strip</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">lower</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> t </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> tools_needed</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">split</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">","</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        sub_tools </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> category </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> requested</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> category </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> tool_pools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                sub_tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">extend</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tool_pools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">category</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        sub_agent </span><span class="token operator">=</span><span class="token plain"> Agent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            name</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"sub-agent"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            instructions</span><span class="token operator">=</span><span class="token plain">approach</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            model</span><span class="token operator">=</span><span class="token plain">model</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            tools</span><span class="token operator">=</span><span class="token plain">sub_tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        result </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> Runner</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">run</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">sub_agent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> task</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">final_output </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">or</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Sub-agent completed but produced no output."</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">delegate_task</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><br></div></code></pre></div></div>
<p>The current implementation gets its tool allowlists from
<code>deploy/config/subagents/*.md</code>, not from free-form tool-category strings. A
research subagent can be configured with web and knowledge tools, while a code
subagent can be configured with sandbox tools. This limits the subagent's
blast radius and keeps its tool list focused.</p>
<p>The main agent's system prompt includes guidance on when to use delegation:</p>
<blockquote>
<p>Use for: deep research requiring multiple searches and synthesis, complex data analysis pipelines, multi-file code projects, document drafting that needs iteration.
Do NOT use for: simple questions, single tool calls, quick lookups, or tasks you can handle in one or two steps.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="skillmd-on-demand-skill-loading">SKILL.md: on-demand skill loading<a href="https://docs.aibox-demo.egroup.hu/blog/claude-code-style-agents#skillmd-on-demand-skill-loading" class="hash-link" aria-label="Direct link to SKILL.md: on-demand skill loading" title="Direct link to SKILL.md: on-demand skill loading" translate="no">​</a></h2>
<p>The second pattern we borrowed from Claude Code is progressive skill loading. Claude Code uses SKILL.md files with YAML frontmatter to define skills that can be loaded on demand. We adopted the same format, compatible with the <a href="https://agentskills.io/specification" target="_blank" rel="noopener noreferrer" class="">agentskills.io specification</a>:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">---</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> summarizer</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">description</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token scalar string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token scalar string" style="color:rgb(255, 121, 198)">  Use when the user asks to "summarize", "give me a tldr",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token scalar string" style="color:rgb(255, 121, 198)">  or "condense this document".</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">version</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> 1.0.0</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">---</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Document Summarizer</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">You are a summarization specialist</span><span class="token punctuation" style="color:rgb(248, 248, 242)">...</span><br></div></code></pre></div></div>
<p>The <code>SkillLoader</code> class scans a skills directory, parsing each <code>SKILL.md</code> file into a <code>Skill</code> dataclass:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">@dataclass</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">Skill</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    description</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    instructions</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    version</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">to_system_prompt</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f"## Skill: </span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">self</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token string-interpolation interpolation">name</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">\n\n</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">self</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token string-interpolation interpolation">instructions</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">"</span><br></div></code></pre></div></div>
<p>The critical optimization: at agent creation time, only skill names and one-line descriptions are injected into the system prompt. The full instructions stay unloaded until the agent explicitly calls <code>load_skill</code>:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> skills</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    skill_index </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"\n"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">join</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f"- **</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">s</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token string-interpolation interpolation">name</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">**: </span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">s</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token string-interpolation interpolation">description</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> s </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> skills</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    instructions </span><span class="token operator">+=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f"\n\n# Available skills\n\n"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f"Use load_skill to activate a skill before starting a task it covers.\n"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f"</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">skill_index</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    skill_map </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">s</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> s </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> s </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> skills</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">@function_tool</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">load_skill</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">skill_name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">"""Load a skill's full instructions."""</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        skill </span><span class="token operator">=</span><span class="token plain"> skill_map</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">skill_name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">not</span><span class="token plain"> skill</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f"Skill '</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">skill_name</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">' not found."</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> skill</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">to_system_prompt</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<p>This saves context window space. An agent with 20 available skills only pays for 20 one-line descriptions in its system prompt, not 20 full instruction sets. When the agent recognizes a task that matches a skill, it loads just that skill's instructions on demand.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="skill-precedence-and-import-planned">Skill precedence and import (planned)<a href="https://docs.aibox-demo.egroup.hu/blog/claude-code-style-agents#skill-precedence-and-import-planned" class="hash-link" aria-label="Direct link to Skill precedence and import (planned)" title="Direct link to Skill precedence and import (planned)" translate="no">​</a></h2>
<p>Skills will be layered with clear precedence: platform built-in skills as the base, tenant-custom skills extending or overriding them, and agent-specific skills taking highest priority. Currently, skills are loaded from the local filesystem. Database-backed skill registration and GitHub import are planned features that have not yet been implemented.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="current-subagent-model">Current subagent model<a href="https://docs.aibox-demo.egroup.hu/blog/claude-code-style-agents#current-subagent-model" class="hash-link" aria-label="Direct link to Current subagent model" title="Direct link to Current subagent model" translate="no">​</a></h2>
<p>The old hub-and-spoke model with an orchestrator and YAML <code>handoffs</code> was replaced by an adaptive main agent. The main agent receives the conversation, decides whether the work should stay inline or be delegated, and calls <code>Delegate</code> only for independent work that benefits from a focused context.</p>
<p>Subagent types are defined as Markdown files with YAML frontmatter in <code>deploy/config/subagents/</code>. The main agent sees the available types and writes the task brief itself. Subagents do not recursively spawn more subagents.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="lessons-learned">Lessons learned<a href="https://docs.aibox-demo.egroup.hu/blog/claude-code-style-agents#lessons-learned" class="hash-link" aria-label="Direct link to Lessons learned" title="Direct link to Lessons learned" translate="no">​</a></h2>
<p>Building these patterns taught us a few things:</p>
<p><strong>Sub-agent isolation matters.</strong> Early versions gave sub-agents all tools. This led to confused sub-agents trying to use tools they did not need, wasting tokens on irrelevant tool descriptions. Curated tool pools solved this.</p>
<p><strong>Skill descriptions need to be trigger-oriented.</strong> A description like "summarization tool" does not help the agent decide when to load the skill. A description like "Use when the user asks to summarize, give me a tldr, or condense this document" gives the agent clear matching criteria.</p>
<p><strong>The OpenAI Agents SDK's <code>Runner.run</code> is synchronous from the caller's perspective.</strong> The parent agent blocks while the sub-agent executes. For long-running tasks, this means the user sees no streaming output until the sub-agent finishes. We are exploring ways to stream sub-agent progress back to the parent.</p>]]></content>
        <author>
            <name>AI-in-a-Box Team</name>
            <uri>https://github.com/Mapika/ai-in-a-box</uri>
        </author>
        <category label="agents" term="agents"/>
        <category label="skills" term="skills"/>
        <category label="architecture" term="architecture"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Per-Session Docker Sandboxes: Isolated Code Execution for AI Agents]]></title>
        <id>https://docs.aibox-demo.egroup.hu/blog/sandboxed-execution</id>
        <link href="https://docs.aibox-demo.egroup.hu/blog/sandboxed-execution"/>
        <updated>2026-04-09T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[When your AI agent can execute arbitrary code, the execution environment is a security boundary. Shared subprocess execution on the host is fine for single-user prototypes. For a multi-tenant platform where different organizations share the same infrastructure, it is not even close to acceptable.]]></summary>
        <content type="html"><![CDATA[<p>When your AI agent can execute arbitrary code, the execution environment is a security boundary. Shared subprocess execution on the host is fine for single-user prototypes. For a multi-tenant platform where different organizations share the same infrastructure, it is not even close to acceptable.</p>
<blockquote>
<p><strong>Current reference:</strong> This post explains the design intent behind sandbox
isolation. The current API and deployment knobs are documented in the
<a class="" href="https://docs.aibox-demo.egroup.hu/reference/sandbox">Code Sandbox reference</a> and <a class="" href="https://docs.aibox-demo.egroup.hu/tutorials/run-code">Run Code tutorial</a>.</p>
</blockquote>
<p>We built a per-session Docker sandbox system that gives each user session its own isolated container.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-subprocess-execution-fails-at-multi-tenant">Why subprocess execution fails at multi-tenant<a href="https://docs.aibox-demo.egroup.hu/blog/sandboxed-execution#why-subprocess-execution-fails-at-multi-tenant" class="hash-link" aria-label="Direct link to Why subprocess execution fails at multi-tenant" title="Direct link to Why subprocess execution fails at multi-tenant" translate="no">​</a></h2>
<p>The simplest code execution approach is <code>subprocess.run()</code> on the host. Many AI tools start here. The problems show up quickly:</p>
<ul>
<li class=""><strong>No isolation.</strong> One tenant's code can read another tenant's files, environment variables, and process memory.</li>
<li class=""><strong>Resource exhaustion.</strong> A fork bomb or memory leak in one session takes down the entire host.</li>
<li class=""><strong>Privilege escalation.</strong> If the host process runs as root (common in Docker-in-Docker setups), executed code inherits those privileges.</li>
<li class=""><strong>No cleanup.</strong> Files, processes, and network connections persist between sessions.</li>
</ul>
<p>You could sandbox with something like nsjail or gVisor, but we wanted per-session state persistence (files created in one code execution are available in the next) and the ability to pass through GPUs for ML workloads. Docker containers gave us both.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-container-manager">The container manager<a href="https://docs.aibox-demo.egroup.hu/blog/sandboxed-execution#the-container-manager" class="hash-link" aria-label="Direct link to The container manager" title="Direct link to The container manager" translate="no">​</a></h2>
<p>The <code>ContainerManager</code> class manages the lifecycle of sandbox containers. Each tenant+session pair gets its own container with its own Docker volume for workspace persistence:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">ContainerManager</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">__init__</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> worker_image</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> idle_timeout</span><span class="token operator">=</span><span class="token number">900</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> mem_limit</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"512m"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                 cpu_quota</span><span class="token operator">=</span><span class="token number">100000</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> enable_gpu</span><span class="token operator">=</span><span class="token boolean">False</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> max_concurrent_sessions</span><span class="token operator">=</span><span class="token number">50</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">client </span><span class="token operator">=</span><span class="token plain"> docker</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">from_env</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sessions</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">dict</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> SandboxSession</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>When a code execution request arrives, <code>get_or_create</code> either returns an existing running container or creates a new one:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">get_or_create</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> tenant_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> session_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> SandboxSession</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    key </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f"</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">tenant_id</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">:</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">session_id</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> key </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sessions</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        session </span><span class="token operator">=</span><span class="token plain"> self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sessions</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">key</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        session</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">container</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token builtin" style="color:rgb(189, 147, 249)">reload</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> session</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">container</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">status </span><span class="token operator">==</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"running"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            session</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">touch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> session</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)"># Reject if at capacity</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sessions</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">&gt;=</span><span class="token plain"> self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">max_concurrent_sessions</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">raise</span><span class="token plain"> RuntimeError</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"Maximum concurrent sessions reached."</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)"># Create volume and container...</span><br></div></code></pre></div></div>
<p>The capacity limit prevents resource exhaustion at the platform level. When you hit the limit, new sessions are rejected with a clear error rather than silently degrading the host.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="container-hardening">Container hardening<a href="https://docs.aibox-demo.egroup.hu/blog/sandboxed-execution#container-hardening" class="hash-link" aria-label="Direct link to Container hardening" title="Direct link to Container hardening" translate="no">​</a></h2>
<p>Every sandbox container is created with aggressive security constraints:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">kwargs </span><span class="token operator">=</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">dict</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    image</span><span class="token operator">=</span><span class="token plain">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">worker_image</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    command</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"sleep infinity"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    user</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"1000:1000"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain">                   </span><span class="token comment" style="color:rgb(98, 114, 164)"># Non-root</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    mem_limit</span><span class="token operator">=</span><span class="token plain">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">mem_limit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    memswap_limit</span><span class="token operator">=</span><span class="token plain">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">mem_limit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain">        </span><span class="token comment" style="color:rgb(98, 114, 164)"># No swap</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    cpu_period</span><span class="token operator">=</span><span class="token number">100000</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    cpu_quota</span><span class="token operator">=</span><span class="token plain">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">cpu_quota</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    pids_limit</span><span class="token operator">=</span><span class="token number">256</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain">                      </span><span class="token comment" style="color:rgb(98, 114, 164)"># Prevent fork bombs</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    network_disabled</span><span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain">               </span><span class="token comment" style="color:rgb(98, 114, 164)"># No network access</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    security_opt</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"no-new-privileges:true"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    cap_drop</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"ALL"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain">                    </span><span class="token comment" style="color:rgb(98, 114, 164)"># Drop ALL Linux capabilities</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    cap_add</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"CHOWN"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"SETUID"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"SETGID"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"DAC_OVERRIDE"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    tmpfs</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"/tmp"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"size=100M,noexec,nosuid,nodev"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<p>Breaking this down:</p>
<ul>
<li class="">
<p><strong><code>user="1000:1000"</code></strong>: The container runs as a non-root user. Even if code exploits a vulnerability in the container runtime, it starts from an unprivileged position.</p>
</li>
<li class="">
<p><strong><code>cap_drop=["ALL"]</code></strong>: Every Linux capability is dropped. The container cannot mount filesystems, change network configuration, load kernel modules, or use raw sockets. Only the four capabilities needed for basic file operations are added back.</p>
</li>
<li class="">
<p><strong><code>no-new-privileges:true</code></strong>: Prevents privilege escalation via setuid/setgid binaries. Even if a setuid binary exists in the container image, it cannot gain elevated privileges.</p>
</li>
<li class="">
<p><strong><code>pids_limit=256</code></strong>: Hard cap on the number of processes. A fork bomb hits this limit and stops rather than consuming all PIDs on the host.</p>
</li>
<li class="">
<p><strong><code>network_disabled=True</code></strong>: No network access at all. The sandbox cannot make outbound HTTP requests, connect to databases, or exfiltrate data. All external operations go through the agent runtime's tools (web_search, scrape_url) which are not available inside the sandbox.</p>
</li>
<li class="">
<p><strong><code>mem_limit</code> with <code>memswap_limit</code> equal</strong>: Memory is capped with no swap. When the limit is hit, the OOM killer terminates the process rather than swapping to disk and slowing down the host.</p>
</li>
<li class="">
<p><strong><code>tmpfs</code> with <code>noexec</code></strong>: The <code>/tmp</code> directory is a size-limited tmpfs with no-exec. Code cannot write an executable to tmp and run it.</p>
</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="path-traversal-prevention">Path traversal prevention<a href="https://docs.aibox-demo.egroup.hu/blog/sandboxed-execution#path-traversal-prevention" class="hash-link" aria-label="Direct link to Path traversal prevention" title="Direct link to Path traversal prevention" translate="no">​</a></h2>
<p>File read/write operations go through a validation function before touching the container:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">@staticmethod</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">_validate_path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"\x00"</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">raise</span><span class="token plain"> ValueError</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"Path contains null bytes"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">startswith</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"/"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">raise</span><span class="token plain"> ValueError</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"Absolute paths are not allowed"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">".."</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">split</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"/"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">or</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">".."</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">split</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"\\"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">raise</span><span class="token plain"> ValueError</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"Path traversal ('..') is not allowed"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    normalized </span><span class="token operator">=</span><span class="token plain"> posixpath</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">normpath</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> normalized</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">startswith</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">".."</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">or</span><span class="token plain"> normalized</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">startswith</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"/"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">raise</span><span class="token plain"> ValueError</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"Path traversal is not allowed after normalization"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> normalized</span><br></div></code></pre></div></div>
<p>This prevents agents from reading <code>/etc/passwd</code> or writing to <code>/usr/bin</code> inside the container. All paths are relative to <code>/workspace</code>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="gpu-passthrough">GPU passthrough<a href="https://docs.aibox-demo.egroup.hu/blog/sandboxed-execution#gpu-passthrough" class="hash-link" aria-label="Direct link to GPU passthrough" title="Direct link to GPU passthrough" translate="no">​</a></h2>
<p>For ML workloads (training small models, running inference on data, generating visualizations with GPU acceleration), the container manager supports GPU passthrough:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">enable_gpu</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    kwargs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"device_requests"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        docker</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">types</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">DeviceRequest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">count</span><span class="token operator">=</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> capabilities</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"gpu"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">enable_network</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        kwargs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"network_disabled"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token boolean">False</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    kwargs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"cap_add"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">append</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"SYS_PTRACE"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># CUDA profiling</span><br></div></code></pre></div></div>
<p>GPU passthrough is separate from network access. GPU containers still keep
networking disabled unless an operator explicitly enables networked sandbox
sessions, and <code>SYS_PTRACE</code> is added only for CUDA profiling support. These
containers are created only when an administrator explicitly enables GPU mode
for a tenant.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="lifecycle-management">Lifecycle management<a href="https://docs.aibox-demo.egroup.hu/blog/sandboxed-execution#lifecycle-management" class="hash-link" aria-label="Direct link to Lifecycle management" title="Direct link to Lifecycle management" translate="no">​</a></h2>
<p>Containers are not permanent. A background task runs <code>cleanup_stale()</code> periodically, removing containers idle for longer than the configured timeout (default 15 minutes):</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">cleanup_stale</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    now </span><span class="token operator">=</span><span class="token plain"> time</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">time</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    stale_keys </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        k </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> k</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> s </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sessions</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">items</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> now </span><span class="token operator">-</span><span class="token plain"> s</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">last_used </span><span class="token operator">&gt;</span><span class="token plain"> self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">idle_timeout</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> key </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> stale_keys</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        session </span><span class="token operator">=</span><span class="token plain"> self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sessions</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">pop</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">key</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        session</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">container</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">stop</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">timeout</span><span class="token operator">=</span><span class="token number">5</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        session</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">container</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">remove</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">force</span><span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">volumes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">session</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">volume_name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">remove</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">force</span><span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<p>Both the container and its volume are destroyed. No state leaks between sessions after cleanup.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-not-e2b">Why not E2B?<a href="https://docs.aibox-demo.egroup.hu/blog/sandboxed-execution#why-not-e2b" class="hash-link" aria-label="Direct link to Why not E2B?" title="Direct link to Why not E2B?" translate="no">​</a></h2>
<p><a href="https://e2b.dev/" target="_blank" rel="noopener noreferrer" class="">E2B</a> is a solid hosted sandbox service. We evaluated it and decided against it for two reasons:</p>
<ol>
<li class="">
<p><strong>Sovereignty.</strong> E2B runs containers on their infrastructure. For an air-gapped or on-premise deployment, that is a non-starter. The sandbox system needs to run on the same infrastructure as everything else.</p>
</li>
<li class="">
<p><strong>GPU access.</strong> E2B does not currently support GPU passthrough. Our users need to run PyTorch and CUDA workloads inside sandboxes.</p>
</li>
</ol>
<p>The tradeoff is that we maintain our own container management code. For a platform that already runs Docker Compose and Kubernetes, this is a reasonable cost. The <code>ContainerManager</code> class is around 250 lines and handles the full lifecycle.</p>]]></content>
        <author>
            <name>AI-in-a-Box Team</name>
            <uri>https://github.com/Mapika/ai-in-a-box</uri>
        </author>
        <category label="security" term="security"/>
        <category label="sandbox" term="sandbox"/>
        <category label="docker" term="docker"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Security Hardening Notes from the Early Audit]]></title>
        <id>https://docs.aibox-demo.egroup.hu/blog/security-hardening</id>
        <link href="https://docs.aibox-demo.egroup.hu/blog/security-hardening"/>
        <updated>2026-04-08T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Historical post: This post records an early hardening sprint. Some]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p><strong>Historical post:</strong> This post records an early hardening sprint. Some
implementation details have changed since it was written. Use the current
<a class="" href="https://docs.aibox-demo.egroup.hu/admin/security">Security guide</a>, <a class="" href="https://docs.aibox-demo.egroup.hu/reference/auth">Authentication reference</a>,
and <a class="" href="https://docs.aibox-demo.egroup.hu/reference/audit-trail">Audit Trail reference</a> as the source of truth.</p>
</blockquote>
<p>Before shipping AI-in-a-Box to production, we ran a comprehensive security audit across all services. We found 27 vulnerabilities: 5 critical, 8 important, and 14 medium. This post captures the findings and the intended remediation work from that point in time.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-audit-process">The audit process<a href="https://docs.aibox-demo.egroup.hu/blog/security-hardening#the-audit-process" class="hash-link" aria-label="Direct link to The audit process" title="Direct link to The audit process" translate="no">​</a></h2>
<p>We reviewed every service for the OWASP Top 10, then added AI-specific checks: prompt injection pathways, guardrail bypass scenarios, and agent tool abuse vectors. Each finding was categorized by severity and given a specific remediation.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="critical-fixes">Critical fixes<a href="https://docs.aibox-demo.egroup.hu/blog/security-hardening#critical-fixes" class="hash-link" aria-label="Direct link to Critical fixes" title="Direct link to Critical fixes" translate="no">​</a></h2>
<p>These were stop-shipping issues that could lead to data exposure or remote code execution.</p>
<p><strong>SSRF via URL parameters.</strong> The web scraping tool accepted arbitrary URLs from the agent. An attacker could craft a prompt that made the agent scrape <code>http://169.254.169.254/latest/meta-data/</code> (the AWS metadata endpoint) or internal service URLs. Fix: we added URL validation with an allowlist of schemes (http/https only) and a denylist of internal IP ranges (RFC 1918, link-local, loopback). The Firecrawl integration now rejects requests to non-routable addresses.</p>
<p><strong>Command injection in sandbox.</strong> The code sandbox passed user input directly into shell commands via string formatting. A carefully crafted filename like <code>; rm -rf /</code> could escape the intended command. Fix: we replaced string interpolation with proper argument arrays and added input sanitization. The <code>exec_run</code> call now uses <code>["bash", "-c", command]</code> with the command as a single argument, and file paths go through <code>_validate_path</code> before use.</p>
<p><strong>Path traversal in file operations.</strong> The sandbox file read/write endpoints accepted paths like <code>../../etc/passwd</code> without validation. Fix: we added the <code>_validate_path</code> static method that rejects absolute paths, null bytes, and <code>..</code> components both before and after normalization. All file operations are confined to <code>/workspace</code>.</p>
<p><strong>Guardrail fail-open.</strong> When the guardrail service was unreachable (network error, timeout, crash), the agent runtime silently continued without safety checks. Fix: we changed the guardrail client to fail-closed. If the guardrail service returns an error or times out, the request is blocked with an explicit error message. The system prompt is checked on every request, not just the first.</p>
<p><strong>Audit chain race condition.</strong> The hash-chained audit log used a simple in-memory counter for chain sequencing. Under concurrent requests, two events could get the same sequence number, breaking the chain's integrity guarantee. Fix: we moved chain sequencing to a PostgreSQL sequence with a <code>SELECT ... FOR UPDATE</code> lock, ensuring strict ordering even under high concurrency.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="important-fixes">Important fixes<a href="https://docs.aibox-demo.egroup.hu/blog/security-hardening#important-fixes" class="hash-link" aria-label="Direct link to Important fixes" title="Direct link to Important fixes" translate="no">​</a></h2>
<p>These were significant security gaps that required attention before production use.</p>
<p><strong>httpx client reuse.</strong> Several services created a new <code>httpx.AsyncClient</code> on every request, leaking file descriptors under load. More importantly, this meant no connection pooling and no shared TLS session cache. Fix: we moved to a single shared client per service, created at startup and closed on shutdown, with proper connection limits and timeouts.</p>
<p><strong>Session eviction.</strong> There was no mechanism to invalidate active sessions. If a user's access was revoked in Keycloak, their existing JWT continued to work until expiration. Fix: we added a token revocation check to the API gateway that validates tokens against Keycloak's introspection endpoint on a configurable interval.</p>
<p><strong>Missing rate limiting on auth endpoints.</strong> The login and token refresh endpoints had no rate limiting, allowing brute-force attacks. Fix: we added per-IP rate limiting with exponential backoff on the gateway's auth routes.</p>
<p><strong>Agent tool abuse.</strong> Early delegation prototypes made recursive subagent
launches too easy. The current runtime exposes a bounded <code>Delegate</code> tool:
configured subagents return one final result to the main agent and cannot
dispatch further subagents.</p>
<p><strong>Webhook URL validation.</strong> The Dify integration accepted arbitrary webhook URLs for workflow callbacks without validation. Fix: same URL validation as the SSRF fix, applied to all outbound HTTP calls from the platform.</p>
<p><strong>Memory scope leakage.</strong> A bug in the memory service's query filtering allowed agents to read memories from other tenants when the <code>tenant_id</code> parameter was omitted from the query. Fix: we made <code>tenant_id</code> a required parameter at the API level, with the gateway injecting it from the JWT claims so agents cannot override it.</p>
<p><strong>Langfuse credentials in logs.</strong> The inference router logged full request/response bodies for debugging, which included the Langfuse API keys in headers. Fix: we added a header sanitization pass that redacts <code>Authorization</code>, <code>X-Api-Key</code>, and similar headers before logging.</p>
<p><strong>Unencrypted inter-service communication.</strong> Services communicated over plain HTTP within the Docker network. While the Docker network provides some isolation, this is insufficient for compliance requirements. Fix: we added mTLS support via the service mesh (Istio/Linkerd) in Kubernetes deployments, and documented TLS configuration for Docker Compose setups.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="medium-fixes">Medium fixes<a href="https://docs.aibox-demo.egroup.hu/blog/security-hardening#medium-fixes" class="hash-link" aria-label="Direct link to Medium fixes" title="Direct link to Medium fixes" translate="no">​</a></h2>
<p>These improved defense-in-depth without addressing immediate exploits.</p>
<p><strong>Docker healthchecks.</strong> None of the service containers had healthchecks defined. Docker Compose and Kubernetes had no way to detect a hung service. Fix: we added <code>/health</code> endpoints to every service and corresponding <code>HEALTHCHECK</code> directives in Dockerfiles.</p>
<p><strong>Kubernetes security contexts.</strong> The Helm chart deployments ran containers as root by default. Fix: we added <code>securityContext</code> blocks to all pod specs: <code>runAsNonRoot: true</code>, <code>readOnlyRootFilesystem: true</code>, <code>allowPrivilegeEscalation: false</code>, and <code>capabilities.drop: [ALL]</code>.</p>
<p><strong>NetworkPolicy.</strong> The Kubernetes deployment had no NetworkPolicy resources, meaning any pod could communicate with any other pod. Fix: we added NetworkPolicy resources that restrict traffic to the minimum required paths (e.g., only the agent runtime can reach the guardrail service).</p>
<p><strong>CORS configuration.</strong> The API gateway accepted <code>*</code> as the allowed origin in the default configuration. Fix: we changed the default to reject all cross-origin requests, requiring administrators to explicitly configure allowed origins.</p>
<p><strong>Missing Content-Security-Policy headers.</strong> The frontend nginx configuration served pages without CSP headers. Fix: we added a strict CSP that restricts script sources to the application's own origin.</p>
<p><strong>Redis without AUTH.</strong> The default Docker Compose configuration ran Redis without a password. Fix: we added a generated password to <code>deploy/.env.example</code> and configured all services to use it.</p>
<p><strong>MinIO default credentials.</strong> The default MinIO deployment used <code>minioadmin/minioadmin</code>. Fix: same approach as Redis, generated credentials in <code>deploy/.env.example</code>.</p>
<p><strong>Qdrant without API key.</strong> The vector database was accessible without authentication from any container on the Docker network. Fix: we enabled Qdrant's API key authentication and added the key to the service configuration.</p>
<p><strong>Log injection.</strong> User-supplied strings were written directly into structured logs without sanitization. A user could inject fake log entries. Fix: we switched to structured JSON logging with proper escaping across all Python services.</p>
<p><strong>Sandbox image pinning.</strong> The sandbox worker image tag was <code>latest</code>, meaning container content could change unpredictably. Fix: we pinned to a specific digest in the configuration.</p>
<p><strong>Guardrail policy caching.</strong> Guardrail policies were fetched from the database on every request with no caching. Under load, this created a hot path to PostgreSQL. Fix: we added a TTL cache (default 60 seconds) for policy lookups, with cache invalidation on policy updates.</p>
<p><strong>Missing request size limits.</strong> The API gateway had no maximum request body size, allowing memory exhaustion via large uploads. Fix: we added a configurable <code>max_body_size</code> (default 10MB) to the gateway.</p>
<p><strong>Dependency pinning.</strong> Python <code>requirements.txt</code> files used <code>&gt;=</code> version specifiers. Fix: we switched to exact pins with hashes for reproducible builds.</p>
<p><strong>TLS certificate validation.</strong> The external LLM gateway did not verify TLS certificates when connecting to OpenRouter. Fix: we enabled certificate verification with the system CA bundle.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="lessons-learned">Lessons learned<a href="https://docs.aibox-demo.egroup.hu/blog/security-hardening#lessons-learned" class="hash-link" aria-label="Direct link to Lessons learned" title="Direct link to Lessons learned" translate="no">​</a></h2>
<p>Three patterns stood out:</p>
<ol>
<li class="">
<p><strong>Fail-closed by default.</strong> Every security boundary (guardrails, auth, rate limiting) should block requests when the checking mechanism is unavailable. Fail-open is the most dangerous default in a security-sensitive system.</p>
</li>
<li class="">
<p><strong>Multi-tenant isolation is an ongoing discipline.</strong> Adding <code>tenant_id</code> to every table is the easy part. The hard part is ensuring every query path, every cache key, and every error message respects the tenant boundary.</p>
</li>
<li class="">
<p><strong>AI-specific attack surfaces are real.</strong> SSRF via agent tool calls, prompt injection to bypass guardrails, and recursive agent spawning are not theoretical. They showed up in our audit and needed specific mitigations.</p>
</li>
</ol>]]></content>
        <author>
            <name>AI-in-a-Box Team</name>
            <uri>https://github.com/Mapika/ai-in-a-box</uri>
        </author>
        <category label="security" term="security"/>
        <category label="audit" term="audit"/>
        <category label="hardening" term="hardening"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Designing a Warmer Documentation Theme]]></title>
        <id>https://docs.aibox-demo.egroup.hu/blog/documentation-theme</id>
        <link href="https://docs.aibox-demo.egroup.hu/blog/documentation-theme"/>
        <updated>2026-04-07T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[We wanted the AI-in-a-Box documentation to feel purpose-built rather than like a default Docusaurus theme with a color swap. This post describes the early design pass that gave the docs a warmer dark palette, denser tables, and more readable code blocks.]]></summary>
        <content type="html"><![CDATA[<p>We wanted the AI-in-a-Box documentation to feel purpose-built rather than like a default Docusaurus theme with a color swap. This post describes the early design pass that gave the docs a warmer dark palette, denser tables, and more readable code blocks.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="choosing-the-values">Choosing the values<a href="https://docs.aibox-demo.egroup.hu/blog/documentation-theme#choosing-the-values" class="hash-link" aria-label="Direct link to Choosing the values" title="Direct link to Choosing the values" translate="no">​</a></h2>
<p>We used browser inspection and side-by-side screenshots to compare headings, body text, code blocks, tables, sidebar links, and the navbar. That let us tune actual rendered values instead of guessing from a screenshot.</p>
<p>The key values we captured:</p>
<ul>
<li class=""><strong>Page background:</strong> <code>rgb(14, 12, 13)</code>, nearly black with a warm undertone, not pure <code>#000</code></li>
<li class=""><strong>Body text:</strong> <code>#a6a1a0</code> on dark, Inter font, 16px, line-height 1.75</li>
<li class=""><strong>Headings:</strong> <code>#e6e1e0</code>, DM Sans, varying weights from 600 to 700</li>
<li class=""><strong>Primary accent:</strong> A warm amber, adapted to <code>#d4a574</code> to give the docs their own identity while staying readable on dark backgrounds.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-warm-dark-theme">The warm dark theme<a href="https://docs.aibox-demo.egroup.hu/blog/documentation-theme#the-warm-dark-theme" class="hash-link" aria-label="Direct link to The warm dark theme" title="Direct link to The warm dark theme" translate="no">​</a></h2>
<p>The default Docusaurus dark theme uses cool grays and blues. We wanted a warmer neutral range, so the backgrounds have a slight red/brown shift:</p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token selector attribute punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token selector attribute attr-name" style="color:rgb(241, 250, 140)">data-theme</span><span class="token selector attribute operator" style="color:rgb(255, 121, 198)">=</span><span class="token selector attribute attr-value" style="color:rgb(255, 121, 198)">'dark'</span><span class="token selector attribute punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">--ifm-background-color</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token hexcode color">#0e0c0d</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">--ifm-background-surface-color</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token hexcode color">#131112</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">--ifm-font-color-base</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token hexcode color">#a6a1a0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">--ifm-heading-color</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token hexcode color">#e6e1e0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">--ifm-navbar-background-color</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token hexcode color">#0e0c0d</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">--ifm-footer-background-color</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token hexcode color">#0a0909</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>The difference between <code>#0e0c0d</code> and Docusaurus's default <code>#1b1b1d</code> is subtle in isolation but obvious side by side. The warm background makes text feel less harsh and reduces eye strain during long reading sessions.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="table-styling">Table styling<a href="https://docs.aibox-demo.egroup.hu/blog/documentation-theme#table-styling" class="hash-link" aria-label="Direct link to Table styling" title="Direct link to Table styling" translate="no">​</a></h2>
<p>This was the biggest visual improvement. Default Docusaurus tables have alternating row backgrounds, visible borders on all sides, and a distinct header background color. The revised tables use just separator lines:</p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token selector class" style="color:rgb(255, 121, 198)">.markdown</span><span class="token selector" style="color:rgb(255, 121, 198)"> table thead tr</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">border-bottom</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">1</span><span class="token unit">px</span><span class="token plain"> solid </span><span class="token color function" style="color:rgb(80, 250, 123)">rgba</span><span class="token color punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token color number">70</span><span class="token color punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token color"> </span><span class="token color number">65</span><span class="token color punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token color"> </span><span class="token color number">64</span><span class="token color punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token color"> </span><span class="token color number">0.5</span><span class="token color punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token important">!important</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">background</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token color">transparent</span><span class="token plain"> </span><span class="token important">!important</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token selector class" style="color:rgb(255, 121, 198)">.markdown</span><span class="token selector" style="color:rgb(255, 121, 198)"> table tbody tr</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">background</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token color">transparent</span><span class="token plain"> </span><span class="token important">!important</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">border-bottom</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">1</span><span class="token unit">px</span><span class="token plain"> solid </span><span class="token color function" style="color:rgb(80, 250, 123)">rgba</span><span class="token color punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token color number">45</span><span class="token color punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token color"> </span><span class="token color number">40</span><span class="token color punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token color"> </span><span class="token color number">38</span><span class="token color punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token color"> </span><span class="token color number">0.5</span><span class="token color punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token important">!important</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>We had to use <code>!important</code> liberally because Docusaurus injects table styles at a high specificity through its theme. Killing the striped row backgrounds (<code>--ifm-table-stripe-background: transparent</code>) and the default border color (<code>--ifm-table-border-color: transparent</code>) handles most of it, but some rules needed the override.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="code-blocks">Code blocks<a href="https://docs.aibox-demo.egroup.hu/blog/documentation-theme#code-blocks" class="hash-link" aria-label="Direct link to Code blocks" title="Direct link to Code blocks" translate="no">​</a></h2>
<p>The code blocks use a 16px border radius and a <code>#1f1f1f</code> background that is slightly lighter than the page background, giving them subtle contrast without a visible border:</p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token selector class" style="color:rgb(255, 121, 198)">.prism-code</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">border-radius</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">16</span><span class="token unit">px</span><span class="token plain"> </span><span class="token important">!important</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">border</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> none </span><span class="token important">!important</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">font-size</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">14</span><span class="token unit">px</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">line-height</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">1.7</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">padding</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">14</span><span class="token unit">px</span><span class="token plain"> </span><span class="token number">16</span><span class="token unit">px</span><span class="token plain"> </span><span class="token important">!important</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token selector attribute punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token selector attribute attr-name" style="color:rgb(241, 250, 140)">data-theme</span><span class="token selector attribute operator" style="color:rgb(255, 121, 198)">=</span><span class="token selector attribute attr-value" style="color:rgb(255, 121, 198)">'dark'</span><span class="token selector attribute punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token selector" style="color:rgb(255, 121, 198)"> </span><span class="token selector class" style="color:rgb(255, 121, 198)">.prism-code</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">background-color</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token hexcode color">#1f1f1f</span><span class="token plain"> </span><span class="token important">!important</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>The monospace font is JetBrains Mono, which has better readability at small sizes than most monospace options and includes programming ligatures.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-result">The result<a href="https://docs.aibox-demo.egroup.hu/blog/documentation-theme#the-result" class="hash-link" aria-label="Direct link to The result" title="Direct link to The result" translate="no">​</a></h2>
<p>The total CSS override is about 500 lines. The visual impact is significant: the documentation feels like a purpose-built site rather than a template with swapped colors. Three fonts (Inter for body, DM Sans for headings, JetBrains Mono for code) provide clear hierarchy. The warm palette reduces the clinical feel common in technical documentation.</p>
<p>The result is still Docusaurus underneath, but it reads more like a focused product manual than a generated documentation shell.</p>]]></content>
        <author>
            <name>AI-in-a-Box Team</name>
            <uri>https://github.com/Mapika/ai-in-a-box</uri>
        </author>
        <category label="documentation" term="documentation"/>
        <category label="design" term="design"/>
        <category label="css" term="css"/>
    </entry>
</feed>