Skip to content

Commit 6342342

Browse files
committed
hmac hardening
1 parent bcdf686 commit 6342342

File tree

2 files changed

+369
-5
lines changed

2 files changed

+369
-5
lines changed

lib/hooks/plugins/request_validator/hmac.rb

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,32 @@ def self.valid?(payload:, headers:, secret:, config:)
9090
return false if secret.nil? || secret.empty?
9191

9292
validator_config = build_config(config)
93-
normalized_headers = normalize_headers(headers)
9493

95-
# Get signature from headers
94+
# Security: Check raw headers BEFORE normalization to detect tampering
95+
return false unless headers.respond_to?(:each)
96+
9697
signature_header = validator_config[:header]
98+
99+
# Find the signature header with case-insensitive matching but preserve original value
100+
raw_signature = nil
101+
headers.each do |key, value|
102+
if key.to_s.downcase == signature_header.downcase
103+
raw_signature = value.to_s
104+
break
105+
end
106+
end
107+
108+
return false if raw_signature.nil? || raw_signature.empty?
109+
110+
# Security: Reject signatures with leading/trailing whitespace
111+
return false if raw_signature != raw_signature.strip
112+
113+
# Security: Reject signatures containing null bytes or other control characters
114+
return false if raw_signature.match?(/[\u0000-\u001f\u007f-\u009f]/)
115+
116+
# Now we can safely normalize headers for the rest of the validation
117+
normalized_headers = normalize_headers(headers)
97118
provided_signature = normalized_headers[signature_header.downcase]
98-
return false if provided_signature.nil? || provided_signature.empty?
99119

100120
# Validate timestamp if required (for services that include timestamp validation)
101121
if validator_config[:timestamp_header]
@@ -178,10 +198,13 @@ def self.valid_timestamp?(headers, config)
178198

179199
return false unless timestamp_value
180200

201+
# Security: Strict timestamp validation - must be only digits with no leading zeros
202+
return false unless timestamp_value.match?(/\A[1-9]\d*\z/) || timestamp_value == "0"
203+
181204
timestamp = timestamp_value.to_i
182205

183-
# Ensure timestamp is a valid integer
184-
return false unless timestamp.is_a?(Integer) && timestamp > 0
206+
# Ensure timestamp is a positive integer (reject zero and negative)
207+
return false unless timestamp > 0
185208

186209
current_time = Time.now.to_i
187210
tolerance = config[:timestamp_tolerance]

0 commit comments

Comments
 (0)