Screenshot of PR 2048 on GitHub

Have I Been Pwned is a service that lets you check if your accounts have been included in data breaches. You can enter your email address to subscribe to notifications if (or when) your personal details are compromised.

HIBP also offers an API that lets site operators check their users’ passwords haven’t been previously compromised. It does this using a clever k-Anonymity model which allows this check to happen without revealing the user’s chosen password to HIBP or any other third party. You can read up on the precise method in the HIBP API documentation.

My pull request to add Have I Been Pwned support to RubyGems.org was recently (well, a month ago) merged and deployed to production.

The implementation uses the wonderful Unpwn gem from André Arko which first uses a bloom filter to check the password against the top one million passwords and only connects to HIBP if necessary. This not only makes the check faster for the most common passwords, but also provides some level of protection even if the HIBP API is unreachable.

My part was mostly to create a validator that uses the Unpwn gem and to add tests for the new validation. The change also required changes to the existing tests because the simple passwords used in the test suite (e.g. password1234 or secret12345) had already been compromised.

require "unpwn"

class UnpwnValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    record.errors.add(attribute, :unpwn) unless unpwn.acceptable?(value)
  rescue Pwned::TimeoutError # rubocop:disable Lint/HandleExceptions
    # Do nothing if the HTTP call timesout, consider the value valid
  rescue Pwned::Error # rubocop:disable Lint/HandleExceptions
    # Do nothing if the HTTP call fails, consider the value valid
  end

  private

  def unpwn
    @unpwn ||= Unpwn.new(min: nil, max: nil, request_options: { read_timeout: 3 })
  end
end

I also had to make a couple of changes to the Unpwn gem to allow the read timeout value top be passed to the underlying Pwned API client.

It might be clichéd to have been able to contribute a little something back to the Ruby ecosystem and it makes me smile a little knowing that a little bit of code that I wrote is running on RubyGems.org. Perhaps it’ll even prevent the occasional account compromise from happening. Of course this does nothing to prevent existing accounts with re-used passwords from getting compromised but I suppose that every little bit helps…