Skip to content

Commit 9dda6fd

Browse files
committed
recognize branded CAPTCHA dialogs
1 parent d6b9ec1 commit 9dda6fd

7 files changed

Lines changed: 24 additions & 36 deletions

File tree

src/chrome/src/agent/agent.js

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2966,13 +2966,7 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
29662966
})
29672967
: { kind: 'none' };
29682968
if (!challenge) {
2969-
const remainingDialogSurface = /^(?:\s*)(?:dialog|alertdialog)(?=\s|$)/im
2970-
.test(toolResult.pageContent)
2971-
|| toolResult.pageGate?.surface === 'dialog';
2972-
const failedSolveMustRemainManual = activeGate?.status === 'manual_required'
2973-
&& activeGate.publicGate?.solveFailedToClearChallenge === true
2974-
&& remainingDialogSurface;
2975-
if (activeGate && authoritativeRootRead && !failedSolveMustRemainManual) {
2969+
if (activeGate && authoritativeRootRead) {
29762970
const clearedGate = {
29772971
...activeGate.publicGate,
29782972
status: 'cleared',

src/chrome/src/agent/captcha-frame-runtime.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,7 @@ export function detectCaptchaCandidatesInPage(scope = null) {
644644
return false;
645645
}
646646
};
647-
const challengeDialogRe = /\b(?:captcha|security verification|human verification|verify (?:that )?you(?:'|\u2019)re (?:a )?human|verify (?:that )?you are (?:a )?human|are you (?:a )?human|robot check|challenge verification)\b/i;
647+
const challengeDialogRe = /\b(?:(?:re|h|fun)?captcha|security verification|human verification|verify (?:that )?you(?:'|\u2019)re (?:a )?human|verify (?:that )?you are (?:a )?human|are you (?:a )?human|robot check|challenge verification)\b/i;
648648
const challengeDialogs = Array.from(
649649
pageDocument.querySelectorAll('dialog, [role="dialog"], [role="alertdialog"]')
650650
).filter((element) => {

src/chrome/src/agent/captcha-gate.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
const CHALLENGE_DIALOG_RE = /\b(?:captcha|security verification|human verification|verify (?:that )?you(?:'|\u2019)re (?:a )?human|verify (?:that )?you are (?:a )?human|are you (?:a )?human|robot check|challenge verification)\b/i;
1+
const CHALLENGE_DIALOG_RE = /\b(?:(?:re|h|fun)?captcha|security verification|human verification|verify (?:that )?you(?:'|\u2019)re (?:a )?human|verify (?:that )?you are (?:a )?human|are you (?:a )?human|robot check|challenge verification)\b/i;
22
const CHALLENGE_FAILURE_RE = /\b(?:verification (?:failed|error|unsuccessful|expired|timed out)|could not verify|unable to verify)\b/i;
3-
const CHALLENGE_CONTEXT_RE = /\b(?:captcha|human|robot|challenge)\b/i;
3+
const CHALLENGE_CONTEXT_RE = /\b(?:(?:re|h|fun)?captcha|human|robot|challenge)\b/i;
44

55
function matchesChallengeLabel(value, allowGenericFailure = false) {
66
const text = String(value || '');
@@ -94,9 +94,9 @@ export function detectChallengeDialogInPage(options = null) {
9494
? { challenge: null, frameContext: { frameUrl, frameName, childFrames: [] } }
9595
: null;
9696
}
97-
const challengeRe = /\b(?:captcha|security verification|human verification|verify (?:that )?you(?:'|\u2019)re (?:a )?human|verify (?:that )?you are (?:a )?human|are you (?:a )?human|robot check|challenge verification)\b/i;
97+
const challengeRe = /\b(?:(?:re|h|fun)?captcha|security verification|human verification|verify (?:that )?you(?:'|\u2019)re (?:a )?human|verify (?:that )?you are (?:a )?human|are you (?:a )?human|robot check|challenge verification)\b/i;
9898
const challengeFailureRe = /\b(?:verification (?:failed|error|unsuccessful|expired|timed out)|could not verify|unable to verify)\b/i;
99-
const challengeContextRe = /\b(?:captcha|human|robot|challenge)\b/i;
99+
const challengeContextRe = /\b(?:(?:re|h|fun)?captcha|human|robot|challenge)\b/i;
100100
const matchesChallenge = value => {
101101
const text = String(value || '');
102102
return challengeRe.test(text)

src/firefox/src/agent/agent.js

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2632,13 +2632,7 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
26322632
})
26332633
: { kind: 'none' };
26342634
if (!challenge) {
2635-
const remainingDialogSurface = /^(?:\s*)(?:dialog|alertdialog)(?=\s|$)/im
2636-
.test(toolResult.pageContent)
2637-
|| toolResult.pageGate?.surface === 'dialog';
2638-
const failedSolveMustRemainManual = activeGate?.status === 'manual_required'
2639-
&& activeGate.publicGate?.solveFailedToClearChallenge === true
2640-
&& remainingDialogSurface;
2641-
if (activeGate && authoritativeRootRead && !failedSolveMustRemainManual) {
2635+
if (activeGate && authoritativeRootRead) {
26422636
const clearedGate = {
26432637
...activeGate.publicGate,
26442638
status: 'cleared',

src/firefox/src/agent/captcha-frame-runtime.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,7 @@ export function detectCaptchaCandidatesInPage(scope = null) {
644644
return false;
645645
}
646646
};
647-
const challengeDialogRe = /\b(?:captcha|security verification|human verification|verify (?:that )?you(?:'|\u2019)re (?:a )?human|verify (?:that )?you are (?:a )?human|are you (?:a )?human|robot check|challenge verification)\b/i;
647+
const challengeDialogRe = /\b(?:(?:re|h|fun)?captcha|security verification|human verification|verify (?:that )?you(?:'|\u2019)re (?:a )?human|verify (?:that )?you are (?:a )?human|are you (?:a )?human|robot check|challenge verification)\b/i;
648648
const challengeDialogs = Array.from(
649649
pageDocument.querySelectorAll('dialog, [role="dialog"], [role="alertdialog"]')
650650
).filter((element) => {

src/firefox/src/agent/captcha-gate.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
const CHALLENGE_DIALOG_RE = /\b(?:captcha|security verification|human verification|verify (?:that )?you(?:'|\u2019)re (?:a )?human|verify (?:that )?you are (?:a )?human|are you (?:a )?human|robot check|challenge verification)\b/i;
1+
const CHALLENGE_DIALOG_RE = /\b(?:(?:re|h|fun)?captcha|security verification|human verification|verify (?:that )?you(?:'|\u2019)re (?:a )?human|verify (?:that )?you are (?:a )?human|are you (?:a )?human|robot check|challenge verification)\b/i;
22
const CHALLENGE_FAILURE_RE = /\b(?:verification (?:failed|error|unsuccessful|expired|timed out)|could not verify|unable to verify)\b/i;
3-
const CHALLENGE_CONTEXT_RE = /\b(?:captcha|human|robot|challenge)\b/i;
3+
const CHALLENGE_CONTEXT_RE = /\b(?:(?:re|h|fun)?captcha|human|robot|challenge)\b/i;
44

55
function matchesChallengeLabel(value, allowGenericFailure = false) {
66
const text = String(value || '');
@@ -94,9 +94,9 @@ export function detectChallengeDialogInPage(options = null) {
9494
? { challenge: null, frameContext: { frameUrl, frameName, childFrames: [] } }
9595
: null;
9696
}
97-
const challengeRe = /\b(?:captcha|security verification|human verification|verify (?:that )?you(?:'|\u2019)re (?:a )?human|verify (?:that )?you are (?:a )?human|are you (?:a )?human|robot check|challenge verification)\b/i;
97+
const challengeRe = /\b(?:(?:re|h|fun)?captcha|security verification|human verification|verify (?:that )?you(?:'|\u2019)re (?:a )?human|verify (?:that )?you are (?:a )?human|are you (?:a )?human|robot check|challenge verification)\b/i;
9898
const challengeFailureRe = /\b(?:verification (?:failed|error|unsuccessful|expired|timed out)|could not verify|unable to verify)\b/i;
99-
const challengeContextRe = /\b(?:captcha|human|robot|challenge)\b/i;
99+
const challengeContextRe = /\b(?:(?:re|h|fun)?captcha|human|robot|challenge)\b/i;
100100
const matchesChallenge = value => {
101101
const text = String(value || '');
102102
return challengeRe.test(text)

test/run.js

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4725,6 +4725,11 @@ test('CAPTCHA dialog parsing handles descendant-only labels and escaped quotes',
47254725
label: 'Verify that you\u2019re a human',
47264726
normalized: /verify that you re a human/,
47274727
},
4728+
{
4729+
tree: 'dialog "reCAPTCHA challenge" [ref_8]',
4730+
label: 'reCAPTCHA challenge',
4731+
normalized: /recaptcha challenge/,
4732+
},
47284733
];
47294734
for (const [build, gate] of [['chrome', CaptchaGateCh], ['firefox', CaptchaGateFx]]) {
47304735
for (const example of cases) {
@@ -4776,6 +4781,11 @@ test('CAPTCHA mutation preflight ignores hidden and off-viewport verification di
47764781
], async () => {
47774782
assert.equal(gate.detectChallengeDialogInPage()?.label, 'Verify you are a human', `${build}: article-bearing challenge dialog was missed`);
47784783
});
4784+
await withCaptchaFakePage(build, [
4785+
captchaEl('div', { role: 'dialog', innerText: 'reCAPTCHA' }),
4786+
], async () => {
4787+
assert.equal(gate.detectChallengeDialogInPage()?.label, 'reCAPTCHA', `${build}: branded reCAPTCHA dialog was missed`);
4788+
});
47794789
await withCaptchaFakePage(build, [
47804790
captchaEl('div', { role: 'dialog', innerText: 'Email verification failed' }),
47814791
], async () => {
@@ -4969,18 +4979,8 @@ test('one CAPTCHA solve remains gated until a root read confirms clearance', asy
49694979
},
49704980
{ filter: 'visible' },
49714981
);
4972-
assert.equal(changedManual.gate?.status, 'manual_required', `${label}: changed dialog reset a failed solve to a paid solve`);
4973-
const manualCompletion = await agent._observeCaptchaChallenge(
4974-
tabId,
4975-
'get_accessibility_tree',
4976-
{
4977-
pageContent: 'main [ref_930]\n heading "Welcome" [ref_931]',
4978-
pageUrl: 'https://example.test/identity-check',
4979-
},
4980-
{ filter: 'visible' },
4981-
);
4982-
assert.equal(manualCompletion.gate?.status, 'cleared', `${label}: dialog-free root read did not clear the manually completed CAPTCHA`);
4983-
assert.equal(agent._captchaGateStates.has(tabId), false, `${label}: manually completed CAPTCHA gate remained persisted`);
4982+
assert.equal(changedManual.gate?.status, 'cleared', `${label}: unrelated post-CAPTCHA dialog kept the failed solve gated`);
4983+
assert.equal(agent._captchaGateStates.has(tabId), false, `${label}: unrelated post-CAPTCHA dialog left a persisted gate`);
49844984

49854985
const confirmationAgent = new AgentClass({});
49864986
confirmationAgent._captchaGateStates.set(tabId, {

0 commit comments

Comments
 (0)