Module: BugBunny::Observability Private

Included in:
BugBunny, Consumer, Controller, Producer, Session
Defined in:
lib/bug_bunny/observability.rb

This module is part of a private API. You should avoid using this module if possible, as it may be removed or be changed in the future.

Constant Summary collapse

SENSITIVE_KEYS =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Patrones de keys que deben ser ocultados en los logs. Se usa substring matching en lowercase para cubrir variantes como "user_password", "accessToken", "X-Authorization", etc. Excluye "pass" y "session" bare para evitar falsos positivos en keys como "passport_number" o "processing_session_count".

%w[
  password passwd secret token api_key auth authorization
  credential private_key csrf session_id
].freeze
SENSITIVE_KEYS_ALTERNATION =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Alternación de keys sensibles ordenada de más larga a más corta: en un regex la alternación matchea leftmost-first, así que sin este orden auth ganaría sobre authorization y el patrón dejaría de matchear (orization=x no sigue con [:=]).

SENSITIVE_KEYS.sort_by { |k| -k.length }.join('|').freeze
SENSITIVE_VALUE_RULES =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Reglas de VALOR sensible, como pares [regex, reemplazo].

sensitive_key? solo ve el NOMBRE de la clave; no puede ver una credencial embebida en TEXTO LIBRE. El caso canónico es el message de una excepción inesperada (llega como reason= o error_message=, nombres no sensibles): un NoMethodError sobre un objeto de respuesta HTTP puede arrastrar Authorization: "Bearer eyJ..." en su mensaje y el filtro por-clave lo deja pasar entero al log.

El reemplazo conserva el nombre de la clave cuando viaja dentro del texto (token=[FILTERED], no [FILTERED]): saber QUÉ credencial apareció es diagnóstico útil; su valor no.

[
  # Esquemas de autenticación HTTP: "Bearer <jwt>", "Basic <base64>".
  [/\b(?:bearer|basic)\s+[A-Za-z0-9\-._~+\/]{8,}={0,2}/i, '[FILTERED]'],
  # La key viaja DENTRO del texto: `token=abc`, `password: 'x'`, `"api_key" => "y"`.
  #
  # El prefijo `\w*` va en lugar de un `\b`: `_` es word-char, así que un borde de
  # palabra NO existe dentro de `access_token` ni de `accessToken` y esas variantes
  # se colarían en claro — justo las que {.sensitive_key?} cubre a propósito con
  # substring matching. Se captura el prefijo para conservar el nombre COMPLETO de la
  # key en el log (`access_token=[FILTERED]`): saber qué credencial apareció es
  # diagnóstico útil. No reintroduce el falso positivo de `passport_number` porque
  # ninguna key de SENSITIVE_KEYS es substring suyo (por eso `pass` bare está excluida).
  [/(\w*(?:#{SENSITIVE_KEYS_ALTERNATION}))["']?\s*(?:=>|[:=])\s*["']?[^\s,;"'}\])]+/i,
   '\1=[FILTERED]'],
  # Credenciales en una URL: `amqp://user:pass@host` → conserva el esquema y el host.
  [%r{(://)[^\s/:@]+:[^\s/@]+@}, '\1[FILTERED]@']
].freeze

Class Method Summary collapse

Class Method Details

.redact_structure(obj) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Redacta una estructura ANTES de serializarla, recorriendo keys y valores.

Se usa para los valores Hash de #safe_log. Redactar el JSON ya serializado con redact_value no sirve: la regla de key-dentro-del-texto normaliza el separador a = y se come la comilla de cierre de la key, dejando un objeto donde el par "token": "abc" quedó colapsado en "token=[FILTERED]" — el secreto desaparece, pero el campo deja de ser JSON parseable y quien consume el log pierde el objeto entero, no solo el valor redactado.

Recorriendo la estructura, además, las keys internas SÍ pasan por sensitive_key? (que solo veía las keys de primer nivel del metadata).

Parameters:

  • obj (Object)

    Estructura a redactar (Hash/Array anidados incluidos).

Returns:

  • (Object)

    La misma forma, con los valores sensibles reemplazados.



93
94
95
96
97
98
99
100
101
102
103
# File 'lib/bug_bunny/observability.rb', line 93

def self.redact_structure(obj)
  case obj
  when Hash
    obj.each_with_object({}) do |(k, v), acc|
      acc[k] = sensitive_key?(k) ? '[FILTERED]' : redact_structure(v)
    end
  when Array then obj.map { |element| redact_structure(element) }
  when Numeric, TrueClass, FalseClass, NilClass then obj
  else redact_value(obj)
  end
end

.redact_value(value) ⇒ String

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Redacta credenciales embebidas en un valor de texto libre.

Complementa a sensitive_key?: esa filtra por NOMBRE de clave, esta por CONTENIDO. Se aplica a todo valor no numérico que #safe_log serializa.

Parameters:

  • value (Object)

    El valor a redactar (se serializa con to_s).

Returns:

  • (String)

    El valor con las credenciales reemplazadas por [FILTERED].



73
74
75
76
77
# File 'lib/bug_bunny/observability.rb', line 73

def self.redact_value(value)
  SENSITIVE_VALUE_RULES.reduce(value.to_s) do |acc, (pattern, replacement)|
    acc.gsub(pattern, replacement)
  end
end

.sensitive_key?(key) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Determina si una key es sensible y debe filtrarse en los logs. Accesible como método de módulo para que otros componentes puedan reutilizarlo.

Parameters:

  • key (String, Symbol)

    La clave a evaluar.

Returns:

  • (Boolean)

    true si la key es sensible.



23
24
25
26
27
28
# File 'lib/bug_bunny/observability.rb', line 23

def self.sensitive_key?(key)
  # Normalize hyphens → underscores so HTTP headers like "X-Api-Key"
  # match the same patterns as Ruby symbol keys like :api_key.
  key_str = key.to_s.downcase.tr('-', '_')
  SENSITIVE_KEYS.any? { |sensitive| key_str.include?(sensitive) }
end