Stop reporting blocked LinkedIn responses as free usernames (#3173)

LinkedIn's 999 and its reCAPTCHA interstitial both came out as confident results, one as a free name, the other as a claimed one. Both are errors now.
This commit is contained in:
Soxoj
2026-09-17 17:29:20 +02:00
committed by GitHub
parent 7b76721413
commit 41a0ccebd5
4 changed files with 50 additions and 12 deletions
+6 -5
View File
@@ -40,11 +40,12 @@ def detect_error_page(
return CheckError("Rate limited", "429 status code")
if status_code == 999:
# LinkedIn's "Request denied", served to blocked clients for existing
# and non-existing profiles alike, so it is not really a not-found.
# Classifying it as an error would flip LinkedIn to UNKNOWN for every
# blocked IP, which reads as "LinkedIn is broken" to users. Kept as a
# pass-through on purpose: the caller's checkType branch decides.
return None
# and non-existing profiles alike. Falling through to the checkType
# branch turned every profile into AVAILABLE, so a blocked client was
# told that williamhgates has no LinkedIn. "We could not check" is the
# honest answer here; the cost is that a blocked IP now sees an error
# instead of a confident wrong "Not found!".
return CheckError("Access denied", "999 status code")
if status_code >= 500:
return CheckError("Server", f"{status_code} status code")
return None
+6
View File
@@ -84,6 +84,12 @@ COMMON_ERRORS = {
'Captcha', 'Google rate-limit / captcha'
),
'id="gs_captcha_f"': CheckError('Captcha', 'Google rate-limit / captcha'),
# Google reCAPTCHA interstitial: answers HTTP 200 on every path, so on a
# status_code site every username reads as claimed (linkedin.com). Matches
# the challenge page itself, not a login form that merely embeds a widget.
'google.com/recaptcha/challengepage/': CheckError(
'Captcha', 'Google reCAPTCHA interstitial'
),
}
PROXY_RECOMMENDATION = (
+7 -3
View File
@@ -149,9 +149,13 @@ def test_detect_error_page_403_ignored():
def test_detect_error_page_999_linkedin():
# LinkedIn returns 999 on bot suspicion. Deliberately not an error: making
# it one turns LinkedIn UNKNOWN for every blocked IP.
assert detect_error_page("", 999, {}, ignore_403=False) is None
# LinkedIn returns 999 on bot suspicion, for real and made-up profiles
# alike. It has to be an error: passing it through made every profile
# look free to a blocked client.
err = detect_error_page("", 999, {}, ignore_403=False)
assert err is not None
assert err.type == "Access denied"
assert "999" in err.desc
def test_detect_error_page_500():
+31 -4
View File
@@ -40,10 +40,6 @@ def test_http_429_is_rate_limit():
assert err.type == "Rate limited"
def test_ignore_linkedin_999_status():
# 999 stays a pass-through on purpose, see detect_error_page.
assert detect_error_page("", 999, {}, ignore_403=False) is None
def test_pow_challenge_pages_are_bot_protection():
# Both serve a 2xx on every path, so without a marker every username
# would read as claimed.
@@ -59,3 +55,34 @@ def test_pow_challenge_pages_are_bot_protection():
assert anubis.type == "Bot protection"
assert pow_js.type == "Bot protection"
def test_linkedin_999_is_an_error_not_an_absence():
# LinkedIn serves 999 to blocked clients for existing and non-existing
# profiles alike. Passing it through to the status_code branch reported
# every profile as free, so a blocked user was told that a real account
# does not exist.
err = detect_error_page("", 999, {}, ignore_403=False)
assert err is not None
assert err.type == "Access denied"
def test_google_recaptcha_interstitial_is_a_captcha():
# Answers 200 on every path, so without a marker every username reads as
# claimed on a status_code site.
err = detect_error_page(
'<title>Checking your browser - reCAPTCHA</title>'
'<script src="https://www.google.com/recaptcha/challengepage/x"></script>',
200, {}, ignore_403=False,
)
assert err is not None
assert err.type == "Captcha"
def test_page_embedding_a_recaptcha_widget_is_not_an_error():
# A login form that loads the reCAPTCHA script is an ordinary page; only
# the challenge interstitial counts as a block.
assert detect_error_page(
'<form><script src="https://www.google.com/recaptcha/enterprise.js"></script></form>',
200, {}, ignore_403=False,
) is None