Keep state and keys out of the shared repo
The dev VM is treated as untrusted, and the repo is a read-write virtiofs mount shared with the backend host -- so a CA private key sitting in it is a key that machine can sign with, and a leaf signed by this CA is one the phone's pinned app accepts without question. Pinning against a CA the attacker holds is no pinning at all. So certificates are now generated on the machine that serves them, into $XDG_CONFIG_HOME/ai-app/certs at 0700 with 0600 keys (AI_APP_CERTS overrides), and config.json and session transcripts move to the XDG config and data directories. Transcripts move for a plainer reason than the keys: they are whole conversations, and they were world-readable at 0644. Two smaller things fall out. The host and VM stop sharing one config, which had already put a test token on the production backend. And state stops living where `git clean -xdf` would take the enrollment and every transcript with it. State that predates the move is still read from the repo, with a warning naming where to move it, so an existing install keeps working rather than silently coming up on an empty config -- the precedence is covered by a test, since picking the wrong file would otherwise be silent. Verified: 31 tests, clippy clean; the certificate script writing 0700/0600 into an overridden directory; and the server logging the fallback and serving from it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017xn8nHw1tw1R6PtiY1eEtw
This commit is contained in:
1 parent
fff1fb49e8
commit
d2d2832ec8
8 files changed
+248
-37
No files matched your search
@@ -252,15 +252,46 @@ GET /usage cached usage windows
|
||||
GET/PUT /hosts, /models config editing from the phone
|
||||
```
|
||||
|
||||
Sessions live in `config.json` + a per-session directory (transcript.jsonl,
|
||||
attachments, produced images). Deleting a session is the complete path out of
|
||||
everything spawning one created.
|
||||
Sessions live in `config.json` (`$XDG_CONFIG_HOME/ai-app/`) + a per-session
|
||||
directory under `$XDG_DATA_HOME/ai-app/sessions/` (transcript.jsonl,
|
||||
attachments, produced images), owner-only. Deleting a session is the
|
||||
complete path out of everything spawning one created. State that predates
|
||||
the move is still read from the repo, loudly, so an existing install keeps
|
||||
working until it is moved by hand.
|
||||
|
||||
### Security
|
||||
|
||||
- TLS with a self-signed CA, pinned in the app — `gen-dev-cert.sh` and
|
||||
`PinnedCert.kt` copied from local-updater, same idempotent-CA/reissued-leaf
|
||||
scheme, same one-way-door caveat about regenerating the CA.
|
||||
- **The dev VM is untrusted** (decided 2026-08-25): a machine that isn't
|
||||
malicious but could become so. It matters because the repo is a
|
||||
read-write virtiofs mount shared between the VM and the backend host, so
|
||||
under this model everything in it — source, `server/target/` binaries,
|
||||
and the shell scripts the host runs, some with sudo — is
|
||||
attacker-writable. Two consequences:
|
||||
- **Nothing secret lives in the repo.** Certificates are generated on
|
||||
the machine that serves them and written to
|
||||
`$XDG_CONFIG_HOME/ai-app/certs` (0700, keys 0600); `config.json` and
|
||||
session transcripts go to the XDG config and data directories, per
|
||||
machine. A CA private key the VM could read would let it mint a leaf
|
||||
the pinned app accepts, which is precisely the attack pinning exists
|
||||
to stop — pinning against a CA the attacker holds is no pinning at
|
||||
all. Transcripts move for a plainer reason: they are whole
|
||||
conversations. As a bonus this ends the host and VM sharing one
|
||||
config, which had already produced a test token live on the backend,
|
||||
and takes state out of reach of `git clean -xdf`.
|
||||
- **The host should not execute what the VM can write** — build and run
|
||||
the backend from a host-only checkout rather than the shared mount.
|
||||
Moving the keys closes the smaller door; this is the larger one.
|
||||
- Development in the VM generates its own throwaway CA. Whatever is
|
||||
installed on the real phone must pin only the host's.
|
||||
- The CA key is not needed by the server at all (only `leaf.pem` and
|
||||
`leaf-key.pem` are read), so it can move offline once the setup is
|
||||
stable; reissuing a leaf is the only time it is wanted.
|
||||
- Not addressed, and accepted: a compromised VM can return anything it
|
||||
likes from the sessions it runs, since running an agent there is the
|
||||
point. The blast radius is that session's content, not the backend.
|
||||
- This server is strictly more dangerous than the updater: its API *is*
|
||||
remote code execution (spawn a bypass-permissions Claude on any SSH host).
|
||||
Pinning authenticates the server to the phone but not the phone to the
|
||||
|
||||
Reference in new issue
Block a user