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 →