Web HR Monitor: Heart Rate and HRV in the Browser

I’ve been reading about HRV (Heart Rate Variability) for a while and wanted a way to record it on my computer and actually understand the underlying calculations, rather than just consume a number from a proprietary app. I also wanted an excuse to finally play with the Web Bluetooth API, which lets a browser connect directly to Bluetooth LE devices with no native app or server involved. The result is web-hr-monitor, a small app that runs entirely in the browser — no backend, no install.

The live version is at guyru.github.io/web-hr-monitor. Open it in Chrome or Edge, click Connect to HR Monitor, pair your BLE heart rate monitor, and you get a live readout along with running average, maximum, minimum, and a heart rate distribution histogram for the session.

HRV Analysis

The more interesting part is the HRV analysis. After connecting, you can run a 2-minute test that collects RR intervals — the time between successive heartbeats — and computes two standard metrics:

  • RMSSD (Root Mean Square of Successive Differences): measures short-term variability.
  • SDNN (Standard Deviation of NN intervals): measures overall variability.

I did most of my testing with a Polar H7, which worked well.

screenshot of HRV results panel with RMSSD/SDNN values

Synthesized RR Intervals

While testing I ran into something worth knowing: not all heart rate monitors report real RR intervals. Some devices don’t actually measure the time between beats — instead they synthesize a value as 60000 / HR. The result is mathematically consistent with the reported heart rate but has no actual beat-to-beat variability, making HRV analysis meaningless.

I discovered this when I switched from the Polar H7 to a Decathlon ANT+/Bluetooth heart rate monitor I had lying around. The app detects this automatically: if the reported RR intervals consistently fall within 1 ms of 60000 / HR, it warns you that the data is likely synthesized and HRV results are unreliable.

Browser Support

The Web Bluetooth API works in Chrome and Edge. Firefox and Safari don’t support it.

On Linux (and older Windows versions) you need to enable an experimental flag before it works:

  1. Open chrome://flags#enable-experimental-web-platform-features
  2. Enable the flag and restart the browser

On Windows 10/11 and Android it works out of the box. The app also requires an HTTPS connection — the GitHub Pages deployment covers that, and http://localhost works fine for local development.

captive-firefox: Solving Captive Portal Headaches with DNS over TLS

If you’re running systemd-resolved with DNS over TLS (like I detailed in my split DNS post), you’ve probably run into the same annoying problem I have: captive portals just don’t work properly.

The issue is straightforward but frustrating. Your system is configured to use secure DNS servers like Cloudflare (1.1.1.1) with DNS over TLS, which is great for security and privacy. But when you connect to that hotel Wi-Fi or coffee shop network, the captive portal can’t intercept your DNS queries to redirect you to their login page. Your requests bypass their DNS entirely, so you never see the portal.

The Manual Workaround (That Gets Old Fast)

The typical workaround involves giving the Wi-Fi network DNS priority and disabling DNS over TLS:

sudo resolvectl domain wlp0s20f3 "~." && sudo resolvectl dnsovertls wlp0s20f3 no
# Connect to captive portal
# Log in manually
# Revert changes
sudo resolvectl domain wlp0s20f3 "" && sudo resolvectl dnsovertls wlp0s20f3 yes

This works, but it’s annoying for two reasons: you have to remember to revert the changes, and it affects your entire system’s DNS behavior instead of just the browser you need for the captive portal.

Enter captive-firefox

I got tired of this dance and wrote a simple Bash script that handles captive portals elegantly. The idea is simple: launch Firefox in a sandbox that uses the Wi-Fi network’s DNS server directly, bypassing your system’s DNS configuration entirely.

Here’s what the script does:

  1. Auto-detects your Wi-Fi interface (or you can specify it)
  2. Extracts the DHCP-provided DNS server from NetworkManager
  3. Launches Firefox in a firejail sandbox with that specific DNS
  4. Uses a completely isolated profile – no access to your regular browsing data

The result? Firefox can see the captive portal while your system maintains its secure DNS configuration.

Zero Dependencies, Maximum Convenience

The script requires only standard Linux tools you probably already have:

  • Bash
  • firejail (for sandboxing)
  • nmcli (NetworkManager CLI)
  • iw (wireless tools)
  • Firefox

No Go binaries to compile and no complex dependencies. Just copy the script and run it.

Usage

Most of the time, it’s as simple as:

./captive-firefox.sh

The script will auto-detect everything and open Firefox, pointing to the standard captive portal detection URL. For edge cases, you can specify the interface or target URL manually.

I’ve also included a .desktop file so you can launch it from your application menu when needed.

Security Considerations

The sandboxed Firefox instance:

  • Can’t access your real Firefox profile or data
  • Uses only the captive portal’s DNS (isolated from your secure setup)
  • Runs in a firejail container for additional isolation
  • Automatically cleans up when closed

Your main system’s DNS configuration remains untouched throughout the process.

Get It

The script is available on my GitHub: captive-firefox

Finally, a civilized way to deal with captive portals without compromising your DNS security setup.

Tarsum in Rust

Almost 14 years ago, I wrote a small utility, named tarsum, to calculate checksums on files inside a tar archive. It was useful for verifying data inside backups. Recently, I decided to rewrite it in Rust. It’s available from https://github.com/guyru/tarsum.

Installation using cargo is straightforward:

$ cargo install --git https://github.com/guyru/tarsum

Surprisingly, when testing on a large tar archive (a recent Linux tarball, 1.3 GB), the performance of both the Python and Rust implementations is very similar.

Greasemonkey: Fix links to PDFs in Bank Hapoalim

This fixes both the links to the PDFs and the embedding in the mailbox. Click on “View Raw” to install.

// ==UserScript==
// @name Bank Hapoalim
// @description Workaround bugs in Bank Hapoalim website to display pdf messages.
// @author Guy Rutenberg
// @namespace http://www.guyrutenberg.com
// @include https://login.bankhapoalim.co.il/portalserver/mailInbox#/folders/0
// @include https://login.bankhapoalim.co.il/ng1-portals/rb/he/mails#/folders/0
// @run-at document-idle
// @version 1.1
// @grant none
// ==/UserScript==
//
// @include url should be the url of the inner iframe containing the list of mails.
var MutationObserver = window.MutationObserver;
var myObserver = new MutationObserver (mutationHandler);
var obsConfig = {
childList: true, attributes: false,
subtree: true,
};
var target = document.getElementsByTagName('mail-manager')[0];
myObserver.observe (target, obsConfig);
function mutationHandler (mutationRecords) {
console.log('here');
mutationRecords.forEach ( function (mutation) {
for (var item of mutation.addedNodes) {
if (item.tagName == 'PDF-VIEWER') {
var url = 'https://login.bankhapoalim.co.il' + item.getAttribute('pdf-show');
iframe = item.querySelector('iframe');
iframe.src = url;
}
}
});
}

Introducing mdview – a lightweight Markdown viewer

My favorite editor is vim, but it has downsides as well. Vim doesn’t have the GUI needed to extend it to preview things like Markdown properly. Yeah, sure, vim can highlight Markdown syntax, but that is not a replacement for real previewing. With that itch in mind, I searched for a solution but found none that satisfied me. For reStructuredText, I’ve found a solution that worked well. It worked by starting a local web server and doing the previewing in the browser. Inspired by it, I started writing mdview.

mdview allows you to instantly preview any Markdown file you’re editing in your favorite browser. It will automatically refresh when the file is changed, hence it’s great for working with the editor and browser side by side for live preview.
Continue reading Introducing mdview – a lightweight Markdown viewer

Outbrained – Greasemonkey script to remove tracking Outbrain links

Outbrain is a service that provides related content links to publishers. It is used by some news sites I frequent, and recently I’ve been annoyed by its tracking behavior. When you hover your cursor above the link, it seems like a regular, benign link, but once you click on the link, it changes to an evil tracking URL. To add to the annoyance, it is not always easy to distinguish Outbrain “ads” from legitimate links at first sight.

To end this annoyance for me, I’ve written a little Greasemonkey script. It is currently set up to work for Haaretz, Ynet, Calcalist, and TheMarker, but it should work fine for any site using Outbrain if enabled.

Download: outbrained.user.js

name-taken – Check if your project name is taken

Every time I want to start a new open-source project, I come across this small “problem”: making sure that the name for the project isn’t already taken. Today I decided to solve it by creating a simple script that queries different open-source repositories to check if a project with the desired name exists.

Usage is quite simple:

$ name_taken.py enlightenment
Debian: Name not taken :-)
SourceForge: Name taken :-(

Currently, the script is in an early stage and can search for projects in Debian’s list of packages and on SourceForge. The code is hosted on GitHub: https://github.com/guyru/name_taken, and licensed under GPL2 or higher. Suggestions on how to make this tool more useful (and, of course, patches) are really welcome.

Haaretz Premium Bypass Userscript POC

Haaretz‘s site became paywalled a couple of months ago, allowing users to read 10 “premium” articles before requiring payment. After a friend recommended their smartphone app, which unlike the site is free, I started reading it mainly on my phone. A few days ago I had no internet connection on my phone, and instead of seeing an article I saw an error page saying it wasn’t reachable (the usual Android built-in browser type). The URL was something like http://www.haaretz.co.il/misc/smartphone-article/.premium-1.2070500, while the URL for the same article on the regular site is http://www.haaretz.co.il/news/world/middle-east/.premium-1.2070500. This of course got me curious, and a quick check showed that there is no problem accessing the mobile version from a desktop browser. So I went ahead and wrote a simple proof-of-concept Greasemonkey script to demonstrate replacing missing premium content on the desktop site with content intended for the smartphone app.
Continue reading Haaretz Premium Bypass Userscript POC

spass-3.1 Secure Password Generator Released

Usually, release announcements go together with the actual release. Somehow, I’ve postponed writing about the new release for quite some time, but better late than never.

spass is a tool that creates cryptographically strong passwords and passphrases by generating random bits from your sound card. It works by passing noise from the sound card through a Von Neumann process to remove bias and then uses MD5 to “distill” a truly random bit from every 4 bits of input.

The new version of spass, version 3.1, was released two months ago. The code should now compile easily on both Linux (ALSA, OSS, and PortAudio backends) and Windows (only PortAudio is supported). There are some minor tweaks to the CLI, but the main part is a new Qt interface, with screenshots of it available on the project’s SourceForge page. I’ve also migrated the build system to CMake (from automake), which should make it easier to build.

You can download the sources, 64-bit Debian package, and binaries for Windows from here. If you use spass and create binary packages for more platforms, that would be great.

BTW, as you can see, I’ve migrated the code to SourceForge from GitHub. I know it’s not a popular move, but their lack of binary downloads is really frustrating.

spass 3.0 Released

I’ve released today the new version of spass, a tool that creates cryptographically strong passwords and passphrases by generating random bits from your sound card.

On the user-facing side, spass can now create passphrases as well as passwords. The words for the passphrases are chosen from a list of 8192 words, which means each word adds 13 bits of entropy to the passphrase.

spass can now use one of three audio backends (the old version could only use OSS):

  • Advanced Linux Sound Architecture (ALSA)
  • Open Sound System (OSS)
  • PortAudio

PortAudio support will hopefully make it easy to port spass to other platforms as well (such as Windows). The random number generator got an overhaul, and now there is an unbiasing step before applying the hash function. This should help produce consistent results in terms of entropy. Behind the scenes, I’ve migrated the project from autotools to cmake.

You can find more information, as well as both source and binary packages, at https://github.com/guyru/spass.