Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

galata-vault

crates.io docs.rs PyPI CI MIT

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. Everything gv does 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 (ClientBuilder builds the HTTP one), keys through a KeyStore you supply, and local bookkeeping through a StateStore. 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 the embedded feature, 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.

ModuleFeatureRole
protoalwaysformats, API types, descriptors, audit chain, signature verification
keyssdkkey tree, owner, token and writer keys, bundles, name key
sealsdkage envelopes, signed records, rotation; the only value decryption
clientsdkthe client transport and request signing; no value decryption
the rootsdkthe token client, the owner API, embedded
backendstorageSQLite storage and journal records
server_coreserver-corethe server’s rules, with no HTTP, async or decryption code
serverserverthe HTTP API, an axum shell over the core
clicli, uigv, and a library another binary can brand and extend
mcpmcpthe 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

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 local on 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

ItemStatus
One published crate in place of ten, with the server/SDK split enforced by featuresdone in 0.4.0
Refuse at startup a database whose tables do not match its schema versiondone, 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-mcpplanned; 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.