Skip to content

Browser Fingerprint Viewer — Go source

See exactly what websites can learn about your browser without cookies — screen, GPU, fonts, timezone, language, and more. Educational, not tracking.

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

// browser-fingerprint — Go polyglot showcase port (pure analysis layer).
//
// The authoritative Go source is the live CLI twin at
// cli/browser-fingerprint/browserfingerprint.go — the foundation of the
// `cosmodev browser-fingerprint` command, kept in lock-step with
// src/lib/browser-fingerprint.ts via shared test vectors. This display
// snippet mirrors that twin's portable core: the signal registry, risk
// classification, category grouping, risk counts, and the SHA-256
// fingerprint hash. The browser-API collection layer (canvas / WebGL / font
// probes) is DOM-bound by design and does not port.
//
// Display source — part of CosmoDev's polyglot tool pages.
package browserfingerprint

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"strings"
)

// RiskLevel is a privacy-risk classification:
//   - high:   near-unique on its own or part of a rare combination
//   - medium: narrows you to a sizable-but-specific population
//   - low:    coarse; shared by millions of browsers
type RiskLevel string

const (
	RiskLow    RiskLevel = "low"
	RiskMedium RiskLevel = "medium"
	RiskHigh   RiskLevel = "high"
)

// FingerprintCategory groups related signals.
type FingerprintCategory string

const (
	CategoryHardware FingerprintCategory = "Hardware"
	CategoryGraphics FingerprintCategory = "Graphics"
	CategoryNetwork  FingerprintCategory = "Network"
	CategoryBrowser  FingerprintCategory = "Browser"
	CategoryInput    FingerprintCategory = "Input"
)

// FingerprintCategories is the canonical category order (card grid, top to
// bottom).
var FingerprintCategories = []FingerprintCategory{
	CategoryHardware, CategoryGraphics, CategoryNetwork, CategoryBrowser, CategoryInput,
}

// FingerprintSignal is one collected, classified signal.
type FingerprintSignal struct {
	ID       string
	Label    string
	Value    string
	Risk     RiskLevel
	Category FingerprintCategory
}

// SignalDef classifies one signal id.
type SignalDef struct {
	Label    string
	Risk     RiskLevel
	Category FingerprintCategory
}

// SignalDefinitions is the static registry of every signal the tool
// collects, with its privacy-risk classification. Single source of truth:
// BuildSignal derives from it, so a signal can never be collected without
// being classified.
var SignalDefinitions = map[string]SignalDef{
	// High risk
	"canvas":         {Label: "Canvas fingerprint", Risk: RiskHigh, Category: CategoryGraphics},
	"webgl-renderer": {Label: "WebGL renderer", Risk: RiskHigh, Category: CategoryGraphics},
	"webgl-vendor":   {Label: "WebGL vendor", Risk: RiskHigh, Category: CategoryGraphics},
	"fonts":          {Label: "Installed fonts", Risk: RiskHigh, Category: CategoryBrowser},
	"timezone":       {Label: "Timezone", Risk: RiskHigh, Category: CategoryBrowser},
	// Medium risk
	"screen":               {Label: "Screen & color depth", Risk: RiskMedium, Category: CategoryHardware},
	"device-pixel-ratio":   {Label: "Device pixel ratio", Risk: RiskMedium, Category: CategoryHardware},
	"hardware-concurrency": {Label: "CPU cores", Risk: RiskMedium, Category: CategoryHardware},
	"device-memory":        {Label: "Device Memory", Risk: RiskMedium, Category: CategoryHardware},
	"platform":             {Label: "Platform", Risk: RiskMedium, Category: CategoryBrowser},
	"languages":            {Label: "Languages", Risk: RiskMedium, Category: CategoryBrowser},
	"touch":                {Label: "Touch support", Risk: RiskMedium, Category: CategoryInput},
	"connection":           {Label: "Connection type", Risk: RiskMedium, Category: CategoryNetwork},
	// Low risk
	"user-agent":      {Label: "User agent", Risk: RiskLow, Category: CategoryBrowser},
	"do-not-track":    {Label: "Do Not Track", Risk: RiskLow, Category: CategoryBrowser},
	"cookies-enabled": {Label: "Cookies enabled", Risk: RiskLow, Category: CategoryBrowser},
	"online":          {Label: "Online status", Risk: RiskLow, Category: CategoryNetwork},
	"pdf-viewer":      {Label: "PDF viewer", Risk: RiskLow, Category: CategoryBrowser},
}

// ClassifySignalRisk returns the risk level for a known signal id; ok is
// false for unknown ids.
func ClassifySignalRisk(id string) (RiskLevel, bool) {
	def, ok := SignalDefinitions[id]
	return def.Risk, ok
}

// BuildSignal builds a signal from its id and collected value. Errors on
// unknown ids so a typo'd id fails loudly instead of silently rendering an
// unclassified row.
func BuildSignal(id string, value string) (FingerprintSignal, error) {
	def, ok := SignalDefinitions[id]
	if !ok {
		return FingerprintSignal{}, fmt.Errorf("Unknown fingerprint signal id: %s", id)
	}
	return FingerprintSignal{ID: id, Label: def.Label, Value: value, Risk: def.Risk, Category: def.Category}, nil
}

// CategoryGroup is one category and its signals.
type CategoryGroup struct {
	Category FingerprintCategory
	Signals  []FingerprintSignal
}

// GroupByCategory groups signals by category in canonical order, omitting
// empty categories.
func GroupByCategory(signals []FingerprintSignal) []CategoryGroup {
	groups := map[FingerprintCategory][]FingerprintSignal{}
	for _, s := range signals {
		groups[s.Category] = append(groups[s.Category], s)
	}
	var out []CategoryGroup
	for _, c := range FingerprintCategories {
		if list, ok := groups[c]; ok {
			out = append(out, CategoryGroup{Category: c, Signals: list})
		}
	}
	return out
}

// HashFingerprint is the stable fingerprint ID: SHA-256 over every signal
// value, joined in signal order. Same browser state → same hash; any single
// changed value → new hash.
func HashFingerprint(signals []FingerprintSignal) string {
	return Sha256Hex(ConcatSignalValues(signals))
}

// ConcatSignalValues is the exact byte string the fingerprint hash is
// computed over: every value joined with | in signal order.
func ConcatSignalValues(signals []FingerprintSignal) string {
	values := make([]string, len(signals))
	for i, s := range signals {
		values[i] = s.Value
	}
	return strings.Join(values, "|")
}

// Sha256Hex is the SHA-256 hex digest of input.
func Sha256Hex(input string) string {
	sum := sha256.Sum256([]byte(input))
	return hex.EncodeToString(sum[:])
}

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 →