Class: Otto::Privacy::UserAgentPrivacy
- Inherits:
-
Object
- Object
- Otto::Privacy::UserAgentPrivacy
- Defined in:
- lib/otto/privacy/user_agent_privacy.rb
Overview
Idempotent: re-anonymizing already-anonymized output is a no-op, so a UA reduced at the edge and one reduced again downstream agree.
User-Agent anonymization utilities.
Reduces a User-Agent string to a lower-entropy form by stripping build identifiers and version numbers and truncating, so it can be logged or stored for analytics without a high-entropy fingerprint while preserving browser/OS family information. This is the User-Agent analogue of IPPrivacy for IP addresses, exposed as a public surface so downstream consumers can reduce a UA outside of the full RedactedFingerprint / middleware flow without re-implementing (and drifting from) these regexes.
Constant Summary collapse
- DEFAULT_MAX_LENGTH =
Default cap on the returned string, guarding against a DoS via a huge User-Agent header. Matches the length RedactedFingerprint has always applied.
500
Class Method Summary collapse
-
.anonymize(ua, max_length: DEFAULT_MAX_LENGTH) ⇒ String?
Anonymize a User-Agent string.
Class Method Details
.anonymize(ua, max_length: DEFAULT_MAX_LENGTH) ⇒ String?
Anonymize a User-Agent string.
Removes build identifiers (e.g. +Build/MRA58N+) and version numbers (+...+, +..+, +.*+; dot- or underscore-separated), then truncates to +max_length+. Browser/OS family text is preserved – the point is a partial, not a full redaction.
Build identifiers are stripped BEFORE versions: if versions went first, a token like +Build/MPJ24.139-64+ would become +Build/MPJ.-64+ and the build regex (which matches only +[\w.-]+) would no longer catch it.
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/otto/privacy/user_agent_privacy.rb', line 45 def self.anonymize(ua, max_length: DEFAULT_MAX_LENGTH) return nil if ua.nil? || ua.empty? # Remove build identifiers (e.g., Build/MRA58N, Build/MPJ24.139-64). # Must run BEFORE version stripping (see method note). anonymized = ua.gsub(%r{Build/[\w.-]+}, 'Build/*') # Remove version patterns (*.*.*.*, *.*.*, *.*), longest first. # Support both dot and underscore separators (e.g. 10.15.7 and 10_15_7). anonymized = anonymized .gsub(/\d+[._]\d+[._]\d+[._]\d+/, '*.*.*.*') .gsub(/\d+[._]\d+[._]\d+/, '*.*.*') .gsub(/\d+[._]\d+/, '*.*') # Truncate if too long (prevent DoS via huge UA strings). anonymized.length > max_length ? anonymized[0...max_length] : anonymized end |