Skip to content

Privacy Score — Zig 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 Zig implementation — the same logic the interactive tool runs, in a shareable, citable form.

// privacy-score — composite privacy scoring engine.
//
// Language: Zig (0.13+, 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.

const std = @import("std");

/// Round to the nearest point and pin into the 0..25 window.
fn clamp25(points: f64) i32 {
    const r: i32 = @intFromFloat(@round(points));
    return @min(25, @max(0, r));
}

/// 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.
fn fingerprintPoints(signal_count: i32, high_risk: i32, medium_risk: i32) i32 {
    const surface = @as(f64, @floatFromInt(@max(0, signal_count - 12))) * 0.5;
    return clamp25(25.0 - @as(f64, @floatFromInt(high_risk)) * 4.0 - @as(f64, @floatFromInt(medium_risk)) * 1.5 - surface);
}

fn passwordPoints(score: i32, breached: bool) i32 {
    const base = @as(f64, @floatFromInt(std.math.clamp(score, 0, 4))) / 4.0 * 25.0;
    // Breached is urgent regardless of strength: x0.32 floors even a
    // score-4 password at 8 points -> bad -> the change-it recommendation.
    return clamp25(if (breached) base * 0.32 else base);
}

/// grades is a string of 'A'..'F' characters, e.g. "AAB".
fn headersPoints(grades: []const u8) i32 {
    var f: i32 = 0;
    var c: i32 = 0;
    var b: i32 = 0;
    for (grades) |g| switch (g) {
        'F' => f += 1,
        'C' => c += 1,
        'B' => b += 1,
        else => {},
    };
    if (f > 0) return 0;
    if (c > 0) return 10;
    if (b > 0) return 18;
    return if (grades.len > 0) 25 else 0;
}

fn breachPoints(pwned: bool) i32 {
    return if (pwned) 0 else 25;
}

fn letterFor(percent: i32, max: i32) u8 {
    if (max == 0) return '-';
    if (percent >= 85) return 'A';
    if (percent >= 70) return 'B';
    if (percent >= 50) return 'C';
    return 'D';
}

pub fn main() void {
    const checks = [_]i32{
        fingerprintPoints(18, 2, 4), // 8
        passwordPoints(4, true),     // 8 (breach floor)
        headersPoints("AAB"),        // 18
        breachPoints(false),         // 25
    };
    var total: i32 = 0;
    for (checks) |p| total += p;
    const max: i32 = @as(i32, @intCast(checks.len)) * 25;
    const percent: i32 = @intFromFloat(@round(
        @as(f64, @floatFromInt(total)) / @as(f64, @floatFromInt(max)) * 100.0,
    ));
    std.debug.print("total {d}/{d} = {d}% -> {c}\n", .{ total, max, percent, letterFor(percent, max) });
}

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 →