Make WebAuthn clone detection actually block, and fix false positives
Two problems with sign-count clone detection: - suspicious_sign_count? flagged the case where both the stored and presented counts are 0. Most synced passkeys (Apple/Google) report 0 every time, so every legitimate sign-in was flagged — drowning real signals in noise. Per WebAuthn §6.1.1 a 0 counter means "no counter"; only flag when BOTH counts are non-zero and the new one does not advance. - On a suspicious count the controller only logged a warning and then continued to authenticate and overwrite the stored counter. A cloned credential therefore worked indefinitely. webauthn_verify now rejects the sign-in (no session, no counter update) and emails the user via a new SecurityMailer#suspicious_passkey_used. Tests cover the corrected classification (synced/first-use/normal vs equal/ decreasing) and the new alert email. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
29
test/models/webauthn_credential_test.rb
Normal file
29
test/models/webauthn_credential_test.rb
Normal file
@@ -0,0 +1,29 @@
|
||||
require "test_helper"
|
||||
|
||||
class WebauthnCredentialTest < ActiveSupport::TestCase
|
||||
# suspicious_sign_count?(new_sign_count) — clone detection per WebAuthn §6.1.1.
|
||||
# Build an in-memory credential with a given stored sign_count; no persistence
|
||||
# needed since the method only reads self.sign_count.
|
||||
def credential(stored:)
|
||||
WebauthnCredential.new(sign_count: stored)
|
||||
end
|
||||
|
||||
test "does not flag when the authenticator reports no counter (synced passkeys)" do
|
||||
# Both 0 -> authenticator doesn't implement a counter; must NOT be suspicious.
|
||||
refute credential(stored: 0).suspicious_sign_count?(0)
|
||||
# Stored 0, first real use.
|
||||
refute credential(stored: 0).suspicious_sign_count?(5)
|
||||
# Stored non-zero but authenticator now reports 0 -> no counter, not a clone.
|
||||
refute credential(stored: 5).suspicious_sign_count?(0)
|
||||
end
|
||||
|
||||
test "does not flag a normal increasing counter" do
|
||||
refute credential(stored: 5).suspicious_sign_count?(6)
|
||||
refute credential(stored: 1).suspicious_sign_count?(1000)
|
||||
end
|
||||
|
||||
test "flags a non-advancing counter as a possible clone" do
|
||||
assert credential(stored: 5).suspicious_sign_count?(5), "equal count is suspicious"
|
||||
assert credential(stored: 5).suspicious_sign_count?(3), "decreasing count is suspicious"
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user