slippery and off-topic indeed - put some iterations into it and made a new topic here. it's a subject that many, if not most, of us are interested in, has probably been well discussed before.
nostrdev
Posts
-
Hermes Agent -
Running AI agents on Cloudron: infiltrating other containers and admin servicesThe below is the result of some back and forth with LLMs following our comment on this thread: https://forum.cloudron.io/topic/15616/hermes-agent/28?_=1788437686460
The analysis does not change our position - whilst 99.99% of the time it may be safe to run agents in a cloudron container, the relative ease by which (entirely preexisting) vulnerabilities may be exposed, ought to necessitate the consideration of perhaps running a separate cloudron instance for non-business critical services.
If your cloudron does not serve your company email, vpn, nextcloud, btcpay server etc - you can probably ignore the below.
If readers who are more technical, or well-versed in cloudron architecture and background can correct this analysis, and set things straight, we would appreciate and welcome the feedback.
-- nostr dev --
Generated 2026-09-05. Grounded in the Cloudron platform source (box 10.0.4, release tarball from releases.cloudron.io) and the docs' own App isolation claims.
This is a follow-up to the agent-isolation discussion in the Hermes Agent thread. It is a single-purpose, source-audited writeup: what the Cloudron platform actually puts between containers, what an administrator and a package maintainer can add, what remains, and a verdict for boxes that carry business-critical apps.
Threat model: the agent container is compromised (the realistic vector is prompt injection — the agent runs code that follows instructions found in web content). This document asks exactly one question: what can that compromised container reach on the box — other app containers, and the platform's admin services? Data exfiltration and harm to the agent itself are out of scope.
TL;DR: if the agent container falls, everything else on the box is network-reachable — every co-tenant's app port (by design: that is how the platform's nginx reaches apps), the shared database instances, the platform admin services, and the dashboard. Authentication is the only door, and part of the doors' hygiene is outside your control. Each claim below is verified against the 10.0.4 source with file references so it can be checked, corrected, or argued with.
Evidence base
This section lists the platform facts the analysis rests on, each verified in the Cloudron 10.0.4 source or docs.
- Normal model: every Cloudron app is a single-app container, and a typical box hosts many of them side by side on one user-defined bridge
cloudron(172.18.0.0/16) (src/docker.js, src/platform.js). The network is created with a subnet and gateway 172.18.0.1 — it is NOT--internal, and nothing filters container-to-container traffic (setup/start/cloudron-firewall.sh hooks INPUT only). A MASQUERADE rule is added so containers can also reach each other via the host's public IP. - Platform nginx reaches each app by its container IP:
proxy_pass http://<ip>:<port>(src/nginxconfig.ejs). Consequence: an app's primary HTTP port MUST bind all interfaces (0.0.0.0) to be proxied at all — and is therefore reachable from every other container on the bridge, by design. - setup/start/cloudron-firewall.sh line 119: unconditional ACCEPT from 172.18.0.0/16 to the bridge IP 172.18.0.1 on 3002 (ldap), 3003 (dockerproxy), 3006 (app-bridge), 3007 (scim). DNS (53) to unbound is allowed. The INPUT rules for 22/80/202/443 have no source restriction either, so the dashboard on 443 is reachable from containers — and every app gets an /etc/hosts entry pinning the dashboard FQDN to 172.18.0.1 (src/docker.js), so it is always resolvable by name from inside containers.
- SSO/proxyAuth (src/nginxconfig.ejs
auth_request, src/reverseproxy.js) is enforced by the platform's nginx on the PUBLIC route only. A direct connection to the container's bridge IP does not pass through it — the auth you face on that path is whatever the app itself implements. - docs security.md: apps connect to addons using authentication only; one app cannot access another app's database or files. Shared addon instances have static IPs (MySQL 172.18.30.1, PostgreSQL 172.18.30.2, MongoDB 172.18.30.3) and rate limits (5000 conns/sec per app).
- src/dockerproxy.js: the Docker API proxy on 3003 authorizes by source IP plus
manifest.addons.docker; without the addon it returns 401, and it strips Privileged/CapAdd/Devices/SecurityOpt on create. - The LDAP service on 3002 authenticates binds — the system bind checks a real per-box secret (src/directoryserver.js), and the ldap addon is what hands that secret to the app.
- setup/start/docker-cloudron-app.apparmor: "Keep in sync with Docker's docker-default" — grants network, capability and file broadly; denies mount and /proc, /sys writes. No custom seccomp, no user-namespace remap, no per-app runtime choice anywhere in src/ or setup/.
- NET_RAW is dropped unless the manifest asks for "ping" (src/docker.js) — raw-packet spoofing is blocked, but ordinary TCP connect scanning of the bridge is fully possible.
The 3 key platform protections
This section describes what the platform itself puts between a compromised container and the other containers / admin services. These are what make ordinary multi-app co-tenancy safe.
1. Per-app credentials and authentication-only access to shared services. The platform creates unique credentials for each app on every addon (databases, mail, LDAP) and rotates them itself; every shared service accepts only authenticated access. What it stops: a compromised container can open TCP to any co-tenant and to the shared MySQL/PostgreSQL/Mongo instances, but it holds no valid credentials for them — "one app cannot access another app's database or files". On the flat, unfiltered bridge, authentication is the only inter-app barrier, and it is a real one. Its limits: it protects data, not reachability — and it only covers services that authenticate. The ldap addon hands over a valid directory secret, the docker addon unlocks the dockerproxy; and an app's own HTTP port has only whatever auth that app implements (see protection 3 for what SSO does and does not cover).
2. The container confinement stack applied to every container. Every app container is created read-only rootfs, runs as a non-root user, has NET_RAW dropped unless the manifest asks for it, and runs under the docker-cloudron-app AppArmor profile that denies mount and /proc, /sys writes. What it stops: tampering with the shared kernel interfaces and the common container-escape primitives — i.e. it protects the HOST, which is the admin service of last resort. Its limits, verified in source: the profile is deliberately a copy of Docker's docker-default (baseline, not hardened), and it does nothing to restrict network reach into other containers — a full TCP connect scan of the bridge is trivially possible from a compromised app.
3. Authenticated TLS proxy as the only public ingress. Each app is served on its own subdomain behind the platform's nginx with TLS; the whole container network (172.18.0.0/16) is unreachable from outside the server, no container port is directly exposed to the internet, and origin isolation means XSS in one app cannot steal another app's session. What it stops: infiltration of any app or admin surface from the public internet — attempts must originate from inside the box. Its limits: this is exactly the position a compromised agent container is in — inside the box, on the bridge, with every co-tenant and admin service routable. And SSO/proxyAuth are enforced on this public route only; they do not apply to a direct bridge connection to the container IP, where the only barrier is the app's own auth.
Hardening from the Cloudron admin's seat
This section is what the box administrator can actually do, with the controls Cloudron gives them, to shrink the infiltration surface.
- Enable SSO/proxyAuth on every app that supports it — but understand what it buys: it guards the front door (public route through nginx). It does nothing for a direct container-to-container connection, which lands on the app's own auth. Treat it as defense for the internet path, not the bridge path.
- Audit what each app exposes on the bridge. From each app's web terminal, run
ss -ltnpand check listeners: you should see the app's httpPort on 0.0.0.0 (normal, required) and anything else that app bundles — a Redis, an internal DB, an admin panel — on 127.0.0.1 only. Anything else on 0.0.0.0 is a bridge-reachable door with only the app's own auth in front of it. - Harden the dashboard. It is resolvable by name and reachable on 443 from every container. Single operator account, strong password, 2FA, and restrict dashboard access to admin IPs. Watch the eventlog: ANY failed login originating from a 172.18.x.x address is a container trying the admin door — on a normal box that should be zero, ever.
- Keep the box patched, urgently. The confinement stack is deliberately baseline (docker-default AppArmor copy, stock seccomp, no userns-remap, runc only — Sysbox and Podman were both declined by Cloudron upstream), so the host is defended mainly by patching speed. Verify unattended-upgrades is active; treat kernel/runc/dockerd CVEs on a box that runs agents as urgent, not routine.
- Tune detection to the canaries that matter. Alert on container-originated logins to the dashboard, on container-to-172.18.0.1 flows beyond DNS/dashboard (platform services 3002/3003/3006/3007 are always reachable), and on auth-failure spikes at shared addons (brute-force from the bridge). None of this traffic is filtered by the platform, so only your monitoring sees it.
- Keep tested, encrypted backups. If infiltration succeeds anywhere, rebuild the affected app from backup rather than forensically clean it.
Hardening from the app maintainer's seat
This section is what a package maintainer controls in their own app's container — written for the agent package, but the same rules apply to every package.
- Ship zero addons unless the feature is essential. localstorage only. Each addon mints credentials that are VALID against a shared or admin service: ldap hands the app a real directory secret, docker unlocks container lifecycle via the dockerproxy, sendmail/recvmail authenticates against the mail container, sql mints a database user. No addons = the container holds zero keys that open any admin door. Treat any addon addition on an agent package as a security decision, not a convenience.
- Bind bundled daemons to 127.0.0.1, never 0.0.0.0. Yes, 0.0.0.0 binds are fully possible in a Cloudron container — and required for the primary httpPort (platform nginx proxies to the container IP). But sidecar services the platform never needs to reach — the agent's internal DB, dashboards, worker APIs — must bind loopback. Scheduler tasks and backup containers share the app's network namespace, so loopback does not break them. Loopback binding is the difference between a bundled daemon being reachable only by its own app and by every co-tenant on the bridge.
- Declare no capabilities. No
net_admin(adds NET_ADMIN + NET_RAW), noping(keeps NET_RAW dropped). Non-root user throughout. - Make the app's own HTTP auth real. The httpPort must listen on 0.0.0.0 and is therefore reachable by co-tenants directly, bypassing any platform proxy auth. Whatever the app serves on that port needs its own authentication — for an agent package, the gateway API key/session the package already generates. Never ship the primary port unauthenticated.
- Health check and limits. A real health-check path (not an SPA fallback that always returns 200), and memory/CPU limits so a runaway process cannot starve the box.
Residual risks after all hardening
This section is what remains attackable — from the compromised agent container into other containers and admin services — even with the platform protections and both hardening seats applied.
1. Every co-tenant's primary port is reachable, by design, with only the app's own auth in front. The platform's nginx reaches apps by container IP; therefore every app's HTTP port binds all interfaces and every other container can connect to it directly. SSO/proxyAuth do not apply on that path. So the inter-app barrier for the typical web app reduces to the app's own login form — plus per-app credentials where the app talks to addons. An agent container can enumerate co-tenants (Docker DNS or a /16 sweep) and methodically try every front page and login. Detection: container-to-container flow alerts on the host. Mitigation: the audits above (know which of your apps have weak or default auth on their primary port); SSO wherever the app supports it, for the front door at least.
2. Platform admin services are permanently reachable from every container. Any compromised container can open TCP to 172.18.0.1:3002 (ldap), 3003 (dockerproxy), 3006 (app-bridge), 3007 (scim), DNS, and the dashboard on 443 — unconditionally allowed by the firewall. Without addons these reject the agent's requests (dockerproxy 401s, ldap demands the bind secret), but the attack surface never goes away: the dashboard login accepts attempts from container IPs (rate-limited to 10/s, not blocked), and an unauthenticated bug or info leak in any of these services would be reachable by exactly the workload most likely to look for it. Detection: the 172.18.x failed-login canary; container-to-172.18.0.1 flow alerts. Mitigation: 2FA and IP restriction on the dashboard; accept the network path as a platform constant and monitor it.
3. Kernel/container escape = the host = every admin service at once. The confinement stack blocks the common primitives, but it is deliberately baseline. Operator hardening does not change escape probability. A public runc/kernel escape lands on the host, which means: every container's /app/data, the platform database (all app credentials, user directory), the docker daemon itself, and the operator's SSH surface. This is the maximum-severity outcome of the infiltration question. Detection: host-side auditd on mount/nsenter/setns; file-integrity monitoring; alert on unexpected privileged processes. Mitigation: urgent patch posture; no SSH keys or VPN credentials stored on the box; snapshot for fast rebuild.
4. Every addon is a key into an admin or shared service. The "zero addons" rule is load-bearing and fragile: the moment the agent's manifest gains an addon for a feature (ldap for directory sync, docker for builds, sendmail for notifications), the platform mints credentials valid against an admin or shared service — and those live in the container env, readable by the injected process. Addon creep is the quiet way the infiltration surface regrows. Detection: review the manifest diff at every package update. Mitigation: treat every addon request on an agent package as a security decision.
5. The operator's session is the softest admin door. The agent has a web surface the operator logs into (dashboard, kanban, chat history). A prompt-injected agent renders attacker-influenced content in that surface — crafted links, file names, session summaries — aimed at the one admin who trusts it. The operator's browser session is a realistic path from "agent compromised" to "box admin compromised" that no network control addresses. Mitigation: keep the dashboard in a separate browser profile from the agent UI; 2FA everywhere; treat links and downloads coming out of the agent's UI as untrusted input.
Bottom line
This section is the reachability takeaway, then the verdict for business-critical boxes.
Cloudron's platform protections make ordinary multi-app co-tenancy safe: other containers' data is behind per-app credentials, the host is behind a baseline-but-real confinement stack, and everything admin-facing sits behind an authenticated TLS proxy on the public side. But for a compromised agent container the question is reachability, and the reachability facts are: the bridge is flat and unfiltered; every co-tenant's primary port is reachable by design (that is how the platform's nginx works) with only the app's own auth in front, since SSO/proxyAuth guard the public route alone; the platform admin services and the dashboard are unconditionally reachable from every container; and an addon minted for convenience is a key into an admin door. The practical posture: zero addons on the agent package, loopback for everything the platform does not need to reach, real auth on the primary port, a 2FA-hardened dashboard, urgent kernel patching, and detection tuned to the canaries that matter — container-originated admin logins, container-to-container and container-to-platform flows. The honest caveat from the source: the confinement stack is deliberately baseline, so the host itself remains defended mainly by patching speed.
Is the risk real if you run your business on the box? Yes — but it decomposes.
- What is genuinely new with an agent: the probability of the first container falling. A browsing agent converts any webpage it reads into an attack surface — prompt injection is routinely demonstrated, not theoretical, and an agent that browses daily for months will eventually ingest attacker-controlled content. Most business apps are only attackable through their own public interface; the agent moves the entry point to "anything the agent will read". Second, the operator-session vector: the agent renders links, filenames, and summaries that the one admin who logs into everything trusts by default — a path from "agent pwned" to "box admin pwned" that no network control addresses, with mail (the master key to the business's identity infrastructure) sitting on the same box. Third, the crown jewels of a business box — mail, files, password manager — are exactly the co-tenants whose only inter-app barrier is their own login form on a bridge-reachable port.
- What is honestly not new: the post-compromise topology. If any PHP app on your box is popped today, it can already TCP-scan every co-tenant and try their login forms; businesses have quietly accepted that trade for years. The agent changes the likelihood of the first container falling — not the map of what is reachable after it falls.
- Practical verdict: a business box hosting mail, files, or password credentials alongside an always-on browsing agent is an unfavorable bet — injection probability multiplied by the value of mail — and we would not run it that way. The same box with only public-facing web apps, plus a zero-addon, loopback-bound agent behind SSO/2FA and the canary alerts above, is defensible — with eyes open that the host boundary is baseline confinement plus patch speed.
This writeup is the evidence base behind the position we stated in the Hermes Agent thread: the platform's protections assume the threat is outside the box, and an agent is a workload that reliably puts the threat inside it. That is why we run agent workloads off Cloudron (Firecracker microVMs on dedicated hardware, currently under evaluation). For everyone who does want agents on Cloudron, the two hardening sections above are the practical checklist — and if any of the file-cited facts is wrong, the source is public and we would genuinely like to know.
- Normal model: every Cloudron app is a single-app container, and a typical box hosts many of them side by side on one user-defined bridge
-
Nextcloud Talk high-performance back-endtypically no more than 6 or 7
-
Hermes Agent@andreasdueren we were initially tempted to use cloudron for packaging agents but given the many documented cases of AI breaking out of containers, compounded by the amount of unencrypted internal cloudron comms and ports being available, we backed away
We are now evaluating firecracker on a dedicated machine for agent hosting
Curious what your (and others) position is with regard to the risks of running agents on cloudron containers
-
Nextcloud Talk high-performance back-end@ikalou - we gave up on jitsi / mirotalk / nextcloud for group calls. These days we mostly use Proton Meet.
-
Cloudron `/system/filesystems` fails with LUKS device aliasThe Cloudron dashboard does not show disk usage (not in v10.0.2 nor earlier versions for at least a year). The request to:
/api/v1/system/filesystemsreturns:
{ "status": "Internal Server Error", "message": "Cannot read properties of undefined (reading 'contents')" }Environment
- Cloudron: 10.0.2
- Ubuntu: 24.04.2 LTS
- Kernel: 6.8.0-111-generic
- Hosting: Hetzner
- Storage layout: NVMe → mdadm RAID1 → LUKS → ext4
The root filesystem itself is healthy and reports usage correctly:
Filesystem Type 1B-blocks Used Avail Use% Mounted on /dev/mapper/luks-a7030049-5aad-45e0-915c-3073a14fb4ac ext4 1883388502016 1367907303424 419734818816 77% /Box error
The Box log contains:
shell: df: df -B1 --output=source,fstype,size,used,avail,pcent,target shell: df: df -B1 --output=source,fstype,size,used,avail,pcent,target /home/yellowtent/platformdata { path: '/api/v1/system/filesystems', status: 500, error: HttpError: Cannot read properties of undefined (reading 'contents') at getFilesystems (file:///home/yellowtent/box/src/routes/system.js:153:28) { status: 500, internalError: TypeError: Cannot read properties of undefined (reading 'contents') at Object.getFilesystems (file:///home/yellowtent/box/src/system.js:112:42), details: null } }The relevant code in
/home/yellowtent/box/src/system.jsis:for (const stdPath of standardPaths) { const [dfPathError, diskInfo] = await safe(df.file(stdPath.path)); if (dfPathError) throw new BoxError(BoxError.FS_ERROR, `Error getting std path: ${dfPathError.message}`); filesystems[diskInfo.filesystem].contents.push(stdPath); }Apparent cause
The two
dfinvocations identify the same root device using different names.An unqualified
dfreports:/dev/dm-0 ext4 ... /A path-specific
dfreports:/dev/mapper/luks-a7030049-5aad-45e0-915c-3073a14fb4ac ext4 ... /These are aliases for the same device:
/dev/mapper/luks-a7030049-5aad-45e0-915c-3073a14fb4ac → /dev/dm-0It appears that Cloudron initially indexes
filesystemsusing the literal source returned by the unqualifieddf:filesystems[dfEntry.filesystem] = ...It then looks up the path-specific result using the other device alias:
filesystems[diskInfo.filesystem].contents.push(stdPath);As a result,
filesystems[diskInfo.filesystem]is undefined and the API throws while accessing.contents.It may be necessary to normalize device paths before using them as keys, or fall back to matching by mountpoint/device identity.
Has anyone else encountered this with an mdadm + LUKS root filesystem? Is a fix planned for an upcoming Cloudron release?
-
Wazuh - The Open Source Security Platformgood to know..
-
Cloudron works great with Wazuh/Ossec agentyou can also run the management /dashboard component: https://ca.cloudron.io/app/wazuh
-
Wazuh - The Open Source Security PlatformYour wish has been granted: https://ca.cloudron.io/app/wazuh
-
Upgrade your BTC Pay Server@necrevistonnezr - I guess you are not going to be needing BTC Pay Server any time soon. Thanks for sharing your nicely-worded (and referenced) opinions.
-
Upgrade your BTC Pay Server@necrevistonnezr - hardly. Coldcard are accountable for their catastrophic mistake. The community has also learned a valuable lesson with regards to RNG and entropy.
Note that the coldcard incident had nothing at all to do with the Bitcoin protocol, which continues to securely mine blocks.
Remember that the money you loan to the bank:
- doesn't exist (it's credit)
- does not belong to you (it's a credit)
- is held by a (credit) institution that is not accountable to you. As anyone who has had their account frozen will attest.
-
Upgrade your BTC Pay ServerIf you are running BTC Pay Server, please upgrade immediately, and destroy the macaroons file. There is an active exploit.
More info: https://github.com/btcpayserver/btcpayserver/releases/tag/v2.4.2
Image details (v2.4.2 is now available): https://ca.cloudron.io/app/btcpay-server
-
Android down the shitter? Pixel 10 or iPhone 17? Thoughts?Our recommendation is to use a data-only e-sim (eg silent.link) and ditch the phone number. If you need SMS for 2FA, just use a web provider such as crypton.sh
fyi, for calendar you'll need a caldav bridge, davX is available on Zap Store
On Zap Store you can also see what apps others are recommending / installing (and zap the devs directly)
-
Android down the shitter? Pixel 10 or iPhone 17? Thoughts?Pixel with Graphene OS, it's a no-brainer!
-
Who is selling hosted (and supported) Cloudron servers?We are happy to block the IP of any country implementing dangerous policies like this
No responsible adult should allow their kids to upload personal identity documents to random (or worse, mainstream) websites
-
New matrix admin interface (Ketesa)curious if anyone has tried it / has any feedback?
https://github.com/etkecc/ketesa
Billed as the replacement for Synapse Admin..
-
Routstr AI ServerRoutstr is now available as a community app: https://raw.githubusercontent.com/Routstr/routstrd-auth/refs/heads/cloudron/CloudronVersions.json
You can plug routstr in as a model provider to an array of AI clients, such as Roo Code, Claude, Opencode, Cursor, etc. Just use the url (eg your.cloudron.domain) and the api key.
There is no web app, it's just an API server. To set it up, use the terminal on the server, or install it locally with
bun install -g routstrdInstructions: https://github.com/Routstr/routstrd-auth/tree/cloudron#cloudron
Note - you have a choice of models, and you pay for them using Bitcoin over the lightning network. To top up your balance, run:
routstrd receive xxxxwhere xxxx is an amount in sats. This balance will be shared across all the users, so it's a great way to provide no-kyc AI to your team. -
Umami not respondingsame issue here. Moving to 8gb worked. subsequently resizing back to 2gb or 4gb does not work.
8gb is pretty big!
-
Trakstr Time TrackingAfter being dissatisfied with existing time tracking solutions, we built our own. It is now available for others to try, and we'd love to get your feedback. We have been using it in production for ~6 months. Features include:
- Unlimited users, projects, clients
- Nostr only (everyone needs a nostr signer to access). We have designs to enable cloudron email (with hosted nostr account), plus many more features, if anyone would like to collaborate or invest in it.
- Optional desktop app for activity / screenshot capture
The first user to log in becomes the admin. You can set roles by project (eg owner, manager etc) to allow scoped visibility into hours tracked, and you can configure usage alerts by project or customer using a matrix API key.
https://git.nostrdev.com/trakstr/cloudron-trakstr/raw/branch/master/CloudronVersions.json

-
Strfry Nostr RelayWe've bumped strfry to the latest version (1.1.0) and made it a community app
You can try it out here: https://git.nostrdev.com/stuff/cloudron-strfry/src/branch/main/CloudronVersions.json