Skip to content

Privacy Score — Ruby source

One number for your privacy health: your browser fingerprint, a test password's strength and breach exposure, and a site's security headers — four checks, one score, concrete fixes. Runs in your browser; only a 5-character hash prefix ever leaves it.

This is the Ruby implementation — the same logic the interactive tool runs, in a shareable, citable form.

# privacy-score — composite privacy scoring engine.
#
# Language: Ruby (3.x, standard library only)
# Source:   CosmoDev polyglot showcase port of the Privacy Score tool,
#           ported from src/lib/privacy-score.ts (canonical TypeScript).
# License:  display source — part of CosmoDev's polyglot tool pages.
#
# Four category checks, each scored out of 25; the overall percent is
# renormalized over the categories that actually ran — skipping a check
# never lowers your score. Letter bands: >=85 A, >=70 B, >=50 C, else D.

module PrivacyScore
  module_function

  # Round to the nearest point and pin into the 0..25 window.
  def clamp25(points) = points.round.clamp(0, 25)

  def status_for(points) = points >= 20 ? 'ok' : points >= 10 ? 'warn' : 'bad'

  # More distinguishable signals make a browser more unique: high-risk
  # signals cost 4 each, medium 1.5, and a large surface costs a little more.
  def fingerprint_points(signal_count, high_risk, medium_risk)
    surface = [0, signal_count - 12].max * 0.5
    clamp25(25 - high_risk * 4 - medium_risk * 1.5 - surface)
  end

  def password_points(score, breached)
    base = score.clamp(0, 4) / 4.0 * 25
    # Breached is urgent regardless of strength: x0.32 floors even a
    # score-4 password at 8 points -> 'bad' -> the change-it recommendation.
    clamp25(breached ? base * 0.32 : base)
  end

  def headers_points(grades)
    return 0 if grades.include?('F')
    return 10 if grades.include?('C')
    return 18 if grades.include?('B')

    grades.empty? ? 0 : 25
  end

  def breach_points(pwned) = pwned ? 0 : 25

  def letter_for(percent, maximum)
    return '—' if maximum.zero?
    return 'A' if percent >= 85
    return 'B' if percent >= 70
    return 'C' if percent >= 50

    'D'
  end

  # Renormalize over the checks that ran and grade the overall letter.
  def score_privacy(points)
    total = points.sum
    maximum = points.length * 25
    percent = maximum.zero? ? 0 : (total.to_f / maximum * 100).round
    { total:, max: maximum, percent:, letter: letter_for(percent, maximum),
      statuses: points.map { |p| status_for(p) } }
  end
end

checks = [
  PrivacyScore.fingerprint_points(18, 2, 4),   # 8
  PrivacyScore.password_points(4, true),       # 8 (breach floor)
  PrivacyScore.headers_points(%w[A A B]),      # 18
  PrivacyScore.breach_points(false)            # 25
]
pp checks
pp PrivacyScore.score_privacy(checks)

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →