Class: VectorMCP::Security::AuthenticationRateLimiter

Inherits:
Object
  • Object
show all
Defined in:
lib/vector_mcp/security/authentication_rate_limiter.rb

Overview

In-process limiter for repeated authentication failures.

Attempts are tracked independently by transport-provided remote address and by a SHA-256 fingerprint of the presented credential. Raw credentials are never retained. Storage is bounded to avoid turning the limiter into a memory-exhaustion primitive.

Constant Summary collapse

DEFAULT_MAX_ATTEMPTS =
10
DEFAULT_WINDOW_SECONDS =
60
DEFAULT_MAX_ENTRIES =
10_000
CREDENTIAL_HEADERS =
%w[Authorization X-API-Key X-JWT-Token].freeze
CREDENTIAL_PARAMS =
%w[api_key apikey jwt_token token].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(max_attempts: DEFAULT_MAX_ATTEMPTS, window_seconds: DEFAULT_WINDOW_SECONDS, max_entries: DEFAULT_MAX_ENTRIES, clock: nil) ⇒ AuthenticationRateLimiter

Returns a new instance of AuthenticationRateLimiter.

Parameters:

  • max_attempts (Integer) (defaults to: DEFAULT_MAX_ATTEMPTS)

    failures allowed per identifier and window

  • window_seconds (Numeric) (defaults to: DEFAULT_WINDOW_SECONDS)

    rolling failure window in seconds

  • max_entries (Integer) (defaults to: DEFAULT_MAX_ENTRIES)

    maximum number of tracked identifiers

  • clock (#call, nil) (defaults to: nil)

    monotonic time source (primarily for testing)



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/vector_mcp/security/authentication_rate_limiter.rb', line 29

def initialize(max_attempts: DEFAULT_MAX_ATTEMPTS,
               window_seconds: DEFAULT_WINDOW_SECONDS,
               max_entries: DEFAULT_MAX_ENTRIES,
               clock: nil)
  validate_positive_integer!(:max_attempts, max_attempts)
  validate_positive_number!(:window_seconds, window_seconds)
  validate_positive_integer!(:max_entries, max_entries)

  @max_attempts = max_attempts
  @window_seconds = window_seconds
  @max_entries = max_entries
  @clock = clock || -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) }
  @attempts = {}
  @mutex = Mutex.new
  @logger = VectorMCP.logger_for("security.auth_rate_limit")
end

Instance Attribute Details

#max_attemptsObject (readonly)

Returns the value of attribute max_attempts.



23
24
25
# File 'lib/vector_mcp/security/authentication_rate_limiter.rb', line 23

def max_attempts
  @max_attempts
end

#max_entriesObject (readonly)

Returns the value of attribute max_entries.



23
24
25
# File 'lib/vector_mcp/security/authentication_rate_limiter.rb', line 23

def max_entries
  @max_entries
end

#window_secondsObject (readonly)

Returns the value of attribute window_seconds.



23
24
25
# File 'lib/vector_mcp/security/authentication_rate_limiter.rb', line 23

def window_seconds
  @window_seconds
end

Instance Method Details

#check!(request) ⇒ void

This method returns an undefined value.

Atomically reserve an attempt, raising when any request identifier is blocked.

Parameters:

Raises:



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/vector_mcp/security/authentication_rate_limiter.rb', line 50

def check!(request)
  context = VectorMCP::RequestContext.coerce(request)
  current_time = now
  identifiers = request_identifiers(context)
  retry_after = @mutex.synchronize do
    blocked_for = identifiers.filter_map do |identifier|
      timestamps = active_timestamps(identifier, current_time)
      next unless timestamps.length >= @max_attempts

      timestamps.first + @window_seconds - current_time
    end.max
    unless blocked_for
      identifiers.each do |identifier|
        timestamps = active_timestamps(identifier, current_time)
        timestamps << current_time
        store(identifier, timestamps)
      end
    end
    blocked_for
  end
  return unless retry_after

  seconds = [retry_after.ceil, 1].max
  log_rate_limit(context, seconds)
  raise VectorMCP::RateLimitExceededError.new(retry_after: seconds)
end

#record_failure(request) ⇒ void

This method returns an undefined value.

Record an unsuccessful authentication result for the reserved attempt.

Parameters:



80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/vector_mcp/security/authentication_rate_limiter.rb', line 80

def record_failure(request)
  context = VectorMCP::RequestContext.coerce(request)
  current_time = now
  threshold_reached = @mutex.synchronize do
    request_identifiers(context).filter_map do |identifier|
      timestamps = active_timestamps(identifier, current_time)
      identifier_scope(identifier) if timestamps.length == @max_attempts
    end
  end

  log_failure_threshold(context, threshold_reached) unless threshold_reached.empty?
end

#record_success(request) ⇒ void

This method returns an undefined value.

Clear failure history after successful authentication.

Parameters:



96
97
98
99
# File 'lib/vector_mcp/security/authentication_rate_limiter.rb', line 96

def record_success(request)
  identifiers = request_identifiers(VectorMCP::RequestContext.coerce(request))
  @mutex.synchronize { identifiers.each { |identifier| @attempts.delete(identifier) } }
end

#statusHash

Returns non-sensitive limiter status.

Returns:

  • (Hash)

    non-sensitive limiter status



102
103
104
105
106
107
108
109
110
111
# File 'lib/vector_mcp/security/authentication_rate_limiter.rb', line 102

def status
  tracked_entries = @mutex.synchronize { @attempts.length }
  {
    enabled: true,
    max_attempts: @max_attempts,
    window_seconds: @window_seconds,
    max_entries: @max_entries,
    tracked_entries: tracked_entries
  }
end