fix(security): reduce timestamp validation window to ±5 minutes

BCH-01-011: Reduces replay attack window from ±1 hour to ±5 minutes.

The previous 1-hour window allowed extended replay attacks. A 5-minute
window is industry standard for replay protection while accommodating
reasonable clock drift.

- Updated InputValidator.validateTimestamp to use 300-second window
- Updated tests to verify new boundary conditions

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
jack
2026-01-12 10:46:34 -10:00
co-authored by Claude Opus 4.5
parent b84c36c6fa
commit 1563209797
2 changed files with 37 additions and 17 deletions
+5 -3
View File
@@ -52,11 +52,13 @@ struct InputValidator {
// MessageType/NoisePayloadType enums; keeping validator free of stale lists.
/// Validates timestamp is reasonable (not too far in past or future)
/// BCH-01-011: Reduced from ±1 hour to ±5 minutes to limit replay attack window
static func validateTimestamp(_ timestamp: Date) -> Bool {
let now = Date()
let oneHourAgo = now.addingTimeInterval(-3600)
let oneHourFromNow = now.addingTimeInterval(3600)
return timestamp >= oneHourAgo && timestamp <= oneHourFromNow
// 5 minutes = 300 seconds (industry standard for replay protection)
let fiveMinutesAgo = now.addingTimeInterval(-300)
let fiveMinutesFromNow = now.addingTimeInterval(300)
return timestamp >= fiveMinutesAgo && timestamp <= fiveMinutesFromNow
}
}
+32 -14
View File
@@ -131,6 +131,7 @@ struct InputValidatorTests {
}
// MARK: - Timestamp Validation Tests
// BCH-01-011: Window reduced from ±1 hour to ±5 minutes
@Test func currentTimestampIsValid() throws {
let now = Date()
@@ -138,31 +139,48 @@ struct InputValidatorTests {
#expect(result == true)
}
@Test func timestampWithinOneHourIsValid() throws {
@Test func timestampWithinFiveMinutesIsValid() throws {
// 2 minutes ago should be valid (within 5-minute window)
let twoMinutesAgo = Date().addingTimeInterval(-2 * 60)
let result = InputValidator.validateTimestamp(twoMinutesAgo)
#expect(result == true)
}
@Test func timestampThirtyMinutesAgoIsInvalid() throws {
// BCH-01-011: 30 minutes is now outside the 5-minute window
let thirtyMinutesAgo = Date().addingTimeInterval(-30 * 60)
let result = InputValidator.validateTimestamp(thirtyMinutesAgo)
#expect(result == true)
}
@Test func timestampTwoHoursAgoIsInvalid() throws {
let twoHoursAgo = Date().addingTimeInterval(-2 * 3600)
let result = InputValidator.validateTimestamp(twoHoursAgo)
#expect(result == false)
}
@Test func timestampTwoHoursInFutureIsInvalid() throws {
let twoHoursFromNow = Date().addingTimeInterval(2 * 3600)
let result = InputValidator.validateTimestamp(twoHoursFromNow)
@Test func timestampTenMinutesAgoIsInvalid() throws {
// 10 minutes is outside the 5-minute window
let tenMinutesAgo = Date().addingTimeInterval(-10 * 60)
let result = InputValidator.validateTimestamp(tenMinutesAgo)
#expect(result == false)
}
@Test func timestampAtOneHourBoundaryIsValid() throws {
// Just slightly within the one-hour window
let almostOneHourAgo = Date().addingTimeInterval(-3599)
let result = InputValidator.validateTimestamp(almostOneHourAgo)
@Test func timestampTenMinutesInFutureIsInvalid() throws {
// 10 minutes in future is outside the 5-minute window
let tenMinutesFromNow = Date().addingTimeInterval(10 * 60)
let result = InputValidator.validateTimestamp(tenMinutesFromNow)
#expect(result == false)
}
@Test func timestampAtFiveMinuteBoundaryIsValid() throws {
// Just slightly within the five-minute window (299 seconds)
let almostFiveMinutesAgo = Date().addingTimeInterval(-299)
let result = InputValidator.validateTimestamp(almostFiveMinutesAgo)
#expect(result == true)
}
@Test func timestampJustOutsideFiveMinuteWindowIsInvalid() throws {
// Just outside the five-minute window (301 seconds)
let justOverFiveMinutesAgo = Date().addingTimeInterval(-301)
let result = InputValidator.validateTimestamp(justOverFiveMinutesAgo)
#expect(result == false)
}
// MARK: - Edge Cases
@Test func singleCharacterStringIsAccepted() throws {