cryptofort
v0.2.0
Published
Encrypted-at-rest credential vault with pluggable DB backends and an MCP server for agents.
Maintainers
Readme
Why CryptoFort
Secrets sprawl across .env files, shell history, and plaintext columns — and agents have no safe, structured way to ask for them. CryptoFort seals every secret with authenticated encryption, keeps the key out of the database entirely, and hands agents a narrow MCP interface that returns metadata by default and plaintext only on an explicit get.
Install
npm install cryptofort
# plus the driver for your backend:
npm install @supabase/supabase-js # or: better-sqlite3 | postgresCryptoFort is also published to GitHub Packages as @bradley-t-t/cryptofort. Point the @bradley-t-t scope at the GitHub registry and authenticate with a token that has read:packages — GitHub Packages requires auth even for public packages:
@bradley-t-t:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}npm install @bradley-t-t/cryptofortLibrary usage
import { Vault, Crypto, SqliteAdapter } from 'cryptofort';
const adapter = new SqliteAdapter('vault.db');
await adapter.init();
const vault = new Vault({
adapter,
crypto: new Crypto({ key: process.env.CRYPTOFORT_MASTER_KEY! }),
});
await vault.put({
name: 'stripe-secret-key',
secret: 'sk_live_…',
provider: 'stripe',
tags: ['payments'],
});
await vault.search('stripe'); // metadata only — never the secret
await vault.get('stripe-secret-key'); // the decrypted secretGenerate a master key (base64, 32 bytes):
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"…or from the library with import { generateKey } from 'cryptofort'.
MCP server
Point any MCP client at the cryptofort-mcp binary:
{
"mcpServers": {
"cryptofort": {
"command": "cryptofort-mcp",
"env": {
"CRYPTOFORT_ADAPTER": "supabase",
"SUPABASE_URL": "https://<ref>.supabase.co",
"SUPABASE_SERVICE_ROLE_KEY": "<service-role-key>",
"CRYPTOFORT_MASTER_KEY": "<base64-32-bytes>"
}
}
}
}The server is read-only by default. Add "args": ["--allow-write"] to expose credential_put.
Tools
| Tool | Access | Description |
| :------------------ | :----- | :-------------------------------------------------------------------- |
| credential_search | read | Search by name, description, provider, or tag. Returns metadata only. |
| credential_get | read | Decrypt and return a single secret by exact name. |
| credential_list | read | List credential metadata in a namespace, optionally filtered by tag. |
| credential_put | write | Create or update a credential. Requires --allow-write. |
Environment
| Variable | Required | Purpose |
| :------------------------------------------- | :------- | :---------------------------------------------------------- |
| CRYPTOFORT_MASTER_KEY | always | Base64, 32-byte AES-256 key. Never written to the database. |
| CRYPTOFORT_ADAPTER | — | supabase (default), sqlite, or postgres. |
| CRYPTOFORT_KEY_ID | — | Key identifier for rotation. Defaults to default. |
| SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY | Supabase | Connection for the Supabase adapter. |
| CRYPTOFORT_SUPABASE_DB_URL | — | Direct Postgres URL, used only to auto-create the schema. |
| CRYPTOFORT_POSTGRES_URL | Postgres | Connection string for the Postgres adapter. |
| CRYPTOFORT_SQLITE_PATH | — | SQLite file path. Defaults to cryptofort.db. |
Backends
| Backend | Driver | Best for |
| :----------- | :---------------------- | :-------------------------------------------------------- |
| Supabase | @supabase/supabase-js | Hosted, shared across agents, service-role access. |
| Postgres | postgres | Dropping the vault into existing Postgres infrastructure. |
| SQLite | better-sqlite3 | Local, single-process, zero-infrastructure use. |
How it works
- Only the secret is ciphertext.
name,description,provider, andtagsstay plaintext, so search and listing work without ever decrypting. - Each secret is sealed with AES-256-GCM — authenticated encryption, so any tampering is caught on read.
- The master key never touches the database. It lives only in
CRYPTOFORT_MASTER_KEY; a stolen dump reveals nothing without it. - The MCP server refuses writes unless started with
--allow-write, so an agent can look secrets up but cannot quietly rewrite the vault.
Schema
CryptoFort creates its schema automatically on first connect — one table, one ciphertext column, the rest plaintext metadata for search. There is no migration to run by hand.
- SQLite and Postgres:
adapter.init()issuescreate table if not exists(plus indexes), so pointing CryptoFort at an empty database is enough. - Supabase: the client speaks PostgREST, which cannot run DDL.
init()probes for the table and, when it is missing, creates it through a direct Postgres connection given inCRYPTOFORT_SUPABASE_DB_URL. If the table already exists the probe is a no-op; if it is missing and no DB URL is set,init()fails with a clear message instead of silently.
The canonical column definitions live in src/adapters/schema.ts.
Development
npm run build # bundle with tsup
npm test # run the vitest suite
npm run typecheck # tsc --noEmitLicense
Released under the MIT License.
