1.5 beta: IPC hardening (XPC, gRPC, llama HTTP, Postgres)
Status at the 1.5 release. Every work item below has landed: Postgres, the gRPC facade and the llama HTTP API listen on Unix sockets in
s/at the root of the App Group container, every XPC listener requires a same-team code signature, the in-app Python reaches llama.cpp over NSXPC, the config-changing RPCs answer only on the socket, and the MCP HTTP server is opt-in. The current picture is in the privacy guide andCLAUDE.md; the text below is the plan as written, line numbers included.
Findings from reading main on 2026-09-25, carried onto v1.5-beta on 2026-09-27. The per-launch GARAGE_GRPC_TOKEN from #104 is on this branch and covers the unauthenticated-port part of item 1. Since then the App Store build hosts GarageIngestXPCService and GarageXPCService in the app’s own process behind anonymous listeners; item 2 covers those listeners too. Line numbers below are from main at the time.
Current state
- gRPC is loopback TCP, not XPC.
- The app connects with
ClientConnection.insecure(...).connect(host: "127.0.0.1", port: 50051)(macapp/Sources/GarageApp/Services/GarageGRPCService.swift:135). - The Python workers use
grpc.insecure_channel(garage_python/src/garage_rag/service/client.py). - The server calls
add_insecure_port(service/server.py:1417). - XPC only starts the server.
GarageClient(in_process=True)is a direct Python call, not XPC.
- The app connects with
- The XPC listeners accept every peer.
GarageXPCServiceBase.listener(_:shouldAcceptNewConnection:)returnstruewith no code-signing requirement and no audit-token check. The only protection is that bundled services can be looked up only from inside the app bundle (not the App Group). Sibling XPC services connect fine; see theLlama Loaderself-test. - The llama HTTP API (
127.0.0.1:8790,LlamaHTTPServer) has no authentication. Any local process, under any user, can use the loaded models. Slots expose no prompts.
Work items
- Unix domain sockets for gRPC and llama HTTP. Put them in a 0700 directory inside the App Group container.
- grpc Python takes
unix:addresses, grpc-swift/NIO can connect to a Unix socket,NWListenercan listen onNWEndpoint.unix, and httpx hasHTTPTransport(uds=). - On connect, check the peer: read
LOCAL_PEERTOKENand validate its code signature. - Teach
net/egress.pyand theCALLERStest about UDS destinations. - The launchers keep working because they carry the group entitlement.
- This replaces or backs up the #104 token.
- grpc Python takes
- Done in this branch: a code-signing requirement on every XPC listener (macOS 13+). Pin team
DWVXMLB45Yand theme.rickmark.garage-rag.*identifiers, with a looser requirement for locally signed configurations. Enable hardened runtime and library validation on the app itself. - Inference over NSXPC for in-app Python. Extend the
LlamaModelLoaderBridgepattern: a@convention(c)entry point called through ctypes, which releases the GIL.- Signature:
llama_call(route, json_in, &json_out, &err_out), plus a free function. Swift allocates the reply because embedding batches are large. - It forwards through
LlamaClient, andLlamaXPCClientprefers it when it is installed. - Covers
GarageXPCService, the embed worker andGarageMCPServerService. The launchers and a venvgaragestill need HTTP/UDS, so HTTP becomes opt-in rather than gone.
- Signature:
- Gate config-changing RPCs behind the peer check. Changing
embedding.ollama_hostthroughSetSettingwidens the egress allowlist, which would let document and code chunks go off-box. - Postgres over the same kind of socket. Today the bundled cluster uses TCP only.
- Current settings:
listen_addresses=localhost,unix_socket_directories=(sockets off),--auth=scram-sha-256(PostgresService.swift:396,460). - The protection now is the password in the App Group keychain, not the transport. Any local user can reach port 14824 and try the password.
- Change: set
unix_socket_directoriesto a directory in the group container,unix_socket_permissions=0700andlisten_addresses=''. - Keep
local ... scram-sha-256inpg_hba.conf.peerisn’t worth adding, since every process runs as the same user. - libpq/psycopg take the socket through
host=/path. - The launchers’ “is anything listening on 14824” check becomes “does the socket exist and answer”.
- Watch out: macOS limits a socket path to 104 bytes (
sun_path)..../Group Containers/DWVXMLB45Y.group.me.rickmark.garage-rag/Library/Application Support/GarageApp/...plus.s.PGSQL.14824is too long. Put all three sockets in a short directory right under the container root (e.g.<container>/s/). Postgres refuses to start when the path is too long, so test with a long username. - Check that the sandboxed XPC services can
connect()to a Unix socket in the group container. I believe the group entitlement covers it, but I haven’t verified that. - Development Homebrew servers and CI keep TCP. This applies only to the app’s own cluster.
- Current settings:
- MCP HTTP off by default. With Postgres, gRPC and llama on Unix sockets, the MCP HTTP server
(
127.0.0.1:8787) was the last TCP listener. Assistants are now registered over stdio with the bundledgarage-mcplauncher, and the HTTP server runs only after the user starts it on the MCP page (garage.mcp.httpEnabled). An existing install with an HTTP registration keeps HTTP on.