I built Birdcage because I wanted to access my claw from anywhere without handing auth to a third party or punching holes in my home network.
Birdcage sits on a VPS and handles auth + reverse proxying. The connection back home runs over WireGuard — the VPS only sees opaque packets. The agent manages its own WireGuard interface, discovers endpoints via STUN, and falls back to relay when direct UDP fails.
Auth uses PBKDF2 with adaptive proof-of-work on brute force, JWT dual-token pattern with sliding session expiry, and WireGuard key rotation on a configurable interval. Single binary, pure Go, no CGO.
On the login... when failing either via user lookup, or password mismatch, I'll usually put a random 500-2500ms (or more) delay before logging and sending the response to handle timing attacks.
You can try a db transaction against a lock table for IP and Username as part of multi-request mitigation during any given request. CF offers Durable objects that can be used for this purpose. Return "too many requests" error if a request is sent before another is finished... this will slow things down.
On the minimum passphrase, there are some libraries you can use to get the printable character length... note: you should always normalize (NFC or NFKC) before doing any hashing or validation.
function getPrintableLength(str) {
// Use Intl.Segmenter for accurate, user-perceived character count
const segmenter = new Intl.Segmenter("en-US", { granularity: "grapheme" });
return [...segmenter.segment(str)].length;
}
Personally, I usually just transparently set a max of 1024 bytes, I don't display a hint for it at runtime, only an error on submit though... if someone exceeds that, they deserve the generic error I return.
Email validation can be a bit rough, depending on how permissive or restricting you want to be. If you're willing to wait for a DNS/MX check on the domain, that's a good place to start. You most likely don't want less than 5 characters or more than 100.
Pretty sure all those are covered, upon more careful review. PRs open!
Edit: The create account I hadn't thought of for the email enum. Thanks!
Edit 2: Fixed up two schema issues identified and the last mitigated already via call: await passwords.rejectPasswordWithConstantTime(validatedData.password)
Briar is a fantastic tool. A little rough around the edges but the idea of being able to use Bluetooth to communicate with someone in range versus sending data into a router has appeal beyond offline communication. I'd love to see Codeberg on here in the future. They're doing a bang up job.
I commonly rely on Goodreads for book reviews before deciding if it's worth spending the time to read. Any chance of integrating functionality like this with z-lib.org?
Birdcage sits on a VPS and handles auth + reverse proxying. The connection back home runs over WireGuard — the VPS only sees opaque packets. The agent manages its own WireGuard interface, discovers endpoints via STUN, and falls back to relay when direct UDP fails.
Auth uses PBKDF2 with adaptive proof-of-work on brute force, JWT dual-token pattern with sliding session expiry, and WireGuard key rotation on a configurable interval. Single binary, pure Go, no CGO.