Threat model
This page says who sandcastle defends against, what each of them cannot achieve, and what lies outside the promise. Each promise has an id; the test matrix follows each id to the attacks tried against it and the tests that prove them stopped.
The trusted base
The promise rests on these, and promises nothing against them:
- you, the operator;
- the machine: the kernel, Podman, systemd and root;
- the configuration files;
- sandcastle itself, with its dependencies.
A source you configured is not trusted, and neither is anything it sends.
A compromised source, and the content of a job
A source, or the content of one of its jobs, cannot:
| id | promise |
|---|---|
| S1 | read your keys or tokens |
| S2 | reach the internal network, by a redirect or by DNS |
| S3 | leave anything that outlives the job |
| S4 | spend more than its max_jobs_per_hour |
| S5 | see or change the jobs of another source |
It can still send useless jobs up to its limit, see the results of its own jobs, stop answering and delay the other sources.
The program in a box
The program a capability runs is treated as hostile, whether an input exploited it or it came tampered. It cannot:
| id | promise |
|---|---|
| B0 | run without the locks of its box: no network, a read-only root, no capabilities, no new privileges, a user that is not yours, no swap above its memory |
| B1 | read your keys or tokens |
| B2 | reach any network |
| B3 | leave files or processes behind |
| B4 | read the records of other jobs |
| B5 | spend memory, processor, processes, disk or time without a limit |
A model’s reply
| id | promise |
|---|---|
| M1 | a reply is never run |
| M2 | a reply goes only to the source that sent the job |
| M3 | a reply causes no request and no read |
| M4 | a job costs one request to the model at most |
Another account without privileges
Another account on the machine, with no privileges, cannot:
| id | promise |
|---|---|
| A1 | read your keys, tokens and records |
| A2 | see a secret in the list of processes |
| A3 | hand sandcastle a job or speak to it |
| A4 | use the gate |
The network
| id | promise |
|---|---|
| N1 | the internal network is closed after a name is resolved |
| N2 | a sealed address never opens |
| N3 | an internal exception opens only its own ip:port |
| N4 | the keep never follows a redirect |
| N5 | the keep checks the port |
| N6 | there are no roles: only the default rule |
| N7 | the keep and the gate run with the locks the repository gives them |
| N8 | the gate’s own network refuses the internal network, without the gate |
The internal network is closed by two independent barriers. The gate refuses after resolving the name. Under it, the gate’s network has no route to the internal network, and the gate cannot open one. An internal exception opens the whole IP at the second barrier; the port is checked by the keep, and the name by the keep’s TLS. How the network is closed explains both.
Outside the promise
These are not defended against. A report that one of them should move inside the promise is welcome as an ordinary issue; it is not a vulnerability.
- Root and the machine’s system: the kernel, Podman and systemd.
- Programs that run as the same account as sandcastle.
- sandcastle or its dependencies, when tampered with. A tampered gate can get around the second barrier, but only as far as Podman’s own addresses.
- Availability: a source can delay the others, a job can fail, sandcastle can stop.
- The correctness of a result: it can be wrong or misleading and still be valid.
- A base image that was already bad when its digest was fixed.
- An internal destination you opened, and
http://to it. - Money beyond the spending limit you set at the model’s provider.
- Escapes from Podman’s isolation through flaws nobody knows yet.
- The model’s provider.
- Data that leaves through a destination you allowed.
- Side channels.
- Physical access to the machine, and copies of it. The secrets are kept in clear, guarded only by the permissions of their files; encrypting the disk and keeping the values in a password manager are yours to do.
- Mistakes in your configuration, a box’s limits set above their defaults among them.
The test matrix marks where these touch an attack.