Ciph

Hono

Server-side encryption middleware for Hono. Automatically decrypts incoming requests and encrypts outgoing responses — zero changes to your handler logic.

Quick Start

Install the package:

bun add @ciph/hono @ciph/core hono

Mount the middleware in your Hono app:

import { Hono } from "hono"
import { ciph } from "@ciph/hono"

const app = new Hono()

// Mount once at app root — all routes below are encrypted
app.use("*", ciph({
  secret: process.env.CIPH_SECRET!,
}))

// Your handlers work unchanged
app.post("/api/users", async (c) => {
  // Request body is auto-decrypted
  const data = await c.req.json()
  
  // Store user
  const user = await db.users.create(data)
  
  // Response body is auto-encrypted
  return c.json(user)
})

The shared secret must match your frontend CIPH_SECRET exactly. Store it in environment variables, never in code.

Configuration

Full Config Options

interface CiphConfig {
  /** Shared secret (required). Same as CIPH_SECRET on frontend. */
  secret: string

  /** Routes to skip encryption. Default: ["/health", "/ciph", "/ciph/*"] */
  excludeRoutes?: string[]

  /** Validate IP address in fingerprint. Default: true */
  strictFingerprint?: boolean

  /** Max payload size in bytes. Default: 10 MB */
  maxPayloadSize?: number

  /** Allow unencrypted requests (migration mode). Default: false */
  allowUnencrypted?: boolean
}

Example: Development Setup

app.use("*", ciph({
  secret: process.env.CIPH_SECRET!,
  // Disable IP validation behind proxy
  strictFingerprint: false,
  // Increase payload limit for file uploads
  maxPayloadSize: 50 * 1024 * 1024, // 50 MB
}))

Excluding Routes

Skip encryption for specific routes using exact matches or glob patterns:

ciph({
  secret: process.env.CIPH_SECRET!,
  excludeRoutes: [
    "/health",        // Health checks
    "/status",        // Status endpoints
    "/public/*",      // Public resources
    "/webhooks/*",    // Third-party webhooks
  ]
})

Excluded routes must not process sensitive data. They bypass encryption entirely.

Per-Route Exclusion

Exclude a single route from encryption:

import { ciphExclude } from "@ciph/hono"

app.post("/webhooks/stripe", ciphExclude(), async (c) => {
  // This handler receives unencrypted body
  const event = await c.req.json()
  await processStripeWebhook(event)
  return c.json({ ok: true })
})

DevTools Inspector

Mount the backend inspector to debug encrypted traffic in real-time:

import { ciphDevServer } from "@ciph/devtools-server"

app.route("/ciph", ciphDevServer({
  secret: process.env.CIPH_SECRET!,
}))

Then open http://localhost:3000/ciph in your browser to inspect all encrypted requests/responses.

DevTools are automatically disabled in production regardless of configuration.

How It Works

Request Flow (Decryption)

  1. Read X-Fingerprint header from client
  2. Decrypt fingerprint using shared secret
  3. Validate fingerprint — check IP & user agent match
  4. Derive AES key from secret + fingerprint
  5. Decrypt body using key
  6. Inject plain body into c.req for handler

Response Flow (Encryption)

  1. Intercept response before sending
  2. Serialize response body to JSON/Buffer
  3. Encrypt body using same derived key
  4. Send ciphertext with Content-Type: text/plain

On this page