<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Stefan Iancu</title>
    <description>The latest articles on DEV Community by Stefan Iancu (@ianqqu).</description>
    <link>https://dev.to/ianqqu</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3888592%2F06103a7a-9816-45a3-a80f-941b9bfc1746.jpg</url>
      <title>DEV Community: Stefan Iancu</title>
      <link>https://dev.to/ianqqu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ianqqu"/>
    <language>en</language>
    <item>
      <title>A self-hosted Google reCAPTCHA alternative (we ship it)</title>
      <dc:creator>Stefan Iancu</dc:creator>
      <pubDate>Fri, 29 May 2026 06:29:01 +0000</pubDate>
      <link>https://dev.to/ianqqu/a-self-hosted-google-recaptcha-alternative-we-ship-it-276h</link>
      <guid>https://dev.to/ianqqu/a-self-hosted-google-recaptcha-alternative-we-ship-it-276h</guid>
      <description>&lt;p&gt;You know the one. You're trying to subscribe to a newsletter, and Google asks you to identify all the bicycles. You squint at the photo. You miss one pixel. You get a new grid, this time with motorcycles, and you start wondering if you're the bot.&lt;/p&gt;

&lt;p&gt;That's reCAPTCHA. It works, kind of. It also loads a JavaScript bundle from &lt;code&gt;google.com&lt;/code&gt; on every page it appears on, ships your browsing behavior to Google's risk-scoring API, and gets flagged in nearly every cookie-compliance audit we've seen.&lt;/p&gt;

&lt;p&gt;The captcha on &lt;a href="https://orkestr.eu" rel="noopener noreferrer"&gt;orkestr&lt;/a&gt; is &lt;a href="https://altcha.org/" rel="noopener noreferrer"&gt;Altcha&lt;/a&gt;. Self-hosted, no third-party JS, no Google, no behavioral tracking. Users don't see a puzzle. There's a single checkbox and a quiet "verifying..." while the browser does some math. About a second, total.&lt;/p&gt;

&lt;p&gt;This post is why we picked it over the better-known options. If you're looking for a Google reCAPTCHA alternative and you care about GDPR, this is the path we'd recommend.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest verdict, up front
&lt;/h2&gt;

&lt;p&gt;For low-to-moderate traffic forms (signup, contact, comments, login) where you're not under sustained ML-driven attack: &lt;strong&gt;self-hosted Altcha is the right pick&lt;/strong&gt;. No third-party calls, no GDPR paperwork, no user-hostile puzzles, a small widget loaded from your own origin.&lt;/p&gt;

&lt;p&gt;For very high-traffic public surfaces under active attack by sophisticated operators (e-commerce checkouts, ticket drops, large social platforms) you probably still want a managed service with behavioral ML. Altcha's proof-of-work raises the per-request cost; at internet scale, a real bot farm absorbs it.&lt;/p&gt;

&lt;p&gt;"Low-to-moderate traffic forms" is most of the internet. Altcha wins for most people.&lt;/p&gt;

&lt;h2&gt;
  
  
  How they compare
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Google reCAPTCHA v3&lt;/th&gt;
&lt;th&gt;hCaptcha&lt;/th&gt;
&lt;th&gt;Cloudflare Turnstile&lt;/th&gt;
&lt;th&gt;Altcha (self-hosted)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data leaves your origin&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (google.com)&lt;/td&gt;
&lt;td&gt;Yes (hcaptcha.com)&lt;/td&gt;
&lt;td&gt;Yes (cloudflare.com)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Behavior tracking&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Heavy&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Light&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;JS bundle (gzipped)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;300+ KB&lt;/td&gt;
&lt;td&gt;250+ KB&lt;/td&gt;
&lt;td&gt;85+ KB&lt;/td&gt;
&lt;td&gt;34 KB, your origin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;User puzzle&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Often&lt;/td&gt;
&lt;td&gt;Often&lt;/td&gt;
&lt;td&gt;Rarely&lt;/td&gt;
&lt;td&gt;Never&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GDPR posture&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;US sub-processor&lt;/td&gt;
&lt;td&gt;US sub-processor&lt;/td&gt;
&lt;td&gt;US sub-processor&lt;/td&gt;
&lt;td&gt;None, it's your server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Self-hostable&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Free + paid tiers&lt;/td&gt;
&lt;td&gt;Free + paid tiers&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Free (your CPU)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The "data leaves your origin" row is the whole point. Every other column flows from it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What reCAPTCHA actually does
&lt;/h2&gt;

&lt;p&gt;reCAPTCHA v3 (the "invisible" one) is not really a captcha. It's a behavioral fingerprinting library that returns a score between 0 (likely bot) and 1 (likely human). The score comes from Google looking at your browser, your IP, your mouse movement, your cookies, and crucially, your entire Google account state if you happen to be logged in.&lt;/p&gt;

&lt;p&gt;That's a feature for fraud detection at scale. It's a legal and reputational risk for an EU business, because every form on your site becomes a Google data-collection endpoint. Schrems II turned every cross-border transfer to a US ad-tech company into a per-tool compliance question. reCAPTCHA is exactly that tool.&lt;/p&gt;

&lt;p&gt;The compliance picture got materially worse on April 2, 2026. Google reclassified reCAPTCHA from data controller to data processor, which sounds like an internal Google detail but isn't. EU operators are now fully on the hook for GDPR compliance on every reCAPTCHA hit: a signed DPA with Google, an explicit legal basis documented in your privacy policy, and your team handling any data subject requests. Data still goes to US servers. The compliance burden didn't shrink, it moved to your desk.&lt;/p&gt;

&lt;p&gt;EU DPOs ask the same question in different words: if you don't control the JavaScript on your login page, who does? It's worth sitting with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloudflare Turnstile is better, but it's still Cloudflare
&lt;/h2&gt;

&lt;p&gt;Turnstile is the industry response to reCAPTCHA-fatigue. It's free, the UX is good (most users see a checkbox, not a puzzle), and Cloudflare is more transparent about what it collects than Google has ever been. If you're not in the EU and you don't care about CLOUD Act exposure, Turnstile is a fine pick. It's the second-best option here.&lt;/p&gt;

&lt;p&gt;For an EU-native deploy platform, though, every US sub-processor is a row on a DPA that EU buyers read line by line. We wanted captcha to be a thing on our origin, not a thing on someone else's. Turnstile doesn't get you there.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Altcha does instead
&lt;/h2&gt;

&lt;p&gt;Altcha replaces the "score-the-user" model with a "make-the-bot-pay" model. Proof of work.&lt;/p&gt;

&lt;p&gt;The server generates a random salt, a nonce, and a target prefix, signs the whole envelope with an HMAC key only the server knows, and hands it to the browser. A Web Worker iterates a counter, combining it with the nonce and running PBKDF2-SHA256 each step, until the derived key starts with the target prefix. When it finds a matching counter, the server gets the challenge plus the solution back, verifies the HMAC, verifies the prefix match, and accepts the form.&lt;/p&gt;

&lt;p&gt;The whole thing takes about a second on a modern laptop. A human doesn't notice. A bot farm trying to submit ten thousand forms per second per IP suddenly has to spin up real CPU per submission.&lt;/p&gt;

&lt;p&gt;There's no third party. The challenge endpoint sits on your domain. The widget JavaScript serves from your origin. The HMAC key never leaves your server. There's nothing to add to a sub-processor list, because there's no sub-processor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two gotchas if you self-host Altcha
&lt;/h2&gt;

&lt;p&gt;The integration is small. One new endpoint, one HMAC environment variable (&lt;code&gt;openssl rand -hex 32&lt;/code&gt;), one React component for the widget. Two things are easy to miss, both worth flagging.&lt;/p&gt;

&lt;h3&gt;
  
  
  Don't let a CDN cache the challenge
&lt;/h3&gt;

&lt;p&gt;We sit behind &lt;a href="https://bunny.net" rel="noopener noreferrer"&gt;Bunny CDN&lt;/a&gt; for SSL and edge caching. Our first wiring of Altcha had a weird failure mode: legitimate users were intermittently getting "captcha already used" errors on first submit.&lt;/p&gt;

&lt;p&gt;The cause was the obvious one in hindsight. The CDN was caching responses from &lt;code&gt;GET /api/altcha/challenge&lt;/code&gt;. Two different users were getting the same signed challenge, the first submission consumed it via replay protection, the second user hit a 400.&lt;/p&gt;

&lt;p&gt;The fix is one header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@router.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/altcha/challenge&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_challenge&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;challenge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mint_challenge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hmac_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;media_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cache-Control&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no-store&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're behind any CDN, set this or you'll spend an afternoon wondering why your captcha is mass-failing in production but works locally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replay protection needs a real store
&lt;/h3&gt;

&lt;p&gt;The replay attack on a captcha is obvious: capture a valid submission, replay it many times. Most official Altcha libraries (Django, .NET, and others) ship with a replay store built in, so check yours before reinventing it. We rolled our own integration against FastAPI, which meant we added one ourselves. Redis &lt;code&gt;SETNX&lt;/code&gt; keyed on the verified solution hash:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Returns True if key was set (first use), False if it already existed (replay)
&lt;/span&gt;&lt;span class="n"&gt;is_first_use&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;altcha:used:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;solution_hash&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
    &lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# 10 minutes, safely longer than the challenge TTL
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;is_first_use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;captcha already used&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An in-memory dict won't survive a deploy or scale across worker processes. Redis works. Postgres works. Pick something durable, pick a TTL longer than your challenge expiry, and you're done.&lt;/p&gt;

&lt;p&gt;We use a separate HMAC key per environment, so a leak in dev can't be used to forge prod challenges. Same reasoning as keeping your test Stripe keys separate from your live keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Altcha is the wrong call
&lt;/h2&gt;

&lt;p&gt;We want to be honest about the limits, because they're real.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You're being actively attacked by a sophisticated bot operator.&lt;/strong&gt; A real bot farm with cloud capacity can absorb proof-of-work costs at the bot's marginal cost of CPU. PoW raises the floor; it doesn't put a ceiling on a determined attacker. If you've ever had to pull pricing data off your site after a competitor scrape, you want behavioral ML, not a math puzzle. Altcha also supports Argon2id and Scrypt as memory-hard alternatives to PBKDF2, which resist GPU and ASIC acceleration and narrow the gap between consumer devices and bot farms; not a silver bullet, but worth knowing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your users are on very low-end devices.&lt;/strong&gt; A one-second solve on a modern laptop can become a four-second solve on a five-year-old budget phone. If your audience is consumer mobile in developing markets, benchmark before shipping.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You can't run a Redis (or equivalent) for replay protection.&lt;/strong&gt; A captcha without single-use enforcement is theater. If your hosting tier doesn't let you add a key-value store, fix that first, then come back.&lt;/p&gt;

&lt;p&gt;For everything else (the long tail of SaaS signup forms, internal admin tools, contact forms, comment sections, account recovery flows), Altcha is the better default than reCAPTCHA in 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is Altcha actually free?&lt;/strong&gt;&lt;br&gt;
Yes. It's MIT-licensed open source. The library is at &lt;a href="https://github.com/altcha-org/altcha" rel="noopener noreferrer"&gt;github.com/altcha-org/altcha&lt;/a&gt;. The cost is the CPU spent on the verifying server (negligible) and the client-side compute (about a second of one core, once per submission).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need to run a separate Altcha server?&lt;/strong&gt;&lt;br&gt;
No. Altcha is a protocol plus a small client library. You add two endpoints to your existing backend: one to mint challenges, one to verify them. There's no Altcha-the-service unless you opt into their managed offering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Altcha work without JavaScript?&lt;/strong&gt;&lt;br&gt;
The default flow needs JavaScript (the Web Worker is where the proof-of-work runs). Altcha also supports a server-side mode where a pre-computed challenge can be verified at form-submit time, useful for accessibility tooling. Most setups won't need it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will switching from reCAPTCHA to Altcha break my form analytics?&lt;/strong&gt;&lt;br&gt;
Only if you were using reCAPTCHA's "human score" as a continuous signal in your analytics. If it was a binary pass/fail gating form submission, the switch is transparent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use Altcha on a static site?&lt;/strong&gt;&lt;br&gt;
You need somewhere to mint and verify challenges. A serverless function counts. A small serverless function handles this in about 30 lines of Python.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Captcha is one of those problems that looks solved until you read the privacy policy. Most teams pick reCAPTCHA because it's the default, not because they evaluated it. If you're an EU business, you almost certainly didn't evaluate it. Your DPO will, eventually, and the conversation is going to be uncomfortable.&lt;/p&gt;

&lt;p&gt;We chose Altcha because it was the only option that kept the entire flow on our origin. You can run the same thing on whatever backend you already have. It's a couple of hundred lines of code and one Redis key prefix.&lt;/p&gt;

&lt;p&gt;If you also care about which other US services your stack quietly depends on, the &lt;a href="https://blog.orkestr.eu/vercel-april-2026-breach-checklist/" rel="noopener noreferrer"&gt;Vercel April 2026 breach checklist&lt;/a&gt; is a useful companion read.&lt;/p&gt;

</description>
      <category>development</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>javascript</category>
    </item>
    <item>
      <title>What are managed sandboxes for AI agents?</title>
      <dc:creator>Stefan Iancu</dc:creator>
      <pubDate>Tue, 26 May 2026 06:00:00 +0000</pubDate>
      <link>https://dev.to/ianqqu/what-are-managed-sandboxes-for-ai-agents-45ad</link>
      <guid>https://dev.to/ianqqu/what-are-managed-sandboxes-for-ai-agents-45ad</guid>
      <description>&lt;p&gt;Ask an AI agent to "figure out why this script is slow" and it will. It writes code, installs three packages you've never heard of, runs them, maybe shells out to &lt;code&gt;git&lt;/code&gt; to check something. The question is &lt;em&gt;where&lt;/em&gt; all that runs. If the answer is "your laptop," you've handed a language model write access to your filesystem.&lt;/p&gt;

&lt;p&gt;A managed sandbox for AI agents is the fix. It's a throwaway computer your agent gets, does whatever it wants inside, and hands back. "Managed" means you don't run it - one API call and it exists, one call and it's gone. That's the whole idea. The rest of this post is what that actually means, with real code.&lt;/p&gt;

&lt;h2&gt;
  
  
  A sandbox is just a throwaway computer
&lt;/h2&gt;

&lt;p&gt;Strip away the jargon and a sandbox is a small, isolated computer that exists for a few seconds or a few minutes and then disappears. Your agent gets to be reckless inside it. It can &lt;code&gt;rm -rf&lt;/code&gt; the wrong directory, install a broken package, run an infinite loop - and the worst case is "that sandbox crashed," not "my git history is gone."&lt;/p&gt;

&lt;p&gt;Here's the entire lifecycle with the orkestr Python SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;orkestr&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Sandbox&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;Sandbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python-3.12&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;sbx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;sbx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/workspace/main.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;print(sum(range(1_000_000)))&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sbx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python /workspace/main.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;# 499999500000
&lt;/span&gt;    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;duration_ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# ~120
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create it, write a file, run a command, read the output. The &lt;code&gt;with&lt;/code&gt; block terminates the sandbox when you leave it - even if your code throws halfway through. No leftover process, no lingering bill.&lt;/p&gt;

&lt;p&gt;That's it. If your agent can call a function, it can use a sandbox.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "managed" part means you run nothing
&lt;/h2&gt;

&lt;p&gt;Plenty of teams build their own version of this. They spin up a VM, install Docker, write a queue, handle cleanup, patch the host, watch it 24/7. That's a real project, and it's not the project you actually wanted to ship.&lt;/p&gt;

&lt;p&gt;"Managed" means that whole layer is gone. You call &lt;code&gt;POST /v1/sandboxes&lt;/code&gt;, you get back a sandbox ID, you use it. No host to patch, no capacity to plan, no cleanup cron. The sandbox boots from a &lt;a href="https://dev.to/eu-sandbox-for-ai-agents/"&gt;warm pool&lt;/a&gt; in under 30ms, and a cold boot is around 150ms - fast enough that your agent doesn't wait on it.&lt;/p&gt;

&lt;p&gt;There are four templates to start from:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Template&lt;/th&gt;
&lt;th&gt;What you get&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;python-3.12&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CPython 3.12 with pip and common libs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;python-3.12-bare&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CPython 3.12 only, faster start&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;node-22&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Node 22 with npm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ubuntu-24.04&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A minimal Ubuntu shell&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Pick one at create time. The sandbox comes up with that runtime already installed.&lt;/p&gt;

&lt;h2&gt;
  
  
  It can't reach anything it shouldn't
&lt;/h2&gt;

&lt;p&gt;Here's the part that matters once an LLM is generating the commands. A sandbox isn't useful if it can quietly POST your data to an address the model invented. So network access is a setting you choose, not a default you inherit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# no internet at all - the safe default for code you haven't read
&lt;/span&gt;&lt;span class="n"&gt;sbx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Sandbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python-3.12&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;off&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# allowlist - pip and npm work, random domains don't
&lt;/span&gt;&lt;span class="n"&gt;sbx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Sandbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python-3.12&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;restricted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three modes. &lt;code&gt;off&lt;/code&gt; means no egress - nothing leaves the box. &lt;code&gt;restricted&lt;/code&gt; routes traffic through an allowlisting proxy: the sandbox can reach package registries, GitHub, and the major LLM APIs, and that's the list. Your agent can &lt;code&gt;pip install pandas&lt;/code&gt;; it can't connect to a host it made up. &lt;code&gt;open&lt;/code&gt; is full egress, and it's gated behind a verified payment method on purpose.&lt;/p&gt;

&lt;p&gt;Each sandbox is also isolated from every other sandbox. Two agents running on the same physical machine can't see each other, can't reach each other, can't even tell the other one exists. The boundary is the hardware, not a setting the kernel decides to honor.&lt;/p&gt;

&lt;h2&gt;
  
  
  It can pause mid-thought
&lt;/h2&gt;

&lt;p&gt;Agent sessions aren't always a tidy ten seconds. Sometimes an agent does some work, waits on a human to approve a step, then continues an hour later. You don't want to pay for an idle VM during that hour, and you don't want to lose the agent's working state either.&lt;/p&gt;

&lt;p&gt;So a sandbox can be frozen and thawed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;sbx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Sandbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;node-22&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# ... agent does some work, then hits a checkpoint ...
&lt;/span&gt;
&lt;span class="n"&gt;snapshot_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sbx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;   &lt;span class="c1"&gt;# frozen to disk, not billed for compute
&lt;/span&gt;
&lt;span class="c1"&gt;# an hour later, maybe from a completely different process:
&lt;/span&gt;&lt;span class="n"&gt;sbx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Sandbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;snapshot_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;pause()&lt;/code&gt; snapshots the entire machine - memory, filesystem, running processes - and stops the clock. &lt;code&gt;resume()&lt;/code&gt; brings it back exactly where it was. The agent doesn't know any time passed.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Isn't this just a Docker container?"
&lt;/h2&gt;

&lt;p&gt;Fair question, and the honest answer is: no, and the difference is the whole point.&lt;/p&gt;

&lt;p&gt;A container shares the host's kernel. That's fine when you trust the code - it's how most of the internet runs. But the moment the code is something an LLM wrote thirty seconds ago and nobody reviewed, sharing a kernel with the host and with every other tenant becomes an uncomfortable bet.&lt;/p&gt;

&lt;p&gt;Each orkestr sandbox is a real virtual machine. It boots its own kernel, mounts its own filesystem, and runs in its own slice of hardware. A bad command inside the sandbox stays inside the sandbox - there's no shared kernel for it to climb through. You get the disposability of a container with an isolation boundary that holds up when the code is genuinely untrusted.&lt;/p&gt;

&lt;h2&gt;
  
  
  What managed sandboxes for AI agents cost
&lt;/h2&gt;

&lt;p&gt;Pay-as-you-go on active vCPU and RAM, billed per second. No per-invocation tax, no minimum, no monthly seat. A quick one-shot call costs a fraction of a cent, and a paused sandbox stops the compute clock until you resume it.&lt;/p&gt;

&lt;p&gt;How many sandboxes you can run at once depends on your plan - 1 at a time on the free &lt;a href="https://orkestr.eu/pricing" rel="noopener noreferrer"&gt;Starter tier&lt;/a&gt;, 5 on Pro, 15 on Team. Sandboxes are in private beta, so the full per-second rates aren't public yet - they land when the beta opens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handing it to your agent
&lt;/h2&gt;

&lt;p&gt;You don't have to wire any of this up by hand. There's an MCP server - point Claude Code, Cursor, or any MCP client at &lt;code&gt;/api/sandboxes/mcp&lt;/code&gt; and the agent gets tools directly: &lt;code&gt;create_sandbox&lt;/code&gt;, &lt;code&gt;run_shell&lt;/code&gt;, &lt;code&gt;run_code&lt;/code&gt;, &lt;code&gt;write_file&lt;/code&gt;, &lt;code&gt;read_file&lt;/code&gt;, &lt;code&gt;pause_sandbox&lt;/code&gt;, &lt;code&gt;terminate_sandbox&lt;/code&gt;. The agent calls them like any other tool. No glue code.&lt;/p&gt;

&lt;p&gt;The plain REST API also works as a configured sandbox provider for &lt;a href="https://claude.com/blog/claude-managed-agents-updates" rel="noopener noreferrer"&gt;Claude Managed Agents&lt;/a&gt;, so you can keep the orchestration on Anthropic's side and run the actual tool execution on EU hardware.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to actually reach for a sandbox
&lt;/h2&gt;

&lt;p&gt;Sandboxes aren't a replacement for a normal dev environment. They're for a specific shape of problem - mostly "I'm about to run something I don't want on my machine." Six places they earn their keep.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Running model-generated code.&lt;/strong&gt; The obvious one. Your agent writes a script and you want to actually run it to see if it works, not just stare at the diff. A sandbox is what turns "the agent suggested a fix" into "the agent shipped a fix." Worst case is the VM is gone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Touching files you don't trust.&lt;/strong&gt; A user uploads a CSV. A scraper dumps JSON. A teammate shares a notebook from somewhere. You want &lt;code&gt;pandas&lt;/code&gt; on it, but you don't want a Zip-slip surprise on your filesystem. Open it in a sandbox, do the analysis there, return summaries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A real Linux box from macOS or Windows.&lt;/strong&gt; You're on a Mac. The thing you're testing is Linux-only - a Dockerfile, a glibc bug, a systemd unit. One API call gets you a Linux userland with the right architecture and none of the Docker Desktop dance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parallel experiments.&lt;/strong&gt; Try three fixes at once. Benchmark four configs. Build against five Python versions. Agents that fan out across sandboxes don't fight over ports, lockfiles, or &lt;code&gt;PATH&lt;/code&gt;. Each one starts clean.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context-window discipline.&lt;/strong&gt; This one is the subtle agent-design win. If your agent reads a 50,000-row CSV into the conversation, it just burned thousands of tokens on data the model doesn't need to see. If the agent works on it &lt;em&gt;inside&lt;/em&gt; a sandbox and returns &lt;code&gt;df.describe()&lt;/code&gt; or a chart, the model sees the conclusion, not the corpus. The sandbox holds the data; the conversation holds the answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sensitive intermediate state.&lt;/strong&gt; Generating credentials, decrypting a file, processing PII for a one-shot calculation. A terminated sandbox is gone - no swap file, no shell history, no &lt;code&gt;/tmp&lt;/code&gt; for the next process to read.&lt;/p&gt;

&lt;p&gt;The other half of the rule: sandboxes are the wrong tool when the working directory &lt;em&gt;is&lt;/em&gt; the point. Editing your repo, running your own test suite, iterating on a feature branch - that all belongs on your machine. The sandbox is for code whose provenance you're not sure of, or an environment that needs to be disposable. If neither applies, you're paying latency for no reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;A managed sandbox for AI agents is a disposable, isolated computer your agent can break without consequences - and "managed" means you get it from an API instead of building and babysitting the infrastructure yourself. Network access is something you choose. State can be paused and resumed. The isolation boundary is a real VM, not a shared kernel.&lt;/p&gt;

&lt;p&gt;If your agent runs code - and most useful agents do - it should run that code somewhere that isn't your laptop or your production box. That's the whole job a sandbox does.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What's the difference between a sandbox and a container?&lt;/strong&gt;&lt;br&gt;
A container shares the host's kernel; a sandbox VM brings its own. For trusted code that distinction rarely matters. For code an LLM just generated and nobody reviewed, the VM boundary is what keeps a bad command from reaching the host or another tenant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where does my agent's code actually run?&lt;/strong&gt;&lt;br&gt;
On dedicated hardware in the EU - Germany and Finland. Sandbox snapshots, environment variables, and runtime memory stay in the EU. For an EU company sending agent-generated code somewhere, that removes a data-transfer conversation. There's &lt;a href="https://dev.to/eu-sandbox-for-ai-agents/"&gt;more on the EU angle here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens if my agent forgets to clean up?&lt;/strong&gt;&lt;br&gt;
Every sandbox has a &lt;code&gt;timeout_seconds&lt;/code&gt; (default 600) and auto-terminates when it's hit, so a crashed or forgetful caller can't leave a VM running forever. The SDK's &lt;code&gt;with&lt;/code&gt; block also terminates the sandbox the moment you leave it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I run a server inside a sandbox?&lt;/strong&gt;&lt;br&gt;
Not in the current beta - sandboxes are for running commands and code, not for hosting inbound services. If you want a long-lived app with a public URL, that's a regular &lt;a href="https://orkestr.eu" rel="noopener noreferrer"&gt;orkestr deployment&lt;/a&gt;, not a sandbox.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I get access?&lt;/strong&gt;&lt;br&gt;
Managed sandboxes are in private beta. The waitlist is at &lt;a href="https://orkestr.eu/sandboxes" rel="noopener noreferrer"&gt;orkestr.eu/sandboxes&lt;/a&gt; - it asks what you're building so we can triage who gets in each week.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>security</category>
    </item>
    <item>
      <title>EU managed sandboxes for AI agents, in private beta</title>
      <dc:creator>Stefan Iancu</dc:creator>
      <pubDate>Tue, 19 May 2026 17:19:41 +0000</pubDate>
      <link>https://dev.to/ianqqu/eu-managed-sandboxes-for-ai-agents-in-private-beta-3378</link>
      <guid>https://dev.to/ianqqu/eu-managed-sandboxes-for-ai-agents-in-private-beta-3378</guid>
      <description>&lt;p&gt;Your agent suggested &lt;code&gt;rm -rf node_modules &amp;amp;&amp;amp; rm -rf .git&lt;/code&gt; to fix a build error. You laughed, but only because you weren't running it. The thing about agent-generated shell commands is that they look reasonable until they don't, and most of them shouldn't run on your laptop or your production box - they should run inside something disposable, where the worst case is "the sandbox crashes" and not "I'm git-forensicing my own repo on a Friday night."&lt;/p&gt;

&lt;p&gt;So we built managed sandboxes for AI agents. Private beta opening, waitlist live today at &lt;a href="https://orkestr.eu/sandboxes" rel="noopener noreferrer"&gt;orkestr.eu/sandboxes&lt;/a&gt;. EU-hosted.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this is
&lt;/h2&gt;

&lt;p&gt;An HTTP API. Your agent calls &lt;code&gt;POST /v1/sandboxes&lt;/code&gt;, gets back a fresh sandbox ID, then calls &lt;code&gt;/exec&lt;/code&gt; to run shell commands, &lt;code&gt;/files&lt;/code&gt; to read or write files, &lt;code&gt;/pause&lt;/code&gt; and &lt;code&gt;/resume&lt;/code&gt; to snapshot a session and bring it back later. When the agent's done, it calls &lt;code&gt;DELETE&lt;/code&gt; and the whole thing - kernel, filesystem, processes - is gone.&lt;/p&gt;

&lt;p&gt;Each sandbox is a dedicated VM. Not a container. Not a v8 isolate. Each one boots its own kernel, mounts its own rootfs, and lives in its own slice of hardware. Cold start is around 150ms; from a warm pool, under 30ms. When two agents are running in two sandboxes on the same physical host, they cannot reach each other - the boundary is hardware, not a namespace the kernel decides to trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why an EU sandbox, now
&lt;/h2&gt;

&lt;p&gt;Anthropic shipped &lt;a href="https://claude.com/blog/claude-managed-agents-updates" rel="noopener noreferrer"&gt;Managed Agents&lt;/a&gt;, which lets Claude orchestrate an agent loop while tool execution happens in a sandbox provider you configure. The launch listed four supported providers: Cloudflare, Daytona, Modal, Vercel. All four are headquartered in the US.&lt;/p&gt;

&lt;p&gt;That's a problem if you're an EU company sending agent-generated code somewhere. Standard Contractual Clauses cover transfers in principle, but if you're a Berlin fintech or a Paris insurance startup or a German healthcare company explaining to a procurement team why your agent's working data is processed by a US entity, you have a real conversation ahead of you. The EU sandbox alternative had to exist; we built ours.&lt;/p&gt;

&lt;p&gt;So we did. The orkestr legal entity is in the EU. The hardware is in the EU. Your sandbox snapshots, environment variables, and runtime memory never leave the EU. GDPR DPA on request, signed by the same company that runs the runtime - not a US parent's subsidiary three layers down.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it fits next to what's out there
&lt;/h2&gt;

&lt;p&gt;If you've used &lt;a href="https://e2b.dev" rel="noopener noreferrer"&gt;E2B&lt;/a&gt;, &lt;a href="https://daytona.io" rel="noopener noreferrer"&gt;Daytona&lt;/a&gt;, &lt;a href="https://modal.com" rel="noopener noreferrer"&gt;Modal&lt;/a&gt; sandboxes, or &lt;a href="https://developers.cloudflare.com/sandbox/" rel="noopener noreferrer"&gt;Cloudflare Sandboxes&lt;/a&gt;, the shape is familiar: REST API, Python and JS SDKs, exec / files / snapshot primitives. Here's what the Python SDK looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;orkestr&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Sandbox&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;Sandbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python-3.12&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;sbx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;sbx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/workspace/main.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;print(sum(range(1_000_000)))&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sbx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python /workspace/main.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 499999500000
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Long-running agent sessions can pause and resume across requests, even across workers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;sbx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Sandbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;node-22&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;restricted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;snapshot_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sbx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# ...minutes or hours later, from any process:
&lt;/span&gt;&lt;span class="n"&gt;sbx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Sandbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;snapshot_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's also an MCP server you can drop into Claude Code or Cursor, and the REST API works fine as a configured sandbox provider for Claude Managed Agents. If your agent can call a tool, it can call this.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in the beta
&lt;/h2&gt;

&lt;p&gt;Four templates at launch: &lt;code&gt;python-3.12&lt;/code&gt;, &lt;code&gt;python-3.12-bare&lt;/code&gt;, &lt;code&gt;node-22&lt;/code&gt;, &lt;code&gt;ubuntu-24.04&lt;/code&gt;. Each sandbox sized at 1 vCPU and 1 GB of RAM by default; quick one-shot calls cost fractions of a cent. Three network modes: &lt;code&gt;off&lt;/code&gt; (no egress, the safe default for LLM-generated code), &lt;code&gt;restricted&lt;/code&gt; (allowlist for package registries and common APIs), and &lt;code&gt;open&lt;/code&gt; (full egress, gated behind verified payment).&lt;/p&gt;

&lt;p&gt;Snapshots are native. Resuming on the same host is under half a second; cross-host is a few seconds because the memory file has to fly between data centres.&lt;/p&gt;

&lt;p&gt;The compute runs on dedicated bare-metal hardware in Germany and Finland. Hardware virtualisation on every sandbox, no exceptions. Containers are fine for trusted workloads, but the moment an LLM is generating shell commands you haven't seen yet, sharing the host kernel becomes an awkward conversation. We didn't want to have it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's missing in private beta
&lt;/h2&gt;

&lt;p&gt;The honest list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GPU sandboxes. Not in v1. The CPU product has to work first.&lt;/li&gt;
&lt;li&gt;Persistent volumes across sandbox lifetimes. Use pause/resume for now.&lt;/li&gt;
&lt;li&gt;Custom Docker images as templates. Coming.&lt;/li&gt;
&lt;li&gt;Multi-region routing. Two regions are live; we do not auto-route yet.&lt;/li&gt;
&lt;li&gt;Detailed per-sandbox observability for end users. Building.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you hit something not on this list that feels broken, it's probably a bug, so please tell us.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing, briefly
&lt;/h2&gt;

&lt;p&gt;Per-second CPU and RAM, no minimums, no per-invocation tax. We're keeping the full price list off the page until the first ten design partners have run real workloads against it. The numbers will move once we see what real usage looks like, and we'd rather quote the final price than walk one back.&lt;/p&gt;

&lt;h2&gt;
  
  
  The catch (because there's always one)
&lt;/h2&gt;

&lt;p&gt;We're letting a small group in each week. The waitlist asks what you're building and what volume you're expecting, which is how we're triaging. Design partners get a discount on the first three months once paid usage opens, and a direct line to us for SDK feedback.&lt;/p&gt;

&lt;p&gt;If you've been waiting for an EU sandbox for whatever agent you're building, &lt;a href="https://orkestr.eu/sandboxes" rel="noopener noreferrer"&gt;the waitlist is here&lt;/a&gt;. If you have questions before signing up, email me (stefan at orkestr dot eu). I read everything.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Team workspaces on orkestr, and the ownership-transfer problem</title>
      <dc:creator>Stefan Iancu</dc:creator>
      <pubDate>Mon, 18 May 2026 13:10:00 +0000</pubDate>
      <link>https://dev.to/ianqqu/team-workspaces-on-orkestr-and-the-ownership-transfer-problem-48ki</link>
      <guid>https://dev.to/ianqqu/team-workspaces-on-orkestr-and-the-ownership-transfer-problem-48ki</guid>
      <description>&lt;h2&gt;
  
  
  Introducing the Team Plan on orkestr
&lt;/h2&gt;

&lt;p&gt;Imagine this scenario: You signed up for a Team plan a year ago. Your card is on file. You hired two engineers, invited them, and they've shipped most of the production work since. Now, you're leaving the company.&lt;/p&gt;

&lt;p&gt;On most SaaS platforms, this is the moment you have to open a support ticket: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Hi, can the new tech lead take over the subscription? Also all 12 projects. Also the domains. Also the audit log."&lt;/em&gt; Three business days later, you get an email asking for "verification" of something.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Team plan launched on &lt;a href="https://orkestr.eu" rel="noopener noreferrer"&gt;orkestr&lt;/a&gt; today. The entire flow is built right into the dashboard, and an ownership transfer takes about 90 seconds end-to-end. &lt;/p&gt;

&lt;p&gt;Here is what is shipping today, and why building it was harder than it looks.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Ships Today
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Explicit Team Workspaces:&lt;/strong&gt; Create a team, invite members by email, and switch workspaces from the top bar. Personal projects stay personal; team projects are visible to the team.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared Plan Budgets:&lt;/strong&gt; Your Team plan's 15-project allowance is shared across your personal workspace and the team. The team owner's plan applies to team projects automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Role-Aware Features:&lt;/strong&gt; Members can see who created each project. To protect infrastructure, members cannot delete team projects—only the owner can. Additionally, the activity feed shows team-tagged actions to all members.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seamless Ownership Transfer:&lt;/strong&gt; If the owner wants out, they can hand the team (and every project and function inside it) to another member atomically. This requires a quick 1 EUR card verification from the new owner. The old owner's subscription gets canceled, and the new owner's billing cycle seamlessly picks up on the original renewal date.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Team is available for &lt;strong&gt;29.99 EUR/month or 299 EUR/year&lt;/strong&gt; via the &lt;a href="https://orkestr.eu/pricing" rel="noopener noreferrer"&gt;Pricing Page&lt;/a&gt;. It features the same EU-hosted infrastructure as every other plan, with no US data transfers.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Workspace Model
&lt;/h2&gt;

&lt;p&gt;The word "workspace" is heavily overloaded in SaaS, so it's worth specifying how it works here.&lt;/p&gt;

&lt;p&gt;orkestr features exactly two workspace contexts: &lt;strong&gt;Personal&lt;/strong&gt; and &lt;strong&gt;Team&lt;/strong&gt;. An early draft included a third context called "All Workspaces"—a unified dashboard showing everything across every team you are in. After testing it on staging for a week, it was completely ripped out. It made security scoping invisible, and data scoping is a detail that should be explicit, not implicit.&lt;/p&gt;

&lt;p&gt;The active workspace is now part of every backend request. If you are a member of a team and you open the projects page, you will not see the owner's untagged personal projects. This isn't hidden by the UI; the backend simply never returns them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Expanding Scope for Plan Limits
&lt;/h3&gt;

&lt;p&gt;The one place where scope expands automatically is plan limits. A project's plan is tied to whoever owns it. If you are on the free tier but working on a project within a paid Team workspace, you get full Team-plan limits on that project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rollbacks&lt;/li&gt;
&lt;li&gt;Custom domains&lt;/li&gt;
&lt;li&gt;Expanded CPU and memory budgets&lt;/li&gt;
&lt;li&gt;IP allowlists&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You won't hit any "upgrade to Pro" walls inside a team that is already being paid for.&lt;/p&gt;




&lt;h2&gt;
  
  
  Shared Budgets and Role-Aware Counts
&lt;/h2&gt;

&lt;p&gt;The 15-project limit on the Team plan is a single budget split across both personal and team contexts. &lt;/p&gt;

&lt;p&gt;If you (the owner) have 5 personal projects and your team has 8 team projects, you have used 13 of your 15 allowed projects. However, your team members will only see "8 of 15" on their dashboard. Their personal projects do not count against the team's budget, and they shouldn't see how many personal projects you hold. Even leaking the raw count feels like a privacy violation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Total Team Budget: 15 Projects
├── Owner's View:   5 Personal + 8 Team = 13/15 Used
└── Member's View:  8 Team Projects     =  8/15 Used

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the kind of detail that nobody notices when it's right, and everyone notices when it's wrong. The same logic applies to the activity feed: members see team-tagged actions from everyone plus their own personal actions, but never the owner's personal actions. The same goes for domains, add-ons, and deployments.&lt;/p&gt;




&lt;h2&gt;
  
  
  Solving the Invite Flow &amp;amp; Email Mismatch
&lt;/h2&gt;

&lt;p&gt;Invites are entirely email-based. The owner enters an email, the system creates a &lt;code&gt;TeamInvite&lt;/code&gt; row, and the invitee gets a link. Standard practice.&lt;/p&gt;

&lt;p&gt;However, a common, annoying edge case occurs when an invitee already has an orkestr account, but under a different email. For example, they signed up with &lt;code&gt;firstname@gmail.com&lt;/code&gt; for personal use, but you invited their work email: &lt;code&gt;firstname@company.com&lt;/code&gt;. Most platforms either silently match the accounts (which is dangerous, as emails are not identity) or hard-fail and tell the owner to try again.&lt;/p&gt;

&lt;p&gt;orkestr handles this gracefully. The acceptance page displays: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This invite was sent to &lt;code&gt;firstname@company.com&lt;/code&gt;. Your account email is &lt;code&gt;firstname@gmail.com&lt;/code&gt;. Are these the same person?" &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the invitee confirms, the system logs both emails on the membership row and grants access. If they say no, the invite stays open for whoever actually controls that email address. This tiny UX detail eliminated roughly half of the anticipated support tickets.&lt;/p&gt;




&lt;h2&gt;
  
  
  Re-engineering Ownership Transfer
&lt;/h2&gt;

&lt;p&gt;Until recently, if a Team-plan owner wanted to leave a team, they had one option: remove every member, delete every team project, cancel their subscription, and let the data evaporate. That isn’t an ownership transfer; that's burning down the office and leaving.&lt;/p&gt;

&lt;p&gt;Here is how the new, 90-second automated flow works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Owner Requests Transfer:&lt;/strong&gt; The current owner selects a team member and checks a consent box acknowledging they will lose access to all team projects and functions.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Target Notified:&lt;/strong&gt; If the target teammate isn't on a Team plan yet, they are prompted to subscribe. A team cannot be handed over to an account whose plan limits cannot support the existing resources.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Target Pays 1 EUR:&lt;/strong&gt; This is a Mollie card verification, not a real charge. It serves two purposes: it proves the new card is valid before handing over a subscription, and it satisfies EU consumer protection rules requiring an explicit, attributed action before initiating a recurring charge.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Atomic Swap:&lt;/strong&gt; Once Mollie confirms the verification, a single database transaction reassigns the team, every project, and every function in one go. Either every write succeeds, or the entire transaction rolls back.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Subscription Handover:&lt;/strong&gt; The new owner's billing cycle starts exactly on the day the old owner's subscription would have renewed. The old owner's subscription is canceled, and the 1 EUR verification fee is refunded. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Aligning the start date to the original renewal date prevents both parties from double-paying or losing paid days, completely eliminating a common customer service trap.&lt;/p&gt;




&lt;h2&gt;
  
  
  What’s Not in This Release
&lt;/h2&gt;

&lt;p&gt;To be completely transparent, a few features didn't make the cut for today's launch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No Automatic Expiry on Pending Transfers:&lt;/strong&gt; Right now, transfer requests sit open indefinitely until someone explicitly cancels or accepts them. A 7-day default expiration will be added soon.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Multi-Owner Teams:&lt;/strong&gt; There is a strict limit of one owner per team. Co-ownership introduces entirely different database invariants and security complexities, and we didn't want to ship a half-baked version of it.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you’ve been waiting for team collaboration features, you can get started right now at &lt;a href="https://orkestr.eu/pricing" rel="noopener noreferrer"&gt;orkestr.eu/pricing&lt;/a&gt;. If you’re already on a Pro plan and want to bring your engineers onboard, you can find the upgrade toggle directly in your workspace settings.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
    <item>
      <title>About Vercel's April 2026 breach: your checklist</title>
      <dc:creator>Stefan Iancu</dc:creator>
      <pubDate>Tue, 21 Apr 2026 04:00:00 +0000</pubDate>
      <link>https://dev.to/ianqqu/about-vercels-april-2026-breach-your-checklist-4cji</link>
      <guid>https://dev.to/ianqqu/about-vercels-april-2026-breach-your-checklist-4cji</guid>
      <description>&lt;p&gt;Vercel published a &lt;a href="https://vercel.com/kb/bulletin/vercel-april-2026-security-incident" rel="noopener noreferrer"&gt;security bulletin&lt;/a&gt; on April 19 confirming a breach of its internal systems. On April 20, CEO Guillermo Rauch followed up with the root cause: a Vercel employee used a third-party AI tool called Context.ai. Well, this tool got breached. The attacker pivoted from there into the employee's Vercel Google Workspace account, and from there into Vercel environments and environment variables that weren't marked as "sensitive." Is this the customer fault? (jk)&lt;/p&gt;

&lt;p&gt;Vercel says a "limited subset" of customers were directly impacted and have been contacted. They also say Next.js, Turbopack, and their other open-source projects have been analyzed and are believed safe.&lt;/p&gt;

&lt;p&gt;That's the facts. Now the honest part: if you deploy on Vercel, don't wait for an email. Rotate first, ask questions later.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Vercel breach checklist&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Rotate every environment variable that wasn't marked as "sensitive" in Vercel.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Do this first if you do nothing else. Vercel's &lt;a href="https://vercel.com/docs/environment-variables/sensitive-environment-variables" rel="noopener noreferrer"&gt;sensitive environment variable feature&lt;/a&gt; encrypts values at rest and hides them from the dashboard after creation. Everything else - the default - was readable by anyone with access to that employee's Google Workspace session. Assume those values leaked. Payment processor keys, database URLs, auth secrets, cloud provider keys, admin API tokens to third-party SaaS. Rotate all of it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Review your GitHub organization audit log.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If your Vercel account is connected to GitHub (it almost certainly is), pull the audit log for April 1 through today. Look for unexpected OAuth grants, new deploy keys, workflow edits, branch protection changes, or pushes from unusual IPs. The community-maintained IR playbook has a solid filter list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. If you use the Vercel ↔ Linear integration, check Linear's audit log too.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Linear issues are where people paste stack traces, database URLs, and occasionally plaintext secrets while debugging. The integration has read access to issues. Workspace settings → Security → Audit log.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Audit any npm package you publish from a Vercel-connected workflow.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Vercel says their own packages are clean. That's their release path, not yours. If you use Vercel's build environment to run npm publish or push to a registry, treat any package published in the exposure window as suspect until you've diffed it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Don't skip the boring step: inventory your exposure.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For every Vercel team you control, list the projects, the connected Git orgs, the integrations, and the humans with access. Half the pain of incident response is discovering on day three that there was a second team nobody remembered.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The part nobody wants to talk about&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The attack didn't start at Vercel. It started at &lt;a href="https://context.ai/" rel="noopener noreferrer"&gt;Context.ai&lt;/a&gt;, an AI tool a single employee had connected to their work account. They also &lt;a href="https://context.ai/security-update" rel="noopener noreferrer"&gt;posted&lt;/a&gt; about it. This is how modern breaches happen. You don't get popped through your WAF. You get popped because someone on your team authorized an OAuth grant to a YC-stage AI startup that had one security engineer and a Notion doc for an incident response plan.&lt;/p&gt;

&lt;p&gt;There's no EU-vs-US angle here. European PaaS vendors have employees too. Employees use AI tools. The same thing could happen to Scalingo, Clever Cloud, or us at orkestr. &lt;/p&gt;

&lt;p&gt;What matters is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How much blast radius does one compromised employee account actually have?&lt;/li&gt;
&lt;li&gt;Are production secrets readable from a dashboard, or are they sealed?&lt;/li&gt;
&lt;li&gt;How fast does the vendor tell you, and how specifically?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vercel's bulletin on April 19 was terse to the point of unhelpful. The detail came from press reporting and a follow-up from the CEO a day later. "We recommend reviewing environment variables" isn't incident communication. It's a legal posture. You should expect better from anyone you hand your deploy keys to — us included.&lt;/p&gt;

&lt;p&gt;Whatever platform you're on: rotate your secrets today. Mark them sensitive. Cut the SaaS integrations you're not using. And write down who on your team has production access, because there will be a next one.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>security</category>
      <category>news</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
