Part of our security series — this one follows We Got Breached and 174 Attempts, One Breach, and you can attack the live sandbox at the Natural Language API.
Over the past weeks Magic has been attacked by more people, with more tooling, than in the previous three years combined. Several penetration tests, mine included. The free ones that came out of my guest piece in the Norwegian outlet kode24. And three frontier models, Kimi-K3.1-Pro, GLM-5.3 and DeepSeek-V4-Pro, each pointed at the entire project and told to find what is wrong with it.
None of them found a serious security issue.
I am not going to tell you that means Magic is unbreakable. In September I wrote a whole article about the time somebody did break it. What I can tell you is why the results look the way they do, because it is not luck. It is a set of changes, all of them in the repository, and most of them are on by default. You do not have to configure anything to get them.
The change that matters most: one road to dispatch
The September breach was a logic error in the whitelist. The check lived in [eval], which sees the statements you write, while a C# slot could compose a slot name from an argument and dispatch it behind the check's back.
The fix moved the check into Signaler.GetService, the single function every slot invocation in the runtime passes through. A name that came from a statement, or from a string a slot built a microsecond earlier, is verified the same way. Internal framework calls opt out explicitly with skipWhitelist, and the rule for who qualifies is provenance: a name built from configuration is Magic talking to itself, a name the caller can steer never is.
Since then the same chokepoint has been tightened further:
- Wildcard pins match segment by segment.
/etc/*.pdfgrants one extension in one folder, not the subtree. - Recursive whitelist invocations are blocked, and a nested
[whitelist]cannot widen the vocabulary in scope. [html2pdf]routes every image, stylesheet and font through the whitelisted slots instead of letting the PDF library read files and make HTTP requests on its own.[http.*]supports URL pins, matched with the same segment-wise logic and with no user-info allowed in a pin, so a sandbox can be granted one API instead of the internet.
SQL and files: reject, do not escape
Two of the oldest attack classes got hard guards at the layer where they matter.
SQL. In the read builder, aggregate expressions are matched against a whitelist of function names (count, sum, avg, min, max and friends) with the column constrained to identifier characters. Table and join aliases must be plain identifiers, and [direction] accepts exactly asc or desc. Anything else throws. There is a 281-line test class that attacks the builder directly.
Paths. The resolver that turns a path into a file location now rejects any .. segment outright, before the containment check. The reason is in the commit message: /etc/../config/appsettings.json stayed inside the dynamic files folder after resolution, so it passed every guard that only checked the folder a path started with. Nothing in the repository needs a parent segment, so none is allowed. That one was reported and fixed by an outside contributor, with a test against a running build.
Authentication: assume the attacker controls everything
- Login throttling.
execution.throttleis a new family of slots built on .NET's partitioned rate limiter. The authenticate endpoint, and every other unauthenticated auth endpoint, spends from the same per-IP and global budgets, so hammering one endpoint costs you the others. - OIDC.
auth.token.verifynow refuses to run without a caller-supplied list of trusted issuers, and never fetches discovery metadata from a token's own issuer, since the issuer of an unverified token is by definition attacker controlled. - Password reset. The link in the reset email is built from the
Originheader only if that origin is listed inmagic:frontend:urls. Otherwise it falls back to the backend's own URL. Before, anyone could have aimed that email's session token at a domain of their choosing. - CORS. Any origin may still call the API with a bearer token, but cookies are only vouched for towards configured or same-site origins.
- Sessions. The chat
active-sessionendpoint is scoped to its owner, closing an IDOR where a guessed session id returned someone else's transcript.
Generated endpoints inherit the guardrails
The crudifier's generated search endpoint now gets the same [auth], [log] and [captcha] as the other generated verbs, and a PUT whose primary key is also declared as a column is rejected. A reCAPTCHA-protected chat type now fails closed when no server keys are configured, instead of quietly skipping the check.
Less surface, fewer things to attack
Some of the most effective security work is deletion. The skip-auth mechanism is gone, along with the obsolete Twilio webhooks and the public speak and transcribe endpoints. A recursive expression now throws a normal Hyperlambda exception instead of overflowing the stack. Dozens of package versions were bumped along the way.
Why the audits came back clean
Penetration testers and language models read the same thing: a codebase, and a running server. What they found is a runtime where capability is granted by name, where dispatch has one chokepoint, where the dangerous string-to-SQL and string-to-path conversions reject instead of sanitise, and where the unauthenticated endpoints are throttled before they do any work.
I should be clear about what that does and does not show. A clean result from several auditors is evidence, not proof. The serious findings in this period came from people who attacked the live endpoint for a bounty, and from contributors reading the code, and every one of them is in the articles linked above or in the changes listed here. After those landed, the rest of the testing stopped finding anything serious. That is what I mean when I say Magic is more secure by default now, and it is the only claim I am making.
The honest edges
- Root bypasses everything. Unchanged and deliberate. Give your agents their own user.
- Until you set it up, the default login is
root/root. Magic treats a JWT secret shorter than 50 characters as an unconfigured instance. Set it up before you expose it. - Pins are opt-in per grant. A sandbox only protects you as far as the vocabulary you wrote. An unpinned
http.getis still real reach into your network. - Your own application logic is still yours. Unauthenticated endpoints, evaluated user input and string-built SQL in your own Hyperlambda work exactly as they do anywhere.
- The layers beneath are not mine. .NET, SQLite, the operating system.
- The security policy is thin.
SECURITY.mdis currently one line with my email address. Report anything to thomas@ainiro.io.
Try it
Magic is MIT licensed at github.com/polterguy/magic, and every commit referenced above is public. The live sandbox is still running, still granting nothing but reads on a sample database, and the $100 still stands.
Frequently asked questions
Is Magic Cloud secure by default?
Substantially more than it was. The whitelist check now sits in the single function every slot invocation passes through, SQL and path inputs are rejected rather than escaped, unauthenticated auth endpoints are rate limited, OIDC tokens are only verified against caller-supplied trusted issuers, and CORS only allows cookies for trusted origins. None of those require configuration. Root still bypasses the sandbox, and your own endpoints still need authentication.
Has Magic been penetration tested?
Yes, by several people including the author, plus free tests that followed a guest article in the Norwegian outlet kode24. Three AI models, Kimi-K3.1-Pro, GLM-5.3 and DeepSeek-V4-Pro, also analysed the whole project. None reported a serious issue. Earlier findings from bounty hunters and contributors were fixed and documented.
Does this mean Magic cannot be hacked?
No. A clean audit is evidence, not proof, and the September sandbox breach showed that a logic error can survive for a long time. The claim is that the known classes are closed and the remaining risk sits mostly in application logic and in how you configure roles.
How do I report a vulnerability?
Email thomas@ainiro.io. The live Natural Language API sandbox also carries a standing $100 bounty.
Related reading
- We Got Breached: A Whitelisted data.create Minted a Root JWT
- 174 Attempts, One Breach: What Happened After We Published Our Own Vulnerability
- The Only Sandbox Your AI Agent Cannot Break Out Of
- PHP2Shell and the Case for a Smaller Attack Surface
- Why Secure AI Code Execution Requires Runtime Whitelisting, Not Prompt Filtering
- Break My AI Sandbox and Make $100