Day 36: Deployment Hardening
Yesterday I shipped VPS deployment guides. Today I learned what "works on a VPS" actually means.
The Localhost Assumptions
When you develop locally, everything is HTTP, cookies don't need the Secure flag, HSTS doesn't exist, and crypto.randomUUID() just works. Deploy to a VPS with plain HTTP (before SSL is set up) and all of that falls apart.
The fix list:
- Secure cookies on HTTP. Better Auth sets Secure cookies by default. On a VPS without SSL, the browser silently drops them. Login appears to work but the session isn't persisted. Fixed: detect the protocol and only set Secure when HTTPS is actually configured.
- HSTS on HTTP. Same story. Sending a Strict-Transport-Security header over plain HTTP is pointless and confusing. Now it's conditional.
- crypto.randomUUID. Only available in secure contexts (HTTPS or localhost). On plain HTTP, it throws. Added a fallback.
- Origin trust. Better Auth validates request origins. On a self-hosted setup behind a reverse proxy, the origin can be anything. Relaxed this for self-hosted deployments.
Four separate bugs, all caused by the same root assumption: the app will always run over HTTPS. Locally that's true (localhost is a secure context). In production, you set up SSL after the first deploy, not before.
OpenClaw Supervision
The OpenClaw gateway — the runtime that actually executes agents — needs to restart when config changes. Telegram pairing changes the config. So does adding an agent, changing permissions, or updating a provider key.
The problem: restarts weren't stable. Sometimes the gateway didn't come back cleanly. The auto-device-approval loop would die and not recover. Config writes would race with config reads.
The fix was a proper supervision setup: health check loops, debounced config writes, and a double-check that devices get approved after every restart. Not glamorous work. But a gateway that restarts cleanly is the difference between "the agent stopped responding" and "I didn't notice anything."
Telegram Stabilization
Telegram integration is getting closer but keeps surfacing edge cases. Today's changes: replaced the config.patch approach (too fragile) with file-based config for Telegram. Added an admin "Remove Telegram" action. Fixed the OpenClaw restart loop that happened when Telegram config was written during startup.
The pairing flow now uses OpenClaw's native allow-from store, which is more reliable than the custom solution I had before. Sometimes the best code is the code you delete.
The Security Tradeoff That Keeps Me Up
Here's what gives me stomach aches: every one of these fixes makes the initial setup easier by weakening a security default. Allowing plain HTTP cookies. Trusting all origins. Falling back from secure random generation. Each one individually makes sense — you can't set up SSL before you've even deployed the app. But stacked together, they create a window where Pinchy runs in a state I'm not comfortable with.
So I created two issues to address this properly:
Insecure Mode Warning Banner — a persistent, non-dismissable warning at the top of every page when running without HTTPS. Not a modal that blocks usage, but a constant reminder: "You're running without encryption. Set up HTTPS." It auto-disappears once HTTPS is configured. Inspired by Home Assistant, which does the same thing. And once HTTPS is active, Pinchy automatically hardens itself — Secure cookies, HSTS, the works.
tmpfs for Secrets — the OpenClaw config file currently sits in a Docker volume on disk, with API keys in plaintext. Switching to a tmpfs mount means credentials only exist in RAM, never on disk. Pinchy regenerates the full config from its encrypted database on every startup, so persistent storage isn't needed.
The goal: make the first five minutes easy, then nudge hard toward security. Not security-by-default (that blocks adoption) and not security-by-afterthought (that causes breaches). Security-by-graduation.
Day 36
Twenty commits. Zero new features visible to users. All of them making the difference between a demo that works and a product that works. This is the unglamorous part of shipping software: the part where you make it survive contact with the real world — and then close the security gaps you opened to get there.