galata-vault
End-to-end-encrypted secrets and config documents for software, with no accounts. A project is a key you generate. Everything is encrypted and signed on your machine, and the server (or an in-process backend) stores only ciphertext it cannot read, under signatures it cannot forge.
galata-vault is a Rust library first: the galata-vault SDK. Built on it are
the gv command line, a Python package, a server that cannot decrypt, and a
metadata-only MCP server for AI agents.
It is a general-purpose tool, and it is also the configuration and secret store of Galata, a low-latency algorithmic trading framework in Rust.
galata-vault has not been independently audited. Its formats may change before 1.0. Use it at your own risk.
Stability. The crates are 0.x: a 0.y release may break the API, a
0.y.z release does not, and CHANGELOG.md lists every
change. The wire and file formats are specified separately, in
docs/spec/, and carry their own version markers: data
written under a marker stays readable by every later release that reads it,
and a format that changes gets a new marker. The minimum supported Rust
version is 1.98, and raising it is a minor release. Security fixes cover the
latest 0.y release (SECURITY.md).
Install
cargo add galata-vault # the Rust SDK
pip install galata-vault # the Python package, with gv
curl --proto '=https' --tlsv1.2 -LsSf \
https://github.com/sercanatalik/galata-vault/releases/latest/download/gv-installer.sh | sh
gv is a binary, not a published crate, so it comes from that installer, the
Python package, or cargo install --git https://github.com/sercanatalik/galata-vault gv.
Each GitHub release
also carries a PowerShell installer for gv, and shell installers for
gv-server and gv-mcp. Every binary has a GitHub artifact attestation:
gh attestation verify <file> --repo sercanatalik/galata-vault.
The SDK
#![allow(unused)]
fn main() {
let vault = galata_vault::Vault::from_env()?; // GV_SERVER, and GV_TOKEN or GV_TOKEN_FILE
let url = vault.secret("DATABASE_URL")?; // .expose() gives the exact bytes written
let app: toml::Table = vault.config("app")?.deserialize()?;
Ok::<(), Box<dyn std::error::Error>>(())
}
Vault, the token client. Read, list, write and delete secrets and configs as the token’s scope allows; fetch history and status; verify the audit chain;refresh()after a rotation.owner, the owner API. Everythinggvdoes as a project’s owner: projects, environments, rediscovery from a key, recovery and delegation kits, minting and revoking tokens, rotation, rekey (with resume and abort), and audit verification against a stored head. It prompts for nothing and prints nothing.- Seams. Requests go through a
Transport(ClientBuilderbuilds the HTTP one), keys through aKeyStoreyou supply, and local bookkeeping through aStateStore. Nothing is process-global, and the SDK leaves its host alone: no keychain, no process limits, nothing on stdout or stderr. embedded, no server. With theembeddedfeature,galata_vault::embedded::open(dir)runs the server’s rules in-process over a data directory: no socket, no HTTP, no async runtime.
Errors are one #[non_exhaustive] enum with stable codes, and integrity
failures are their own variant. See the SDK’s README
and its examples.
The command line
gv-server local # or use a server you trust
gv init acme --server http://127.0.0.1:8750 # writes acme-recovery.gvkit; keep it safe
gv env add acme/prod
printf %s "$DATABASE_URL" | gv set DATABASE_URL --env acme/prod
gv run --env acme/prod -- ./deploy # secrets arrive as environment variables
gv token mint --scope read --env acme/prod # for CI: GV_TOKEN + GV_SERVER
gv config set app --format toml --env acme/prod < app.toml
gv ui # the vault in your browser, on loopback
gv ui
gv ui serves the vault to a browser tab from the gv process itself, on
loopback, behind a single-use link. No key or token ever reaches the page: it
gets names and metadata, and a value only for the one reveal that asked for
it. Minting, revoking, rotating and deleting wait for y in the terminal
that started it, with a four-digit code shown on both sides. What it does and
what it refuses to do is docs/local-ui.md.
The concepts behind it (the key tree, delegation and rekey, tokens and what
revocation means, config documents, durability, the local UI, the MCP
server) are in docs/guide.md. The cli module is also a
library: another binary can carry the gv command tree under its own name
(galata_vault::cli).
Python
import galata_vault
vault = galata_vault.Vault.from_env() # GV_SERVER, and GV_TOKEN or GV_TOKEN_FILE
url = vault.get("DATABASE_URL")
app = vault.get_config("app").parse() # a dict
The wheel runs the same Rust code as gv, and bundles gv itself. See
crates/gv-py/README.md.
The server
The server stores ciphertext (age v1), signed records, HMAC name indexes with encrypted names, public keys, owner-signed descriptors and bundles, and an audit hash chain. It never receives a plaintext name or value, a project or environment path, or any key that decrypts; the server binary links no decryption code, and a CI guard fails the build if it ever does. Every client pins a vault id before trusting anything, so a server cannot substitute a key, forge or move a record, or rewrite the tree unnoticed. docs/threat-model.md says what each actor can and cannot do.
gv-server local needs no configuration and listens on loopback. For other
machines you trust, run a configured gv-server behind a TLS-terminating
proxy: deploy/README.md.
One crate, and what is in it
galata-vault is the only published crate. What used to be ten crates are
its modules, each behind the feature that needs it, so a build gets the code
it asked for and no more.
| Module | Feature | Role |
|---|---|---|
proto | always | formats, API types, descriptors, audit chain, signature verification |
keys | sdk | key tree, owner, token and writer keys, bundles, name key |
seal | sdk | age envelopes, signed records, rotation; the only value decryption |
client | sdk | the client transport and request signing; no value decryption |
| the root | sdk | the token client, the owner API, embedded |
backend | storage | SQLite storage and journal records |
server_core | server-core | the server’s rules, with no HTTP, async or decryption code |
server | server | the HTTP API, an axum shell over the core |
cli | cli, ui | gv, and a library another binary can brand and extend |
mcp | mcp | the metadata-only MCP server |
The split is load-bearing, not cosmetic: server does not enable sdk, so a
server build links no value or name crypto at all, and the SDK’s default
build links no database, HTTP server or async runtime.
scripts/check-linkage.sh proves both from the
dependency graph, and checks that the serving modules name no client-side
crypto.
The three binaries live in unpublished crates that wrap those modules
(crates/gv, crates/gv-server, crates/gv-mcp), which is how each keeps
its own release target list. gv-py is the Python package (PyPI:
galata-vault); gv-adversary and gv-conformance are the malicious-server
harness and the conformance suite. ARCHITECTURE.md maps
it all.
Documentation
- docs/spec/: the protocol, with test vectors in
testdata/vectors/v1/and a conformance suite - docs/threat-model.md and ARCHITECTURE.md
- docs/guide.md: concepts and the command line
- SECURITY.md, CHANGELOG.md, RELEASING.md
All of it is also on the documentation site,
https://sercanatalik.github.io/galata-vault/, built from this repository by
.github/workflows/pages.yml.
Part of Galata
Galata is a Rust framework for low-latency algorithmic trading. It covers multi-venue market data capture, signal generation, deterministic portfolio risk controls, and agentic strategy execution driven by a fine-tuned decision model that turns market signals into calibrated probabilities. galata-vault is its single source of configuration and secrets.
flowchart LR
VAULT["gv-server local<br/>loopback :8750<br/>ciphertext only"]
CAP["galata-datawatch<br/>capture, one per venue"]
NATS{{"NATS"}}
TOWER["galata-tower"]
VAULT -- "config document<br/>+ its venue's credentials" --> CAP
VAULT -- "broker passwords" --> NATS
VAULT -- "reader password" --> TOWER
- One source, never two. Capture fetches its configuration document from the vault once, at boot, and refuses to start if the vault is unreachable. There is no cached last-known-good copy to disagree with it.
- Each service holds a token for its own secrets only. A capture process can read its venue’s credentials and nothing else. Credentials that must be cryptographically isolated from each other, such as two venues’ keys, go in child vaults, since an allow-list is server policy rather than a second encryption boundary.
- Local-first. The reference deployment runs
gv-server localon loopback, so the vault’s availability is the machine’s, not a network’s.
Galata consumes this repository only as the published galata-vault crate,
never as a path or git dependency. How it is wired is described in
galata-datawatch.
Roadmap
| Item | Status |
|---|---|
| One published crate in place of ten, with the server/SDK split enforced by features | done in 0.4.0 |
| Refuse at startup a database whose tables do not match its schema version | done, in the next release |
Deprecate the nine crates that 0.4.0 folded into galata-vault (still on crates.io at 0.2.0) | next |
Homebrew tap for gv, gv-server and gv-mcp | planned; see RELEASING.md |
1.0: an external security review published under audit/ with every high-severity finding fixed, the wire and file formats declared frozen in docs/spec/, and OpenSSF Best Practices “passing” | before 1.0 |
For Galata, the vault next holds the Robinhood venue credentials (a provider RPC key for Robinhood Chain and an Ed25519 key for the Crypto Trading API), each in its own child vault.
Contributing and licence
scripts/check-all.sh runs everything CI runs. Contributions are accepted
under the Developer Certificate of Origin: see
CONTRIBUTING.md and the Code of Conduct.
Licensed under the MIT licence.