Module: Parse::Middleware::URLRedaction
- Defined in:
- lib/parse/client/url_redaction.rb
Overview
Shared query-string credential redactor for the logging and profiling middlewares. Kept in ONE place so the two sanitizers cannot drift — a credential that leaks through one but not the other is exactly the failure mode this consolidation prevents.
The previous per-middleware sanitizer only redacted three exact
param names (sessionToken / masterKey / apiKey). A credential
carried under any other name — token, access_token,
client_secret, password, a CloudFront Signature /
Key-Pair-Id, an S3 X-Amz-Signature, etc. — sailed through into
logs and Parse.recent_profiles. This redacts the VALUE of any
query param whose name looks credential-bearing, while leaving the
known-safe Parse query params (and other non-sensitive params)
visible for debuggability.
Constant Summary collapse
- SAFE_QUERY_PARAMS =
Parse (and a couple of CloudFront) query params that contain a sensitive-looking substring but are NOT secrets — protect them from over-redaction. Compared case-insensitively.
%w[ keys excludekeys redirectclassnameforkey ].freeze
- SENSITIVE_NAME =
A query param whose (case-insensitive) name matches this is treated as credential-bearing and has its value replaced with
[FILTERED], unless the name is in SAFE_QUERY_PARAMS. /(?:token|key|secret|password|passwd|pwd|signature|\bsig\b|auth|credential|policy)/i- REDACTED =
"[FILTERED]"
Class Method Summary collapse
-
.decode_name(name) ⇒ String
Percent-decode a query-param NAME so a credential carried under an encoded name is matched by its decoded spelling.
-
.sanitize(url) ⇒ String
The URL with credential-bearing query values redacted.
- .sensitive?(name) ⇒ Boolean
Class Method Details
.decode_name(name) ⇒ String
Percent-decode a query-param NAME so a credential carried under an
encoded name is matched by its decoded spelling. Servers decode
session%54oken to sessionToken before reading it, so matching
the raw name alone lets an encoded credential name slip past
redaction.
ONLY ASCII escapes (%00–%7F, i.e. first hex digit 0–7) are
decoded. Credential keywords are ASCII, so that is sufficient — and
decoding a high byte (%C3, %FF) would splice a lone
continuation/lead byte into the (UTF-8) name and yield an
invalid-encoding string that raises in the downstream #downcase /
#match. High-byte escapes are therefore left literal. + and
other bytes are untouched (param names don't use form-encoding).
The decoded form is used ONLY for the sensitivity decision;
sanitize still emits the original spelling.
89 90 91 |
# File 'lib/parse/client/url_redaction.rb', line 89 def decode_name(name) name.gsub(/%([0-7][0-9A-Fa-f])/) { Regexp.last_match(1).to_i(16).chr } end |
.sanitize(url) ⇒ String
Returns the URL with credential-bearing query values redacted.
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/parse/client/url_redaction.rb', line 41 def sanitize(url) str = url.to_s # Possessive quantifiers (`++` / `*+`) so the param-name and value # runs never backtrack: on a pathological URL (a long run of # non-delimiter chars with no `=`) the greedy form would rescan # super-linearly (polynomial ReDoS on caller-supplied URLs flowing # into log/profile redaction). The excluded-delimiter classes make # the match unambiguous, so possessive matching is identical to the # greedy match for every real query string. str.gsub(/([?&])([^=&#]++)=([^&#]*+)/) do sep = Regexp.last_match(1) name = Regexp.last_match(2) value = Regexp.last_match(3) if sensitive?(name) "#{sep}#{name}=#{REDACTED}" else "#{sep}#{name}=#{value}" end end end |
.sensitive?(name) ⇒ Boolean
64 65 66 67 68 |
# File 'lib/parse/client/url_redaction.rb', line 64 def sensitive?(name) decoded = decode_name(name) return false if SAFE_QUERY_PARAMS.include?(decoded.downcase) !SENSITIVE_NAME.match(decoded).nil? end |