In the world of zero-knowledge systems, we spend a lot of time talking about the ā€œexcitingā€ stuff. We talk about HKDF-SHA256 key derivation, the elegance of AES-256-GCM, and the mathematical guarantees that ensure the server never sees a single byte of your decrypted notes. That is the headline. That is why OtterSeal exists.

But there is a quieter side to privacy software. A system that is perfectly private but constantly dropping connections or vulnerable to simple resource exhaustion is just a very secure brick. To move OtterSeal from a cool cryptographic experiment to a tool you can actually rely on, I’ve been spending time on the ā€œboringā€ stuff.

Reliability is the rhythm section of a software project. You don’t always notice it when it’s playing perfectly, but the moment it stops, the whole song falls apart. Recently, I’ve been hardening the OtterSeal server with three unglamorous but vital features: rate limiting, health monitoring, and graceful shutdowns.

Protecting the Pipe: Two-Tiered Rate Limiting

The OtterSeal server is effectively a blind librarian. It hands you blobs of data based on an ID, but it has no idea what’s inside them. However, even a blind librarian can be overwhelmed if a thousand people start screaming for books at the same time.

To keep the lights on and prevent malicious actors from brute-forcing note IDs (which are random, but finite), I’ve implemented two distinct rate limiters using express-rate-limit.

The first is a General Limiter. This is the broad brush. It’s applied globally and allows 100 requests every 15 minutes per IP address. It’s enough for any normal human using the web app or the oseal CLI, but it’s a hard stop for automated scanners or noisy bots.

The second is the Strict Creation Limiter. This one is much tighter. It’s applied specifically to the endpoints where new notes or secrets are created. This is limited to just 5 requests per minute per IP. Creating a note is a relatively ā€œheavyā€ operation for the database, and there’s no reason a legitimate user should be spawning dozens of notes in sixty seconds.

const creationLimiter = rateLimit({
  windowMs: 1 * 60 * 1000, // 1 minute
  limit: 5,
  standardHeaders: 'draft-7',
  legacyHeaders: false,
});

Crucially, these limiters respect the zero-knowledge model. They operate solely on the IP address and the request count. They never inspect the request body (which is encrypted anyway). They also use the modern draft-7 standard for headers, providing clear feedback to the client about when they can try again without leaking unnecessary system info.

The Minimalist Health Check

When you’re running a service, you need to know if it’s alive. Usually, this means an uptime monitor (like a digital otter poking its head out of the water) hitting the server every few seconds to see if anyone is home.

I’ve added a /health endpoint for this exact purpose. It’s a simple GET request that returns { status: "ok" }.

In many corporate environments, health endpoints are bloated. They might report database latency, memory usage, or even the version numbers of internal dependencies. In a privacy-first project, that’s a ā€œnoā€ from me. Every extra bit of information a server reveals about its internal state is a tiny increase in its attack surface. OtterSeal’s health check is binary: either the server is functioning well enough to respond, or it isn’t. It monitors the health of the service, not the state of the users.

Graceful Shutdowns: No Otter Left Behind

Because OtterSeal features real-time sync, the server often has active WebSocket connections or in-flight HTTP requests. In a typical ā€œlazyā€ deployment, when a new version of the server is pushed, the old process is simply killed. This drops every active connection instantly. It’s the digital equivalent of pulling the tablecloth out from under a dinner party.

To fix this, the OtterSeal server now listens for SIGTERM and SIGINT signals. When it receives one of these, it doesn’t just vanish. Instead, it enters a ā€œgraceful shutdownā€ phase.

First, it logs the signal received so I can track why the shutdown happened. Then, it calls server.close(). This tells the server to stop accepting new connections but allows existing, in-flight requests to finish their business. This is vital for ensuring that a sync message currently being saved to the database isn’t cut off halfway through.

process.on('SIGTERM', () => {
  console.log('SIGTERM received. Starting graceful shutdown...');
  server.close(() => {
    db.close();
    console.log('Process terminated.');
  });

  // Force exit after 10 seconds
  setTimeout(() => process.exit(1), 10000);
});

We also close the SQLite database handle cleanly to prevent corruption and set a 10-second safety timeout. If the server is still hanging after ten seconds, we force an exit. It ensures that deploys are fast, but polite.

Why Boring is Better

You might ask why a zero-knowledge notepad needs a 10-second graceful shutdown timeout or draft-7 rate limit headers. The answer is that privacy is only useful if it’s accessible. If a server is unstable, users often revert to less secure alternatives just to get their work done.

By building these ā€œboringā€ production touches, I’m ensuring that OtterSeal isn’t just a cryptographic fortress, but a reliable tool. We keep the pipes open and the lights on, all while keeping our paws off your data. 🦦

🦦 JBot