Module: Reputable::Reputation

Defined in:
lib/reputable/reputation.rb

Overview

Reputation management functionality

RESILIENCE: All reputation methods are designed to fail silently.

  • Write operations return false on any failure
  • Read operations return nil on any failure
  • Boolean checks (trusted_ip?, blocked_ip?) return false on failure
  • Never raises exceptions during normal operation

Constant Summary collapse

VALID_STATUSES =
%i[
  trusted_verified
  trusted_behavior
  untrusted_challenge
  untrusted_block
  untrusted_ignore
].freeze
VALID_ENTITY_TYPES =
%i[ip asn ja4].freeze

Class Method Summary collapse

Class Method Details

.apply(entity_type:, entity_id:, status:, **options) ⇒ Boolean

Apply a reputation status to an entity

Examples:

Trust an IP after payment

Reputable::Reputation.apply(
  entity_type: :ip,
  entity_id: "203.0.113.45",
  status: :trusted_verified,
  reason: "payment_completed",
  ttl: 0,
  metadata: { order_id: "order_123", amount: 150.00 }
)

Block a suspicious IP

Reputable::Reputation.apply(
  entity_type: :ip,
  entity_id: "198.51.100.23",
  status: :untrusted_block,
  reason: "abuse_detected"
)

Parameters:

  • entity_type (Symbol)

    Type of entity (:ip, :asn, :ja4)

  • entity_id (String)

    The entity identifier (IP address, ASN, etc.)

  • status (Symbol)

    Reputation status to apply

  • options (Hash)

    Additional options

Options Hash (**options):

  • :reason (String)

    Human-readable reason for the status

  • :ttl (Integer)

    TTL in seconds (0 = forever, nil = use default)

  • :metadata (Hash)

    Additional metadata for audit

Returns:

  • (Boolean)

    true if successfully pushed to buffer, false otherwise



53
54
55
56
57
58
59
60
61
62
63
# File 'lib/reputable/reputation.rb', line 53

def apply(entity_type:, entity_id:, status:, **options)
  return false unless Reputable.enabled?
  return false unless valid_entity_type?(entity_type)
  return false unless valid_status?(status)

  entry = build_reputation_entry(entity_type, entity_id, status, options)
  push_to_buffer(entry)
rescue StandardError => e
  Reputable.logger&.debug("Reputable apply error: #{e.class} - #{e.message}")
  false
end

.block_asn(asn, reason: "manual_block", ttl: nil, **metadata) ⇒ Object

Convenience method: Block an ASN



278
279
280
281
282
283
284
285
286
287
# File 'lib/reputable/reputation.rb', line 278

def block_asn(asn, reason: "manual_block", ttl: nil, **)
  apply(
    entity_type: :asn,
    entity_id: normalize_asn(asn),
    status: :untrusted_block,
    reason: reason,
    ttl: ttl,
    metadata: 
  )
end

.block_ip(ip, reason: "manual_block", ttl: nil, **metadata) ⇒ Object

Convenience method: Block an IP



80
81
82
83
84
85
86
87
88
89
# File 'lib/reputable/reputation.rb', line 80

def block_ip(ip, reason: "manual_block", ttl: nil, **)
  apply(
    entity_type: :ip,
    entity_id: ip,
    status: :untrusted_block,
    reason: reason,
    ttl: ttl,
    metadata: 
  )
end

.blocked_asn?(asn) ⇒ Boolean

Check if an ASN should be blocked

Parameters:

  • asn (String)

    ASN

Returns:

  • (Boolean)


249
250
251
# File 'lib/reputable/reputation.rb', line 249

def blocked_asn?(asn)
  lookup_asn(asn) == "untrusted_block"
end

.blocked_ip?(ip) ⇒ Boolean

Check if an IP should be blocked

Parameters:

  • ip (String)

    IP address

Returns:

  • (Boolean)


203
204
205
# File 'lib/reputable/reputation.rb', line 203

def blocked_ip?(ip)
  lookup_ip(ip) == "untrusted_block"
end

.challenge_asn(asn, reason: "suspicious_traffic", ttl: nil, **metadata) ⇒ Object

Convenience method: Challenge an ASN (CAPTCHA, etc.)



290
291
292
293
294
295
296
297
298
299
# File 'lib/reputable/reputation.rb', line 290

def challenge_asn(asn, reason: "suspicious_traffic", ttl: nil, **)
  apply(
    entity_type: :asn,
    entity_id: normalize_asn(asn),
    status: :untrusted_challenge,
    reason: reason,
    ttl: ttl,
    metadata: 
  )
end

.challenge_ip(ip, reason: "suspicious_activity", ttl: nil, **metadata) ⇒ Object

Convenience method: Challenge an IP (CAPTCHA, etc.)



92
93
94
95
96
97
98
99
100
101
# File 'lib/reputable/reputation.rb', line 92

def challenge_ip(ip, reason: "suspicious_activity", ttl: nil, **)
  apply(
    entity_type: :ip,
    entity_id: ip,
    status: :untrusted_challenge,
    reason: reason,
    ttl: ttl,
    metadata: 
  )
end

.challenged_asn?(asn) ⇒ Boolean

Check if an ASN should be challenged

Parameters:

  • asn (String)

    ASN

Returns:

  • (Boolean)


257
258
259
# File 'lib/reputable/reputation.rb', line 257

def challenged_asn?(asn)
  lookup_asn(asn) == "untrusted_challenge"
end

.challenged_ip?(ip) ⇒ Boolean

Check if an IP should be challenged

Parameters:

  • ip (String)

    IP address

Returns:

  • (Boolean)


211
212
213
# File 'lib/reputable/reputation.rb', line 211

def challenged_ip?(ip)
  lookup_ip(ip) == "untrusted_challenge"
end

.ignore_asn(asn, reason: "datacenter_traffic", ttl: nil, **metadata) ⇒ Object

Convenience method: Mark ASN to be ignored in analytics



302
303
304
305
306
307
308
309
310
311
# File 'lib/reputable/reputation.rb', line 302

def ignore_asn(asn, reason: "datacenter_traffic", ttl: nil, **)
  apply(
    entity_type: :asn,
    entity_id: normalize_asn(asn),
    status: :untrusted_ignore,
    reason: reason,
    ttl: ttl,
    metadata: 
  )
end

.ignore_ip(ip, reason: "internal_traffic", ttl: nil, **metadata) ⇒ Object

Convenience method: Mark IP to be ignored in analytics



104
105
106
107
108
109
110
111
112
113
# File 'lib/reputable/reputation.rb', line 104

def ignore_ip(ip, reason: "internal_traffic", ttl: nil, **)
  apply(
    entity_type: :ip,
    entity_id: ip,
    status: :untrusted_ignore,
    reason: reason,
    ttl: ttl,
    metadata: 
  )
end

.lookup(entity_type, entity_id) ⇒ Hash?

Lookup the current reputation status for an entity This is an O(1) Redis HGETALL operation

Examples:

Check IP reputation

rep = Reputable::Reputation.lookup(:ip, "203.0.113.45")
if rep && rep[:status] == "trusted_verified"
  # Allow through
end

Parameters:

  • entity_type (Symbol)

    Type of entity (:ip, :asn, :ja4)

  • entity_id (String)

    The entity identifier

Returns:

  • (Hash, nil)

    Reputation data or nil if not found/expired/error



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/reputable/reputation.rb', line 131

def lookup(entity_type, entity_id)
  return nil unless Reputable.enabled?
  return nil unless valid_entity_type?(entity_type)

  key = "reputation:#{entity_type}:#{entity_id}"

  data = Connection.safe_with(default: nil, context: "reputation_lookup") do |redis|
    redis.hgetall(key)
  end

  if data.nil? || data.empty?
    Reputable.logger&.debug("[Reputable] lookup: #{key} → (none)") if Reputable.verbose?
    return nil
  end

  # Check if expired (Redis TTL should handle this, but double-check)
  expires_at = data["expires_at"].to_i
  if expires_at > 0 && expires_at < (Time.now.to_f * 1000).to_i
    Reputable.logger&.debug("[Reputable] lookup: #{key} → expired") if Reputable.verbose?
    return nil
  end

  result = {
    status: data["status"],
    reason: data["reason"],
    source: data["source"],
    updated_at: data["updated_at"].to_i,
    expires_at: expires_at,
    metadata: safe_parse_json(data["metadata"])
  }

  Reputable.logger&.info("[Reputable] lookup: #{key} → status=#{result[:status]} reason=#{result[:reason]} source=#{result[:source]}") if Reputable.verbose?
  result
rescue StandardError => e
  Reputable.logger&.warn("[Reputable] lookup: exception #{e.class} - #{e.message}")
  nil
end

.lookup_asn(asn) ⇒ String?

Quick lookup for ASN reputation status Returns just the status string (or nil) ASN is normalized (strips "AS" prefix if present)

Examples:

status = Reputable::Reputation.lookup_asn("15169")
# => "untrusted_block" or nil

Parameters:

  • asn (String)

    ASN (e.g., "15169" or "AS15169")

Returns:

  • (String, nil)

    Status string or nil



229
230
231
232
# File 'lib/reputable/reputation.rb', line 229

def lookup_asn(asn)
  result = lookup(:asn, normalize_asn(asn))
  result&.dig(:status)
end

.lookup_ip(ip) ⇒ String?

Quick lookup for IP reputation status Returns just the status string (or nil)

Examples:

status = Reputable::Reputation.lookup_ip("203.0.113.45")
# => "trusted_verified" or nil

Parameters:

  • ip (String)

    IP address

Returns:

  • (String, nil)

    Status string or nil



178
179
180
181
# File 'lib/reputable/reputation.rb', line 178

def lookup_ip(ip)
  result = lookup(:ip, ip)
  result&.dig(:status)
end

.trust_asn(asn, reason: "manual_trust", status: :trusted_behavior, ttl: nil, **metadata) ⇒ Object

Convenience method: Trust an ASN (behavioral by default)



266
267
268
269
270
271
272
273
274
275
# File 'lib/reputable/reputation.rb', line 266

def trust_asn(asn, reason: "manual_trust", status: :trusted_behavior, ttl: nil, **)
  apply(
    entity_type: :asn,
    entity_id: normalize_asn(asn),
    status: status,
    reason: reason,
    ttl: ttl,
    metadata: 
  )
end

.trust_ip(ip, reason: "manual_trust", status: :trusted_behavior, ttl: nil, **metadata) ⇒ Object

Convenience method: Trust an IP (behavioral by default)

Parameters:

  • status (Symbol) (defaults to: :trusted_behavior)

    :trusted_behavior or :trusted_verified

  • ttl (Integer, nil) (defaults to: nil)

    TTL in seconds (0 = forever, nil = use default)



68
69
70
71
72
73
74
75
76
77
# File 'lib/reputable/reputation.rb', line 68

def trust_ip(ip, reason: "manual_trust", status: :trusted_behavior, ttl: nil, **)
  apply(
    entity_type: :ip,
    entity_id: ip,
    status: status,
    reason: reason,
    ttl: ttl,
    metadata: 
  )
end

.trusted_asn?(asn) ⇒ Boolean

Check if an ASN is trusted (any trusted_* status)

Parameters:

  • asn (String)

    ASN

Returns:

  • (Boolean)

    Returns false if disabled, nil lookup, or not trusted



238
239
240
241
242
243
# File 'lib/reputable/reputation.rb', line 238

def trusted_asn?(asn)
  status = lookup_asn(asn)
  return false if status.nil?
  
  status.start_with?("trusted")
end

.trusted_ip?(ip) ⇒ Boolean

Check if an IP is trusted (any trusted_* status)

Examples:

if Reputable::Reputation.trusted_ip?("203.0.113.45")
  # Skip CAPTCHA
end

Parameters:

  • ip (String)

    IP address

Returns:

  • (Boolean)

    Returns false if disabled, nil lookup, or not trusted



192
193
194
195
196
197
# File 'lib/reputable/reputation.rb', line 192

def trusted_ip?(ip)
  status = lookup_ip(ip)
  return false if status.nil?
  
  status.start_with?("trusted")
end