FOKS — Federated Open Key Service
End-to-end encrypted Git, secret storage, and team management you can self-host.
Website · Docs · Hosted Service · Whitepaper · FCPs
*
Install
The easiest way to install is via a package manager. See foks.pub for full details.
macOS (Homebrew):
brew install foks
Debian / Ubuntu:
curl -fsSL https://pkgs.foks.pub/install.sh | sh
apt-get install foks
Fedora:
curl -fsSL https://pkgs.foks.pub/install.sh | sh
dnf install foks
Arch Linux (AUR):
yay -Sy go-foks
Windows:
winget install foks
Static binary (any platform):
curl -fsSL https://pkgs.foks.pub/install-static.sh | sh
Build / Test / Install
Prerequisites
- A modern go, v1.24 or later
- Optional
Build the Client (to test against live alpha prod data):
make # build for the local platform; install into $GOPATH/bin
make git-link # symlink git-remote-foks to foks (only needed once)
foks ctl start # start the agent, persistently via systemd or launchd or Windows Registry
foks signup # sign up for a new account on foks.app, invite code is cczjho9r
Linux
On Linux, you'll need the PCSC lite library before you can build:
sudo apt-get install gcc make libpcsclite-dev pkg-config
And to run with YubiKey support, you'll need:
sudo apt-get install pcscd
On some Linux distributions, standard users lack permission to access pcscd,
the service that manages YubiKey access. The following configuration grants
unprivileged users access:
cat <<EOF > /etc/polkit-1/rules.d/90-pcscd.rules
polkit.addRule(function(action, subject) {
if (action.id == "org.debian.pcsc-lite.access_pcsc" &&
subject.isInGroup("pcscd")) {
return polkit.Result.YES;
}
});
EOF
groupadd pcscd
usermod -aG pcscd $USER
systemctl daemon-reload
systemctl restart polkit
systemctl restart pcscd
Testing on Prod (foks.app)
- For now, you can use the invite code
cczjho9r. - Contact me (max AT ne43.com) if you'd like a coupon code for a free or discounted plan.
- If you want to try a virtual host, first signup for an account on
vh.foks.app, using
foks admin web to login).
Run all Tests
Running make ci will run all tests on a mock yubikey. I do this 99% of the time. Every
so often I test against a hardware yubikey, but I recommend a throwaway yubikey for that, since it
will make destructive changes to the yubikey. You'll need Docker installed
make ci # uses a mock yubikey
make ci-yubi-destructive # uses a real yubikey; it's way slower, and it will make destructive changes to the yubikey
Running Your Own FOKS Server
The FOKS server is meant for anyone to run. There are a fair number of configuration options and steps to starting up a server, so it warrants a separate discussion.
If you are in a hurry, we have a wizard that magically stands up a new server:
/usr/bin/env bash <(curl -fsSL https://pkgs.foks.pub/server-install.sh)
If you need more control, refer to the server documentation.
MCP (Model Context Protocol)
FOKS ships MCP servers that let LLM tools (like Claude Code) interact with the encrypted KV store and team operations. Two servers are available:
foks mcp kv— read, write, list, move, remove, and stat entries in the encrypted KV store (personal or team-scoped)foks mcp team— list team members and query team memberships
Protocols
See proto-src for protocol definition files that dictate how the various parts of the FOKS
system communicate. They are split into 4 groups:
lcl: used on the client machine, to specify how the one-shot client talks to the local agent.rem: used for client-server interactioninfra: used for server-server interactionlib: shared libraries for all protocols
go install to work as expected, we include the built protocol files in the repository;
see the proto/ directory for the generated files. To rebuild the protocol files, run make proto.
Processes
When run in "production", there are several processes running on both server and client. On server:
- reg - doesn't require mTLS, useful for signing up, logging in, or other public operations
- user - requires mTLS, does many user operations, and also team operations
- probe - doesn't require mTLS, a "discovery" service that allows clients to discover where the other
- beacon - run once globally on the whole FOKS system. Allows clients to map a raw host ID
- merkle_query - doesn't require mTLS, allows clients to probe the Merkle Tree for this FOKS instance.
- merkle_batcher - internal service, runs periodically, batches up unrelated transactions into a
- merkle_builder - internal service, processes the output of merkle_batcher to update the Merkle Tree.
- merkle_signer - internal service, signs the Merkle Tree root, finalizing the process.
- queue - internal service, something like SQS that allows various FOKS services to queue up
- internal_ca - internal service, signs the mTLS certificates for various backend services, allowing
- kv-store - requires mTLS, the server-side backend to the key-value store
foks command,
which enables everything from device management, to key-value store access, to team maintenance
operations, etc. The other is git-remote-foks, a git remote helper that interfaces
with the FOKS key-value store. This process is just a symlink (or hardlink) to the foks command,
but it acts differently when called as git-remote-foks. The agent itself runs
with the foks agent subcommand. A design goal here is ultra-simple installation:
just one binary dropped onto your system (essentially statically linked, as Go likes to do)
is all you need.
Testing Philosophy / Context / MetaContext
I've found it immensely useful to run all the above processes in the same address space for the purposes of testing. Meaning, you can set breakpoints in the client code or the server code, and the debugger will just bounce-back and forth between them. These various processes still communicate via TCP or Unix domain sockets, as they would if they were running as separate processes in production.
To enable this configuration to work, it's important that there are few if any global
variables. Meaning, all global state is roughly passed through an an argument, from main()
on down. In Go, this can be slightly cumbersome, since Go in a lot of cases wants to you
pass context.Context as a first argument to any method that you might cancel based on
timeout or some external factors. This would put you in a situation of now passing
two boilerplate arguments, first a context.Context and then some sort of *GlobalState.
To slightly smooth this out, we liberally use these things called MetaContext, which have
context.Context passed by value, and a GlobalState passed by reference. Now there is
only one boilerplate argument to pass instead of two. This is the case on the server and
the client, and we sometimes have MetaContexts that are quite different internally
(since server and client state are quite different).
In both cases, we sometimes dangle methods off of MetaContext if they use fields
from both. Logging is a the most commonly-used example, since logging often includes
"context tags" that are set at the beginning of a request or logical-operation. They
are also shared via RPC so that we can debug how the server logs based on specific
client operations.
Directory Layout
The directories are, roughly from lowest-level to highest level:
- proto/ - protocol definitions
- lib/core - lowest level library, with utilities common to client and server
- lib/core/kv - low-level key-value store library
- lib/merkle/ - Merkle library
- lib/team/ - Team routines
- lib/probe - Implementations of probes for FOKS servers; useful on client and server since
- lib/chain - Implementation of chain players, again, useful on client and server
- client/libclient - Client library routines
- client/libgit - Git client library
- client/libkv - Key-value store client library
- client/libyubi - Yubikey access and test
- server/shared - Routines shared across all server processes and tools
- server/engine - High-level services
- client/agent - High-level client-side agent services
- lib/core > proto - lib/core/kv > lib/core - lib/merkle > lib/core - lib/team > lib/core - lib/chains > lib/merkle - client/libclient > {lib/*} - client/libkv > client/libclient - client/libgit > client/libkv - client/libyubi > client/libclient - (note this dependency can be removed by changing intiialization pathways in libyubi/top.go) - client/agent > client/libyubi - server/shared > {lib/*} - server/engine > server/shared - integration_tests/common > client/lib, server/shared - integration_tests/lib > integration_tests/common - integration_tests/cli > integration_tests/common, client/*
Entities, Teams, Users, Parties
Nomenclature is still in flux, but we have some rough definitions:
EntityID(seeproto/common.snowp) - A public key that serves as an identifier for a thing,
- Parties: A party is a user or a team, they can often do similar things, and sometimes
PartyID to express that the thing operating might either be
a user or a team. Note that PartyIDs, UIDs, and TeamIDs are all subclasses, more or less,
of EntityIDs.
Further Documentation
Some important docs are:
docs/passphrase.snowp- A description of our unfortunately complicated passphrase system.
docs/kv_store.md- A pretty up-to-date description of how the KV store is working,
Future Work
Lots, left blank for now.