Blog
Blog
Technical notes on Go, Rust, security and privacy engineering.
-
A concrete Go worker pool that deadlocks under load, traced back to an unbuffered results channel, with the fix and why bigger buffers only postpone the problem.
-
A walkthrough of sshd_config's KexAlgorithms, Ciphers and MACs directives: what to strip out, why, and how to verify the change without locking yourself out.
-
sync.Once does not retry after a panic in f(). It quietly marks itself done, and OnceValue does the opposite: it re-panics forever. Verified against the Go source.
-
Certificate Transparency Logs: What They Actually Prove About a TLS Certificate, and What They Don't
CT logs make TLS certificate issuance publicly auditable, but they don't validate domain control, check revocation, or stop mis-issuance. Here's the actual guarantee.
-
Create, enrol and maintain your own UEFI Secure Boot signing keys on Linux with sbctl, without turning the next reboot into a recovery exercise.
-
Rust has no async destructor, and it isn't an oversight. Why Drop::drop can't await, and the spawn/block_on/explicit-close patterns people use instead.
-
A single %v where you meant %w compiles cleanly and passes go vet, but it quietly severs the error chain that errors.Is depends on.
-
A practical guide to configuring BorgBackup with a server-enforced append-only repository, so a compromised client can add backups but never delete or rewrite them.
-
A Go service capped at 0.5 CPU in Kubernetes still spins up dozens of OS threads and gets throttled hard. Here's why GOMAXPROCS can't see the limit, and how to fix it.
-
A close look at the DPA 2018 exemptions that let organisations lawfully redact a subject access request, and the patterns of overreach that turn a redaction into a breach.
-
A step-by-step guide to embedding a minimal Dropbear SSH server in initramfs so you can type a LUKS passphrase over the network on a headless server reboot.
-
Binding to port 80 as a non-root Go process without setuid tricks, using Linux capabilities, CAP_NET_BIND_SERVICE, setcap and systemd's AmbientCapabilities.
-
bcrypt silently ignores everything past the 72nd byte of a password. Here's why the limit exists, how it produces real collisions, and how to handle it properly in Go.
-
A practical guide to generating resident FIDO2 SSH keys with ssh-keygen, setting a PIN, and recovering the credential onto a new machine with ssh-keygen -K.
-
Comparing a computed HMAC with == looks correct and compiles fine, but the comparison leaks timing information an attacker can use to forge a valid MAC byte by byte.
-
How SCM_RIGHTS lets one Unix process hand an open file descriptor to another, with a working Go example and a look at where systemd genuinely uses it.
-
A practical guide to installing and configuring fail2ban on Linux to automatically block SSH brute-force attempts, including jail.local setup, systemd journal backends and common pitfalls.
-
Binding to 127.0.0.1 feels private, but loopback TCP has no concept of "who's asking". Unix domain sockets do, and Go makes using them easy.
-
Rust drops local variables in reverse order but struct fields in declaration order. That asymmetry is a documented, real footgun for locks, file handles and temp directories.
-
A practical guide to building a minimal private CA in Go and configuring two services to authenticate each other with mutual TLS, including the identity-check gotcha most tutorials skip.
-
os.Exit terminates a Go process immediately without unwinding the stack, so no deferred function anywhere in the program runs. Here is why, and how to structure main() so it doesn't bite you.
-
Why lexical path checks cannot stop symlink escapes, and how Linux openat2 provides race-resistant directory confinement for Go services.
-
Build, test and persist a default-deny nftables firewall without breaking IPv6, flushing container rules or locking yourself out of SSH.
-
http.Get and http.DefaultClient have no timeout at all. Here's exactly which phases of a request are bounded, which aren't, and how to fix it properly.
-
AES-GCM fails catastrophically, not gracefully, if a nonce is ever reused. Here's what actually breaks, why, and how to stop it happening.
-
A practical guide to joining two LANs over WireGuard, covering key generation, subnet routing, firewall rules, MTU pitfalls and how to verify the tunnel actually works.
-
DUAA 2025 doesn't replace UK GDPR. It amends it in targeted, phased ways: new lawful bases, looser automated decision rules, a new transfer test and a mandatory complaints process.
-
A look at why sha256(sha256(secret)) is not key derivation, what HKDF's extract-and-expand construction actually buys you, and how to use it correctly in Go.
-
A practical guide to running Unbound as a validating, caching DNS-over-TLS resolver on Linux, so every device on your network gets encrypted, DNSSEC-checked lookups without per-app configuration.
-
context cancellation in Go is cooperative, not preemptive: it closes a channel and nothing more. Here's where that quietly fails, and how to actually stop work.
-
io_uring's benchmarks are real, but most of its wins come from storage I/O and syscall-bound workloads. Here's why bolting it onto a network service often buys you nothing, or a security regression.
-
A practical guide to backing up a Linux machine with restic over SFTP: repository setup, password handling, systemd timers, retention, and verifying restores.
-
A look at section 253 of the Investigatory Powers Act 2016: what a technical capability notice can legally require, why the Apple ADP dispute turned on one phrase, and why you're not meant to know these exist.
-
A technical look at why UK cookie banners with a working 'reject all' button still breach PECR, from consent-timing bugs to IAB TCF legitimate interest loopholes.
-
A Practical Guide to GPG Subkeys: Keeping Your Master Key Offline While Signing and Encrypting Daily
How to split a GPG identity into an offline certify-only master key and day-to-day sign, encrypt and auth subkeys, with backup and rotation steps.
-
TLS encrypts the contents of your connections, but packet sizes, timing, SNI fields and DNS queries still leak enough to fingerprint sites, videos and even keystrokes.
-
WAL mode lets SQLite readers and writers coexist, but writers still queue for a single lock. Go's connection pool can turn that into "database is locked" errors.
-
A practical guide to replacing sprawling authorized_keys files with a small self-hosted SSH certificate authority, covering user and host certs, principals, and revocation.
-
A look at section 87 retention notices under the Investigatory Powers Act 2016: what UK ISPs must log, the 12-month cap, entity vs events data, and why the details are secret.
-
fsync() returning zero feels like a promise the bytes are safe. Page cache, errseq_t, directory entries and volatile disk caches all conspire to make that promise weaker than it looks.
-
A practical walkthrough of enrolling a TPM2 chip into a LUKS2 volume with systemd-cryptenroll, including PCR selection, PIN protection and the kernel-update trap that locks people out.
-
A technical look at Ofcom's section 121 powers under the Online Safety Act, what client-side CSAM scanning actually involves, and why "technically feasible" may be closer to a category error than an engineering target.
-
A byte-level look at what a WebAuthn assertion actually signs: the origin, the challenge, and the RP ID hash, and why that binding breaks classic phishing-proxy kits.
-
How to set up LUKS2 disk encryption with the header stored on a separate USB key, with commands, backup steps, and an honest look at what it does and doesn't protect against.
-
tokio::select! drops the losing branch's future, and for some Tokio methods that means silently discarded bytes and desynced protocols. Here's how to spot and fix it.
-
A practical guide to choosing Argon2id memory, time and parallelism parameters in Go, with a working hash/verify implementation and the operational trade-offs nobody mentions.