Last post I made a claim: that Veya is a fabric you can trust to run intelligence and applications without handing over your machine. This is the post where I try to prove a piece of that — by attacking it myself.
The feature is an app host. The idea is simple to say and hard to do safely: let Veya run other people's apps — the growing universe of MCP apps, the dashboards and forms and little tools that vendors are starting to ship — right inside your workspace, composing over your local data. The whole pitch of a sovereign fabric falls apart if "run someone else's app" also means "give someone else's app the run of your laptop." So the interesting engineering isn't the running. It's the containment.
One thing to say up front, because the rest of this post is a story about finding security holes: none of this has shipped. The app host is behind a feature flag, in no build you can download today. That's deliberate — the time to try to break a thing is before anyone depends on it. Nothing below describes a hole in a Veya you can install.
The shape of the thing
A few decisions set the boundary before a line of attack code existed:
- Native-first. The default way an app renders is declarative: it sends a description of what it wants drawn, and Veya draws it with its own trusted components. No third-party code runs at all — there is nothing to escape. The strongest containment is the one where the dangerous thing never executes.
- HTML apps get a separate window, not an iframe. For apps that ship real HTML, we run them in a separate OS-level webview window — never an in-page iframe. This isn't a preference; it's the platform's own guidance. Tauri's security advisory is explicit that embedded iframes can reach the app's privileged bridge, and that the right answer for untrusted content is a dedicated window. We took it literally.
- One-directional. The host drives the app. The app does not drive the host. There is no callback channel from an app back into Veya — no remote procedure calls inbound, no ambient capability. If the host wants something from the app, the host asks. The reverse is simply not wired.
- No bundled browser. The window is the operating system's own webview — WKWebView on macOS — not a shipped copy of Chromium. Smaller, and one less engine to keep patched.
- Trust pinned to content, not names. An app is approved against a hash of what it actually is, so a later swap re-prompts instead of inheriting yesterday's permission.
On paper, airtight. On paper is not where software runs.
So we attacked it
The rule I keep coming back to: a green test suite tells you the code does what you wrote, not that the system does what you meant. The only way to know whether the boundary holds is to compile the real binary, open a real window with real hostile content inside it, and watch what happens.
One caveat that matters for everything that follows: this all happened on macOS, against its system webview. Windows and Linux ship different engines, and a boundary that holds on one doesn't automatically hold on the others — I come back to that at the end.
So that's what we did. We wrote apps whose only purpose was to misbehave — reach for the filesystem, phone home, climb out of the window — loaded them through the exact production path a real app would take, and watched. Two of them got through.
Hole one: the app could read a secret off disk. The "locked-down" window could still reach Tauri's internal bridge and call a privileged backend command. We had a hostile app call the one that reads a stored secrets file. It came back. Untrusted web content, reading local secrets — the exact thing the separate window was supposed to make impossible.
Hole two: the app could phone home. There was no content-security policy on the window, so the app could fetch() any domain on the internet it liked. Combine the two and you have the full kill chain: read something sensitive, ship it out. Wide open.
Both findings came from running the app, not from reading it. The unit tests were green the whole time.
The fix: three layers, on purpose
We didn't patch the two holes and call it done. We assumed we'd be wrong again somewhere, and built three independent layers so that being wrong about one wouldn't open the door:
- Strip the bridge. An init script that removes Tauri's globals from the window before the page's own code runs.
- A backstop in the backend. Every privileged call is tagged with the window it came from. Anything originating in an app window is denied in Rust, before it reaches a single command. This is the load-bearing layer — it holds even if the strip above is defeated.
- A default-deny CSP. A strict content-security policy, enforced both as a header on the custom protocol that serves the app and as a meta tag in the document. Network is locked to the app's declared domains — declare nothing, reach nothing. No inline script.
Then we ran the attack again. Same hostile apps, same production path.
Sealed — and the part I didn't expect
The second run came back clean. The call to read the secret was denied; the file was never touched. The phone-home fetch to an undeclared domain was blocked — and, as a control, a fetch to a domain the app had declared went through, which is how we know it's a real allow-list and not a blanket network kill. The attempt to message the host went nowhere; the attempt to read the host's screen found only its own empty document. Every door we'd pried open on macOS was shut.
But the second attack told us something the first one couldn't, and it's the reason I'm writing this down.
One of our three layers wasn't doing what we thought it was. The init script that "strips the bridge" — layer one — didn't actually remove the internal handle on this kind of window. Tauri re-injects its own IPC bridge into the window after our script runs, so the global we thought we'd deleted was sitting right there, callable, the whole time. If that strip had been our only defense, we'd still be wide open.
What sealed the window wasn't the layer we were most confident in. It was the backstop we'd added precisely because we didn't trust ourselves to get the first layer right.
That is the entire argument for defense in depth, demonstrated against our own code. It isn't belt-and-suspenders caution. It's insurance against your own wrong assumptions — and you don't find out which assumption was wrong until something tries to break in. The Rust backstop turned a surviving exploit primitive into an inert one. The CSP caught the exfil regardless. The layer we'd have bet on did nothing, and it didn't matter, because it wasn't carrying the load alone.
What this is and isn't
Two honest caveats, because the failure mode of a post like this is to oversell the win:
- This was tested on macOS, against its webview engine. Windows and Linux ship different engines that may re-inject globals and enforce policy differently; each needs its own attack run before I'd make the same claim there. One platform proven is not three.
- This is unreleased work — gated behind a flag, in no one's hands. Which is exactly when you want to be finding holes like these: while the only person they can hurt is you.
The reason any of this matters is the thing the fabric is for. Running other people's apps over your own private data is the whole point — it's where a workspace stops being a chatbox and starts being a place where work composes. But that promise is only worth making if the apps can't reach past their box to the data sitting next to them — and you don't get to just assert that. You have to try to break out, fail, and keep the receipts. On macOS, on this unreleased build, we tried and failed. That's a start, not a finish.
These are the receipts.