-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256
On Sun, May 31, 2026 at 02:56:37AM -0700, Alex Schose wrote:
> Hi all,
Hi,
> I've built a FastMCP server that exposes a tag-scoped Qubes Admin API
> to AI agents via dom0-mediated wrappers — the qrexec policy + the
> qmcp.* RPC scripts are the load-bearing pieces. Wanted to put the
> design questions in front of the developer community before claiming
> the design is idiom-correct.
Interesting idea, but honestly, with the current state of LLM, I would
be _very_ careful with giving them access to modify system state without
checking what it actually does. Mistakes like deleting important data
due to some hallucination happen quite often...
And this doesn't even tackle the prompt injection risk, which is yet
another story.
You described this risk in your article, and IIUC the idea is to simply
don't give access to sensitive qubes. That's fair. But surely people
will want to do use it to do something with their actual qubes, not just
empty mockups. Separation into "read-only" access and some additionally
protected write access would be useful.
Maybe have user confirm any (potentially) destructive action? Or maybe
make the LLM write an ansible playbook that user can review before
executing? This is hard problem, because such verification usually
requires substantial knowledge and time (if user would know how to do
that themselves, maybe they wouldn't ask LLM for it?) ...
Regardless of the above remark, it is possible to scope some Admin API
access based on tags, without needing to introduce extra wrappers in
dom0. For example, to allow listing only qubes with ai-managed tag, you
can add rule like:
# allow calling admin.vm.List at all
admin.vm.List * mcp-control @adminvm allow target=dom0
# limit its scope to just ai-managed qubes
admin.vm.List * mcp-control @tag:ai-managed allow target=dom0
And for calls that are about specific qube, the first line wouldn't be
needed, for example:
admin.vm.Start * mcp-control @tag:ai-managed allow target=dom0
Oh, and in your article I see you had issue with disposables, likely
because they didn't have necessary tag added. I guess you will be
interested in `tag-created-with` feature - for example to automatically
add "ai-managed" tag to any qube created by mcp-control:
qvm-features mcp-control tag-created-with ai-managed
> Five questions, all reproduced from the README's reviewer-asks list:
>
> 1. Wrapped-reads existence-hiding. qmcp.GetPropertyAIManaged returns
> the literal string "not found" indistinguishably whether the target
> qube doesn't exist or simply isn't tagged ai-managed. Is uniform "not
> found" from a dom0 qmcp wrapper robust against qrexec-layer leaks
> (timing, error chains, side effects), or are there paths I'm missing?
I'm pretty sure you can't easily eliminate timing side channel.
Existence check is faster, than existence check + tag check. But I'm not
sure how much is that measurable in practice. Other than that, it should
be okay.
> 2. qubes.Filecopy @tag:ai-managed -> @tag:ai-managed allow. Stage B
> adds a policy line bypassing the default ask dialog for inter-qube
> file transfer between ai-managed qubes. Are there assumptions in
> qubes.Filecopy's implementation that depend on the operator dialog
> being present?
There is nothing requiring the GUI confirmation at the protocol level.
Note to use such allow rule, you need to use qvm-copy-to-vm, not just
qvm-copy - otherwise you cannot specify the target, and you'll get
interactive prompt (based on the default qrexec policy, unless you block
it).
But, is your intention really to allow any copy operation between any
AI-managed qubes? Didn't you want to allow copy only to/from
mcp-control?
> 3. target=@adminvm clause on tag-scoped admin allows. Without it,
> qrexec attempts to start the target VM during read-only operations.
> This is subtle, easy to miss, and not surfaced in current Qubes docs.
> Worth a docs PR? Happy to write it.
Sounds like a good idea, yes. Currently it's only mentioned in a comment
in the default policy file, very easy to miss.
> 4. Single-egress chokepoint vs cascade as Qubes idiom. The original
> Stage C design was a cascade (ai-sys-firewall <- ai-sys-tor /
> ai-sys-vpn) with multiple ai-managed network qubes. The implemented
> design is one egress qube with operator-chosen upstream
> (sys-firewall / sys-whonix / a VPN qube / null) and the
> provides_network invariant on it. Documented Qubes patterns lean on
> cascades; is the single-egress chokepoint an established pattern I
> missed, or a reinvention?
I'm not sure if I understand the question. Are you asking if a whole
separate ai-sys-firewall - ai-sys-tor etc chain is a good idea? If all
the network traffic should be configured uniformely (so, all over
clearnet, or all over VPN / Tor), I wouldn't bother with duplicating the
whole chain, and just create one egress qube attached to the chosen
point.
BTW, currently there is no way to limit value of property to be set via
admin.vm.property.Set on the qrexec level. So, with just qrexec policy,
you can't say "allow setting netvm only to value X, or none". You can do
that via a wrapper service, or an admin-permission event handler.
> 5. @tag: matching on klass=DispVM targets on R4.3. Stage D testing
> surfaced this: qrexec policy refuses
> "admin.vm.Remove * mcp-control @tag:ai-managed allow target=@adminvm"
> with "Request refused" when the target is a klass=DispVM, despite the
> DispVM carrying the ai-managed tag (verified via Admin API from dom0).
> Same rule works for klass=AppVM and klass=TemplateVM. Workaround
> deployed: lifecycle (Start/Shutdown/Kill/Pause/Unpause/Remove) routes
> through a dom0 wrapper (qmcp.LifecycleAIManaged) doing the
> ai-managed check in dom0 with qubesadmin authority. Is the underlying
> @tag:-on-DispVM behaviour intentional, a bug, or a configuration step
> I'm missing?
Does it still exist at the point you want to remove it? Disposables
usually have auto_cleanup property set, which means they get removed
automatically once stopped. And you cannot remove a running qube.
But, if you have a stopped disposable, with relevant tag added and it
still can't be removed based on the above rule, it sounds like a bug.
Check journalctl in dom0 for details about such denial.
- --
Best Regards,
Marek Marczykowski-Górecki
Invisible Things Lab
-----BEGIN PGP SIGNATURE-----
iQEzBAEBCAAdFiEEhrpukzGPukRmQqkK24/THMrX1ywFAmodkzgACgkQ24/THMrX
1ywkbgf9HdxKcjrCtDTIpmVwzPK9wEG3O5siPJmw0YD5hSJlcCuPlR9HGjNxo1C+
P6UYb++2Jjc6isWwhUMt7bQ8H9DOPBMxzDUag9LKpMhbIBhKFP97opFeyKsC/qcI
RYW9JEkdeXLXwYNslhfmGUE9qYSpYI7P2gRVGenSIcw5j7Z9CX3Nqk9W9L4u/Brz
rst9og6ODZSk+QDrEQqgLBx4H+MRmUfx4xiJX/dtWSdxhwBDwMa11Z4KdnaVc78c
Nv56ytWbEu+tyMOK/Zh4JqKgwKGtPTFpbwfJIFDG7TKEJEvM7Cb28lYONnootVLw
4Rr2rDpkFuCqPldrdTx+d0FGwyRqRQ==
=F0HH
-----END PGP SIGNATURE-----