<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="rss.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Andres Cidel Blog</title>
        <link>https://andrescidel.com/</link>
        <description>Andres Cidel Blog</description>
        <lastBuildDate>Wed, 24 Jun 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <item>
            <title><![CDATA[Docker Is the Security Layer Your AI Agent Doesn't Know It Needs]]></title>
            <link>https://andrescidel.com/docker-mcp-security</link>
            <guid>https://andrescidel.com/docker-mcp-security</guid>
            <pubDate>Wed, 24 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[MCP is the fastest-moving protocol in AI right now — and almost nobody is talking about its security model. Here's what the default setup gets wrong, and how Docker containers fix it.]]></description>
            <content:encoded><![CDATA[<p>Last September, a developer opened their email client and sent a message to a client. Normal enough.</p>
<p>But somewhere in that email — invisibly, silently — a copy went somewhere else. Not to a spam filter. Not to a backup server. To an attacker.</p>
<p>They weren't hacked in the traditional sense. No phishing link. No password breach. The tool they were using to <em>send</em> the email — an AI tool, one they'd installed in about thirty seconds — had been quietly updated. And that update had one extra line of code.</p>
<p>This wasn't an obscure vulnerability. Thousands of developers were using the same tool. Almost none of them knew.</p>
<p>That's the story I want to unpack today.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-mcp-gold-rush">The MCP Gold Rush<a href="https://andrescidel.com/docker-mcp-security#the-mcp-gold-rush" class="hash-link" aria-label="Direct link to The MCP Gold Rush" title="Direct link to The MCP Gold Rush" translate="no">​</a></h2>
<p>Right now, there is a protocol spreading through the AI ecosystem faster than almost anything I've seen. It's called MCP — Model Context Protocol.</p>
<p>The idea is beautiful in its simplicity: give your AI model <em>real tools</em>. Let it read your files. Query your database. Send emails. Book calendar events. Push code. Every major AI framework — Claude, LLM agents, Cursor — has adopted it. There are now thousands of MCP servers available: plugins for GitHub, Notion, PostgreSQL, Stripe, you name it.</p>
<p>It is, legitimately, one of the most exciting developments in AI right now.</p>
<p>But when a developer wants to add one of these tools, this is what that looks like:</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"mcpServers"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"github"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"command"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"npx"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"args"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"-y"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@modelcontextprotocol/server-github"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"env"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"GITHUB_TOKEN"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"ghp_..."</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>You paste that into a config file and restart the app. That's it.</p>
<p>Now — what is that actually doing?</p>
<p><code>npx</code> is a Node.js command that goes to the internet, finds a package, and runs it. Right now. On your machine. With your permissions. No sandbox. No signature check. No review. Full access to your filesystem. Full access to your network. With your credentials sitting right there in the config.</p>
<p>And the email incident I opened with? That's exactly what happened.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="four-ways-the-default-mcp-setup-fails">Four Ways the Default MCP Setup Fails<a href="https://andrescidel.com/docker-mcp-security#four-ways-the-default-mcp-setup-fails" class="hash-link" aria-label="Direct link to Four Ways the Default MCP Setup Fails" title="Direct link to Four Ways the Default MCP Setup Fails" translate="no">​</a></h2>
<p>This isn't one problem — it's a cluster of problems that compound each other.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-arbitrary-code-execution">1. Arbitrary code execution<a href="https://andrescidel.com/docker-mcp-security#1-arbitrary-code-execution" class="hash-link" aria-label="Direct link to 1. Arbitrary code execution" title="Direct link to 1. Arbitrary code execution" translate="no">​</a></h3>
<p>When you run <code>npx @modelcontextprotocol/server-github</code>, you are executing code you have never reviewed, from a package registry with essentially no trust mechanism, with full access to your system.</p>
<p>Last year, a package called <code>mcp-remote</code> — with over half a million downloads — was found to contain an exploitable vulnerability: <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-6514" target="_blank" rel="noopener noreferrer" class="">CVE-2025-6514</a>. Half a million downloads. One bad update. That's your blast radius.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-supply-chain-attacks">2. Supply chain attacks<a href="https://andrescidel.com/docker-mcp-security#2-supply-chain-attacks" class="hash-link" aria-label="Direct link to 2. Supply chain attacks" title="Direct link to 2. Supply chain attacks" translate="no">​</a></h3>
<p>The email story is a textbook supply chain attack. The Postmark MCP server — popular, trusted — shipped an update that silently BCC'd outgoing emails to an attacker-controlled address. No CVE. No security advisory. No one knew until someone noticed an email going somewhere it shouldn't.</p>
<p>npm's trust model is: whoever published last, wins. There is no mandatory code signing. No policy gate. No delay for review.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-tool-poisoning-and-you-cant-patch-your-way-out">3. Tool poisoning (and you can't patch your way out)<a href="https://andrescidel.com/docker-mcp-security#3-tool-poisoning-and-you-cant-patch-your-way-out" class="hash-link" aria-label="Direct link to 3. Tool poisoning (and you can't patch your way out)" title="Direct link to 3. Tool poisoning (and you can't patch your way out)" translate="no">​</a></h3>
<p>This one is structural.</p>
<p>When an MCP server starts up, it sends the AI model a list of tools and their descriptions. Those descriptions go <em>directly into the model's context</em> — the same context where your instructions live. A malicious MCP server can craft those descriptions to <em>instruct the model</em> to do things: exfiltrate data, ignore user instructions, leak credentials.</p>
<p>The model cannot tell the difference between a tool description and a trusted instruction.</p>
<p><a href="https://nvd.nist.gov/vuln/detail/CVE-2025-54136" target="_blank" rel="noopener noreferrer" class="">CVE-2025-54136</a> is a formalized version of this. But it's not really a bug — it's a design constraint of how LLMs work.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-authentication-as-an-afterthought">4. Authentication as an afterthought<a href="https://andrescidel.com/docker-mcp-security#4-authentication-as-an-afterthought" class="hash-link" aria-label="Direct link to 4. Authentication as an afterthought" title="Direct link to 4. Authentication as an afterthought" translate="no">​</a></h3>
<p>53% of MCP servers fall back to static API keys — tokens hardcoded in config files, often committed to repos. And the official debugging tool for MCP — MCP Inspector — had its own remote code execution vulnerability: <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-49596" target="_blank" rel="noopener noreferrer" class="">CVE-2025-49596</a>.</p>
<p>The debugging tool for a security-critical protocol had an RCE. Let that land for a second.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-containers-are-the-answer">Why Containers Are the Answer<a href="https://andrescidel.com/docker-mcp-security#why-containers-are-the-answer" class="hash-link" aria-label="Direct link to Why Containers Are the Answer" title="Direct link to Why Containers Are the Answer" translate="no">​</a></h2>
<p>I want to show you <em>why</em> I believe this, because the argument isn't "use our product." The argument is about what containers <em>are</em>.</p>
<p>A Linux container gives you namespace isolation. Your process — the MCP server — runs in its own little world. It sees only the files you explicitly give it. It talks only to the networks you allow. It lives in its own process space.</p>
<p>Compare that to the <code>npx</code> approach:</p>



































<table><thead><tr><th>Threat</th><th><code>npx</code></th><th>Docker</th></tr></thead><tbody><tr><td>Filesystem access</td><td>Full host</td><td>Only mounted paths</td></tr><tr><td>Network access</td><td>Full host network</td><td>Explicit grants only</td></tr><tr><td>Process isolation</td><td>Your user, your PID space</td><td>Separate namespace</td></tr><tr><td>Resource limits</td><td>None</td><td><code>--memory</code>, <code>--cpus</code> enforced</td></tr><tr><td>Rollback</td><td>Re-run and hope</td><td>Pin a digest, pull</td></tr></tbody></table>
<p>Let's use the GitHub MCP server as a real example. Before running anything, scan it:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">docker scout cves mcp/github:latest</span><br></div></code></pre></div></div>
<p>This checks the image against known vulnerability databases. If there's a critical CVE in any bundled library, you see it before it ever touches your system.</p>
<p>Then verify its provenance:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">docker buildx imagetools inspect mcp/github:latest \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --format '{{ json .Provenance }}'</span><br></div></code></pre></div></div>
<p>This reads the cryptographic provenance attestation baked into the image at build time. You can see exactly which source commit was compiled into this binary, which build pipeline produced it, and confirm the digest matches what Docker Hub has on record. With <code>npx</code>, you get none of this.</p>
<p>Now run it:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">docker run --rm -i \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --read-only \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --tmpfs /tmp \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --cap-drop ALL \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -e GITHUB_TOKEN="$GITHUB_TOKEN" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  mcp/github:latest</span><br></div></code></pre></div></div>
<p>A few flags worth understanding:</p>
<ul>
<li class=""><code>--read-only</code> — the container cannot write to your disk</li>
<li class=""><code>--cap-drop ALL</code> — strips all Linux kernel capabilities from the process (raw sockets, port binding, filesystem mounting). Normal outbound HTTPS to <code>api.github.com</code> doesn't need any of these — it's just TCP on port 443. So you get a hardened process that still does its job.</li>
</ul>
<p>This server needs network access to reach <code>api.github.com</code>, so we don't use <code>--network none</code>. But we've already scanned the image and verified its provenance. We know exactly what we're running, and the kernel has stripped every privilege it doesn't need.</p>
<p>The tool poisoning problem is still a model-level issue — Docker doesn't fix what happens inside the model's context window. But it <em>severely limits</em> what a compromised server can actually <em>do</em> with those instructions.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-docker-mcp-tooling-available-today">The Docker MCP Tooling Available Today<a href="https://andrescidel.com/docker-mcp-security#the-docker-mcp-tooling-available-today" class="hash-link" aria-label="Direct link to The Docker MCP Tooling Available Today" title="Direct link to The Docker MCP Tooling Available Today" translate="no">​</a></h2>
<p>Docker has been quietly building a native MCP ecosystem, and it's further along than most people realize.</p>
<p>There's a <a href="https://hub.docker.com/mcp" target="_blank" rel="noopener noreferrer" class="">catalog of 270+ MCP servers</a> available as signed OCI images on Docker Hub. Pre-built, scanned for vulnerabilities, with provenance attestations — a cryptographic record of exactly which code was compiled into that image.</p>
<p>And instead of manually writing the Docker command above, there's a CLI:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">docker mcp install github</span><br></div></code></pre></div></div>
<p>That one command finds the image, verifies it, and injects a hardened <code>docker run</code> config — with <code>--read-only</code>, <code>--cap-drop ALL</code>, and scoped mounts — into your Claude Desktop setup. The flags from the previous section are exactly what it produces. You can do it manually to understand what's happening, or let the CLI handle it. Either way, the security posture is the same.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-mcp-gateway">The MCP Gateway<a href="https://andrescidel.com/docker-mcp-security#the-mcp-gateway" class="hash-link" aria-label="Direct link to The MCP Gateway" title="Direct link to The MCP Gateway" translate="no">​</a></h3>
<p>For teams, the piece that matters most is the <a href="https://github.com/docker/mcp-gateway" target="_blank" rel="noopener noreferrer" class="">MCP Gateway</a>.</p>
<p>It's open source — a reverse proxy for your MCP servers. Instead of each MCP server implementing its own auth (and remember, 53% don't), the Gateway handles auth once, for all of them.</p>
<p>There's also a <code>--block-secrets</code> flag. Every payload passing through the Gateway gets scanned for credential patterns — tokens, API keys, passwords. If something looks like a secret, it blocks the request.</p>
<p>That's your audit trail. That's what lets you walk into a security review and say: here is every tool call the agent made, every argument it passed, every response it got. No more "the AI did something, we're not sure what."</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-this-unlocks-for-teams">What This Unlocks for Teams<a href="https://andrescidel.com/docker-mcp-security#what-this-unlocks-for-teams" class="hash-link" aria-label="Direct link to What This Unlocks for Teams" title="Direct link to What This Unlocks for Teams" translate="no">​</a></h2>
<p>The reason most teams can't get MCP approved by their security teams isn't technical — it's because the answer to "what can this thing access?" is currently "everything on the developer's machine." And the answer to "what did it do last Tuesday?" is "we don't know."</p>
<p>Containers change those answers:</p>
<ul>
<li class="">"What can it access?" → Only the paths you mounted.</li>
<li class="">"What network calls did it make?" → Only the endpoints it was permitted to reach — or none at all for servers that don't need external access.</li>
<li class="">"Can we roll back?" → Yes. Pin the digest, restart.</li>
</ul>
<p>The Gateway changes the audit answers:</p>
<ul>
<li class="">"What did it do last Tuesday?" → Here are the logs.</li>
<li class="">"Were any secrets exposed?" → Blocked before they left the process.</li>
<li class="">"Can we enforce which tools the agent can call?" → Yes. Policy at the Gateway level.</li>
</ul>
<p>The multi-tenant case is where this gets especially interesting. The MCP spec has no concept of tenant isolation. If you're building a product where multiple users share an MCP backend — which is where the ecosystem is heading — you have to build that boundary yourself. A container gives you that boundary. Each session gets its own container, its own mounts, its own network policy. When the session ends, the container dies. No state bleeds across users.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-to-do-next">What to Do Next<a href="https://andrescidel.com/docker-mcp-security#what-to-do-next" class="hash-link" aria-label="Direct link to What to Do Next" title="Direct link to What to Do Next" translate="no">​</a></h2>
<p>MCP is genuinely exciting. The speed at which developers are building with it — I find it exciting. I use these tools. I think the direction is right.</p>
<p>But the default path — <code>npx</code>, no sandbox, full host access, hardcoded tokens — has already produced real incidents with real consequences.</p>
<p>The good news is the fix is not complicated. If you're running MCP tools today, in 15 minutes you can be running them in containers instead. The <code>docker mcp install</code> command handles the config. The catalog handles the images. The Gateway handles the audit trail.</p>
<p>If you're a security engineer who's been handed an MCP approval request and you don't know where to start, use the failure modes from this post as your threat model agenda.</p>
<p>And if you're building MCP servers — please, publish them as container images. With provenance. With signed digests. Your users are trusting you with access to their systems.</p>
<hr>
<p><strong>Resources:</strong></p>
<ul>
<li class=""><a href="https://hub.docker.com/mcp" target="_blank" rel="noopener noreferrer" class="">Docker MCP Catalog</a></li>
<li class=""><a href="https://github.com/docker/mcp-gateway" target="_blank" rel="noopener noreferrer" class="">MCP Gateway on GitHub</a></li>
<li class=""><a href="https://docs.docker.com/ai/mcp-catalog-and-toolkit/" target="_blank" rel="noopener noreferrer" class="">Docker MCP CLI docs</a></li>
</ul>]]></content:encoded>
            <category>docker</category>
            <category>mcp</category>
            <category>security</category>
            <category>ai</category>
            <category>containers</category>
        </item>
        <item>
            <title><![CDATA[Alertmanager webhook receivers - custom HTTP requests]]></title>
            <link>https://andrescidel.com/alertmanager-custom-http-request</link>
            <guid>https://andrescidel.com/alertmanager-custom-http-request</guid>
            <pubDate>Fri, 13 May 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[AlertManager offers the possibility to configure receivers to forward grouped alerts to multiple notification integrations.]]></description>
            <content:encoded><![CDATA[<p>AlertManager offers the possibility to configure receivers to forward grouped alerts to multiple notification integrations.</p>
<!-- -->
<p>I recently needed to send the notifications to a webhook receiver. The required configuration is <a href="https://prometheus.io/docs/alerting/latest/configuration/#webhook_config" target="_blank" rel="noopener noreferrer" class="">well documented</a>.</p>
<p>The Openshift Monitoring stack offers the possibility to add some custom configuration to Prometheus and Alertmanager components. In this case, the AlertManager needed to be configured and Openshift offers that possibilty, for more details check <a href="https://docs.openshift.com/container-platform/4.10/monitoring/managing-alerts.html#applying-custom-alertmanager-configuration_managing-alerts" target="_blank" rel="noopener noreferrer" class="">the documentation</a>.</p>
<p>But the problem I had was that the front endpoint that would receive the alerts had some strict requirements:</p>
<ul>
<li class="">A TLS client certificate.</li>
<li class="">A custom HTTP header needed by the gateway.</li>
</ul>
<p>The first issue was that the Alertmanager is controlled, configured and managed by the Openshift Monitoring Operator and it does not allow to add volumes to the Alertmanager StatefulSet because the controller will force its state back, and this is a problem because the TLS configuration requires the location of the certificate and the key, since I cannot mount a Secret as volume in the filesystem of the pod then I cannot provide the credentials.</p>
<p>The second problem was the HTTP header. Even if I have the full control of the configuration of Alertmanager I cannot add HTTP headers or even URL params to the URL of the host that receives the notifications.</p>
<p>The only solution I found was to use an intermediary proxy to receive the payload sent by Alertmanager and re-send the request to the desired receiver with the custom TLS configuration and the custom header. I had to add the webhook receiver to the configuration and provide the name of the service that would dispatch the notifications and forward them to the special receiver.</p>
<p>This pattern is more or less what a Service Mesh would do with sidecar proxies since they intercept the HTTP requests and rework them if necessary.</p>]]></content:encoded>
            <category>Alertmanager</category>
            <category>OpenShift</category>
        </item>
    </channel>
</rss>