Your application never receives the credential. It asks keeplane to make a call; it gets back a response. The access token, refresh token, API key and client secret never leave the boundary — and there is no endpoint, anywhere, that reveals them.
keeplane is a self-hosted control plane for the third-party API credentials belonging to your customers. It runs the OAuth / API-key authorization flow on a page you brand, stores credentials under your own root of trust, refreshes and rotates and revokes them correctly, proxies authenticated requests to providers, enforces policy on every call, and records a tamper-evident audit of every use.
Go · Postgres · one binary · Apache-2.0. Nothing phones home. It runs air-gapped.
Status: pre-v0.1, under construction. The skeleton, configuration, role dispatch, migrations and CI gates are in place; the credential core lands next. See KEEPLANE-PLAN.md for the build order.
mkdir -p deploy/compose/secrets && head -c 32 /dev/urandom > deploy/compose/secrets/root.key
chmod 600 deploy/compose/secrets/root.key
docker compose -f deploy/compose/docker-compose.yml up --build
curl localhost:8080/healthzOr without Docker:
make build
./bin/keeplane migrate --config deploy/compose/keeplane.yaml up
./bin/keeplane serve --config deploy/compose/keeplane.yaml --role=allWhat the process does is chosen at start-up. Same image, same config schema, same version — only
--role differs.
keeplane serve --role=all # T1 · quickstart, small teams
keeplane serve --role=api,proxy,connect # T2 · serving replicas
keeplane serve --role=worker # T2 · background sweeps, leader-elected
keeplane serve --role=credential-core # T3 · isolated, holds the KMS unwrap identityRoles coordinate only through Postgres. They do not call each other over the network. The single
deliberate exception is credential-core in the zoned topology, which exposes exactly three methods
(Unwrap, Renew, Revoke) over mTLS on a private listener — opt-in, off by default.
Not a connector catalog · not a unified data model · not a workflow engine · not an identity provider · not an observability backend · not a secrets manager. See NON-GOALS.md — declining the connector catalog is what keeps this maintainable by a small team.
- A database dump yields zero plaintext credentials. Root key → per-tenant KEK → per-credential
DEK, AES-256-GCM, with AAD binding to
tenant ‖ connection ‖ version ‖ kek_version ‖ record_type, so a ciphertext row copied into another connection simply fails to decrypt. - The caller never names a host. Destinations come from the auth profile. DNS is resolved once and the IP pinned; private ranges are denied; redirects are not followed; the credential is unwrapped only after the dial check passes.
- Profiles are data, never code. Field substitution only — no templating engine, no scripting hook. An expression in a profile is remote code execution with vault access.
- No reveal-credential endpoint. Not masked, not gated: the code path does not exist. Credential material leaves only through an offline, two-person CLI.
- No second copy. One credential store. No cache, no Redis, no
map[string]string"for latency".
Read THREAT-MODEL.md before you deploy it, and SECURITY.md to report something.
make test # unit tests — no Docker, no network
make gates # every CI gate that runs locally
make conformance # the public security test suitemake test-integration needs Postgres (TEST_DATABASE_URL, or testcontainers). make test-live
needs real provider credentials and is maintainer-only.
Apache-2.0. Every security mechanism — credential core, crypto, proxy, policy, the whole audit path, provider profiles, CLI, SDK, conformance suite and Kubernetes deployment — stays open, forever. See OPEN-CORE-PROMISE.md.