Apps on TheCube provide much (read: almost all) of the functionality in TheCube beyond basic voice interaction and the GUI. But, allowing apps to have unfettered access to the filesystem and hardware would present a significant security risk. Therefore, apps are sandboxed using a multilevel system.
The important point is that TheCube does not treat “sandboxing” as one switch. It is not just “run the app and hope for the best,” and it is not just “throw it in a container.” Instead, the runtime is built as a chain of controls, each with a different job:
CubeCore → systemd → CubeAppLauncher → app
Each stage narrows what the app is allowed to do, and each stage exists for a reason.
Level 1: Apps must declare themselves before they are trusted
The first layer of sandboxing happens before an app is ever started.
Apps are discovered from install roots, their manifests are parsed, and those manifests are validated before Core will even consider launching them. A valid app has to declare what it is, how it runs, and what permissions it expects. In practice that means things like:
- app identity
- runtime type
- working directory
- filesystem permissions
- environment rules
- platform and network metadata
That matters because TheCube does not launch raw app manifests directly. Core reads the manifest, validates it, and then compiles a resolved launch policy. That generated policy, not the original manifest, becomes the contract the launcher actually uses.
This is the first major boundary: apps do not define their own runtime in an unrestricted way. They request; Core resolves.
Level 2: Core compiles a stricter launch policy than the app asked for
A key detail in our implementation is that Core does not just pass along developer intent. It translates the manifest into a concrete policy file that includes:
- resolved executable path
- resolved argv
- resolved working directory
- generated environment variables
- resolved filesystem allowlists
- runtime directories that must exist before launch
This matters because a manifest might contain abstract tokens like app-local data paths or shared read-only assets. Core resolves those into exact host paths and exact runtime directories. By the time the launcher sees the policy, ambiguity is gone.
That also gives us one place to normalize behavior across native, Python, Node, Docker, and future app types.
Level 3: Apps get app-private runtime directories instead of broad host access
TheCube intentionally gives apps their own data, cache, and runtime directories. In other words, the default pattern is:
- app-private data
- app-private cache
- app-private runtime state
not:
- random writes to the host filesystem
- arbitrary use of tmp
- implicit access to system locations
This is one of the most practical sandbox layers because it changes how apps are written. If an app can keep its persistent data in its own app directory and its transient state in its own runtime directory, there is less pressure to grant broad host access in the first place.
That is the best kind of sandbox rule: one that removes the need for dangerous exceptions.
Level 4: Filesystem access is allowlisted, then enforced with Landlock
This is the most obvious “sandbox” layer in the traditional sense.
The launcher reads the generated filesystem policy and applies Landlock rules for:
- read-only paths
- read-write paths
- create paths
The important thing is what it does not do. The launcher no longer auto-allows broad host paths like usr, lib, etc, proc, or dev just because an app might need them. If an app needs a system location, that access has to be declared explicitly.
We use a token model for this. Apps can request things like:
- app-scoped paths such as install, data, cache, and runtime locations
- shared read-only paths
- explicit host paths through system://...
That last one is important. If an app needs host path access, it has to say so in a way that is deliberate and reviewable. No implicit filesystem inheritance. No “it probably needs this whole directory tree.” The goal is to make filesystem access narrow, auditable, and intentional.
Level 5: systemd supervises lifecycle, but it is not the sandbox
systemd is part of the chain, but it is not the whole story.
Apps run as transient units. That gives us lifecycle supervision, restart behavior, failure visibility, and a clean operational model. If an app crashes, hangs, or exits badly, systemd is the source of truth for that state.
But systemd is only one layer. It is responsible for process supervision. The launch policy and the launcher are responsible for shaping the app’s environment and filesystem access. Treating supervision and sandboxing as separate concerns is important, because they solve different problems.
Level 6: Local apps do not get implicit API access just because they are on-device
A lot of systems make a dangerous assumption: “if the app is local, it is trusted.”
TheCube does not do that.
Local apps talk to CORE over IPC, not by bypassing security. IPC requests must carry an app runtime identity header. That header is not just the manifest app ID; it is a runtime app auth ID generated at startup and associated with the installed app.
That distinction matters because it gives us a revocable runtime identity instead of a static label an app could trivially reuse forever.
From there, authorization works in two steps:
- Is this a valid installed app identity?
- Is this app authorized to access this specific endpoint?
For public IPC endpoints, a valid app identity is enough.
For non-public endpoints, the app also needs an explicit grant in the app authorization data. In other words, being installed is not the same thing as being authorized.
That is a major sandbox boundary in practice. Filesystem restrictions stop apps from poking around the host. IPC authorization stops them from poking around Core.
Level 7: Hardware access is brokered, not assumed
Another principle in TheCube’s sandboxing model is that direct hardware access should be exceptional.
In our current architecture, apps are expected to prefer Core-brokered APIs rather than opening up arbitrary hardware paths themselves. That matters for two reasons:
- hardware access is powerful and hard to constrain safely if exposed directly
- once Core brokers access, hardware operations can be validated, permissioned, logged, and shaped like any other API surface
This is especially important as TheCube’s hardware model grows. Some devices are directly attached to the Raspberry Pi and are reserved for internal system functions. Others are intended to sit behind the IO bridge and be exposed in controlled ways to apps. The security model is cleaner if apps consume capabilities through brokered interfaces instead of raw bus access.
So sandboxing on TheCube is not just about files. It is also about making sure hardware is reached through the right layer.
Level 8: App-specific credentials beat shared secrets
One subtle but important part of the model is how we handle service access for apps that need more than filesystem and IPC.
A good example is app-scoped PostgreSQL access. Rather than just dumping a shared database password into every app’s environment, Core provisions per-app access and injects a bootstrap token into the launch environment. The app can then use that bootstrap path to fetch the credentials it is entitled to, instead of inheriting a global secret with broader reach than it should have.
That is sandboxing too.
A lot of sandbox discussions focus on syscalls, files, or containers. But secret distribution is part of the same problem. If every app gets the same credential, your sandbox is already leaking. App-specific bootstrap and scoped credential delivery keep the blast radius smaller.
Why this is a multilevel system instead of a single mechanism
No single control is enough on its own.
- Manifest validation is good, but it does not enforce runtime behavior.
- Landlock is good, but it does not control API authorization.
- IPC auth is good, but it does not stop filesystem abuse.
- systemd is good, but it does not define permissions.
- service-specific credential brokering is good, but it does not constrain process execution.
Put together, though, these layers reinforce each other.
An app must declare what it wants.
Core resolves that into a concrete policy.
The launcher enforces filesystem limits.
systemd supervises execution.
IPC requires runtime app identity.
Private Core endpoints require explicit authorization.
Sensitive backing services can be provisioned per app instead of shared globally.
Hardware access is expected to go through Core-managed boundaries.
That is what “multilevel sandboxing” means in practice on TheCube.
The philosophy behind it
TheCube’s app system is intentionally powerful. Apps are not decorative plugins. They are the platform.
That means the sandbox cannot be an afterthought. It has to assume that apps are useful, dynamic, and sometimes third-party, while still protecting:
- the host filesystem
- internal hardware
- Core’s private API surface
- per-app identity
- secrets and service credentials
- the stability of the system as a whole
The goal is not to make apps weak. The goal is to make them powerful in controlled ways.
That is the difference between “apps can do things” and “apps can do everything.” TheCube is built around the former, not the latter.