Skip to content

cipherstash/bunnynet-vault

Repository files navigation

CipherStash + Bunny Encrypted Vault

A proof-of-concept API that stores and retrieves sensitive data with searchable encryption using CipherStash Protect and a Bunny Edge Database (LibSQL/SQLite). Designed to run as a container on Bunny's Magic Container Deployment.

All data is encrypted before it reaches the database. The encryption keys never leave the application layer, and the database only ever stores ciphertext. Despite full encryption in use, the data remains queryable through CipherStash's searchable encryption primitives.

Architecture

                        ┌──────────────────────────────────────────────┐
                        │     Bunny Magic Container (Edge)             │
                        │                                              │
  Client ──────────────►│  Elysia (HTTP)                               │
  X-API-Key             │       │                                      │
  X-Key / JSON body     │       ▼                                      │
                        │  CipherStash Protect                         │
                        │   • encrypt/decrypt values                   │
                        │   • generate HMAC search terms               │
                        │       │                                      │
                        │       ▼                                      │
                        │  LibSQL Client ──────► Bunny Database        │
                        │                        (SQLite on edge)      │
                        │                        Stores only           │
                        │                        ciphertext + HMACs    │
                        └──────────────────────────────────────────────┘

Runtime: Bun Web framework: Elysia Encryption: @cipherstash/protect v10 Database: Bunny Database via @libsql/client (LibSQL, SQLite-compatible)

How Searchable Encryption Works

CipherStash Protect provides application-level encryption with the ability to query encrypted data without decrypting it. This is achieved through two mechanisms:

Column types

The schema defines two column types on the sensitive_data table:

const sensitiveData = csTable('sensitive_data', {
  key: csColumn('key').equality(),  // encrypted + searchable via equality
  value: csColumn('value'),          // encrypted only, not searchable
})
  • .equality() columns generate an HMAC-based search term alongside the ciphertext. The HMAC is a deterministic, one-way hash derived from the plaintext — it cannot be reversed to recover the original value, but the same input always produces the same HMAC. This allows exact-match lookups (WHERE key = ?) without exposing the plaintext to the database.
  • Standard columns are encrypted with no search index. They can only be read after decryption in the application layer.

Write path (POST)

  1. Plaintext key and value arrive in the request body.
  2. protectClient.encryptModel() encrypts both fields. For the key field, it also generates an HMAC search term (.hm).
  3. The HMAC is stored in the key column of the database. The encrypted value object is JSON-serialized and stored in the value column.
  4. The database never sees plaintext.

Read path (GET)

  1. The plaintext key arrives in the X-Key header.
  2. protectClient.createSearchTerms() generates the same HMAC for the key.
  3. The HMAC is used in a SELECT ... WHERE key = ? query.
  4. The encrypted value returned from the database is parsed and passed to protectClient.decrypt().
  5. The decrypted plaintext is returned to the client.

What the database sees

Column Contents
key HMAC string (not reversible, deterministic)
value JSON-encoded ciphertext blob

An attacker with full database access sees only HMACs and ciphertext — no plaintext, no encryption keys.

API Reference

All endpoints require authentication via the X-API-Key header.

GET /sensitive-data

Retrieve a decrypted value by its key.

Headers:

Header Required Description
X-API-Key Yes API key for authentication
X-Key Yes Plaintext key to look up

Response codes:

Status Meaning
200 Decrypted value returned in response body
400 Missing X-Key header
401 Invalid or missing API key
404 No entry found for the given key
500 Encryption or database failure

Example:

curl -H "X-API-Key: $API_KEY" \
     -H "X-Key: my-secret-key" \
     http://localhost:3000/sensitive-data

POST /sensitive-data

Store an encrypted key-value pair. If the key already exists, the value is updated.

Headers:

Header Required Description
X-API-Key Yes API key for authentication

Body (JSON):

{
  "key": "my-secret-key",
  "value": "the sensitive data to encrypt and store"
}

Response codes:

Status Meaning
200 Data created or updated
400 Missing key or value in body
401 Invalid or missing API key
500 Encryption or database failure

Example:

curl -X POST \
     -H "X-API-Key: $API_KEY" \
     -H "Content-Type: application/json" \
     -d '{"key": "db-password", "value": "hunter2"}' \
     http://localhost:3000/sensitive-data

Environment Variables

Create a .env.local file in the project root:

Variable Description
API_KEY Shared secret for authenticating API requests
BUNNY_DATABASE_URL LibSQL connection URL for Bunny Database
BUNNY_DATABASE_AUTH_TOKEN JWT auth token for Bunny Database (read-write)
CS_WORKSPACE_CRN CipherStash workspace CRN
CS_CLIENT_ID CipherStash client UUID
CS_CLIENT_KEY CipherStash client key (hex-encoded)
CS_CLIENT_ACCESS_KEY CipherStash API access key

You will need:

  • A CipherStash account and workspace with a configured dataset/keyset.
  • A Bunny Database instance. Create a sensitive_data table with key (TEXT) and value (TEXT) columns.

Running Locally

Prerequisites

  • Bun v1.x
  • A configured .env.local file

Install and run

bun install
bun --watch index.ts

The server starts on http://localhost:3000.

Running with Docker

Build and run

./run.sh

Or manually:

docker build -t cipherstash-bunny-vault:local .
docker run --env-file .env.local -p 3000:3000 cipherstash-bunny-vault:local

The Dockerfile uses a multi-stage build on the official oven/bun:1 image. It installs production dependencies in an isolated stage and copies only the necessary files (index.ts, tsconfig.json, package.json) into the final image.

Deploying to Bunny Magic Containers

Push the container image to a registry accessible by Bunny, then deploy it via Bunny's Magic Container platform. The container listens on port 3000 and requires all environment variables listed above to be set in the container configuration.

Because the application connects to Bunny Database over HTTPS (LibSQL over HTTP), there is no requirement for the container to be co-located with the database — it works from any Bunny edge location.

Project Structure

├── index.ts          # Entire application: encryption setup, DB client, API routes
├── package.json      # Dependencies and scripts
├── tsconfig.json     # TypeScript configuration
├── Dockerfile        # Multi-stage container build
├── run.sh            # Local Docker build + run script
├── .env.local        # Environment variables (not committed)
└── .dockerignore     # Excludes .env and node_modules from build context

Security Considerations

This is a proof-of-concept. The following should be addressed before production use:

  • Authentication: The current API key check is a simple string comparison via header. Replace with a proper auth mechanism (JWT, mTLS, OAuth2, etc.).
  • Rate limiting: No rate limiting is implemented. Add rate limiting to prevent brute-force attacks on the API key or key enumeration.
  • Input validation: Beyond basic presence checks, there is no input sanitization or length limiting.
  • Transport security: Ensure TLS termination is handled (Bunny's edge handles this for Magic Containers).
  • Key management: CipherStash credentials and the API key are passed as environment variables. Use a secrets manager in production, like Stash.

Tech Stack

Layer Technology
Runtime Bun
Web Framework Elysia
Database Bunny Database (LibSQL/SQLite)
Encryption CipherStash Protect
Container Docker (oven/bun:1)
Language TypeScript

About

Encryption in use SQLite sensitive data store powered by CipherStash searchable encryption. Deployable with Bunny.net.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors