Skip to content

Commit f8888c7

Browse files
committed
Keep ambiguous CAPTCHA observations gated
1 parent d8cf374 commit f8888c7

3 files changed

Lines changed: 28 additions & 44 deletions

File tree

src/chrome/src/agent/agent.js

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2956,23 +2956,12 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
29562956
? recheck.challenge.frameId
29572957
: null;
29582958
observedChallengeFrameUrl = String(recheck.challenge.frameUrl || '');
2959-
} else if (
2960-
recheck.inspectionComplete
2961-
&& recheck.hiddenChallenges?.some(hidden => (
2962-
hidden.frameId === (
2963-
Number.isInteger(observedChallengeFrameId)
2964-
? observedChallengeFrameId
2965-
: 0
2966-
)
2967-
&& hidden.normalizedLabel === challenge.normalizedLabel
2968-
))
2969-
) {
2970-
challenge = null;
29712959
}
2972-
// Only a completed scan that found the same dialog hidden in the same
2973-
// frame clears the tree observation. An unrelated hidden challenge,
2974-
// "not found", or an inconclusive scan cannot disprove a dialog in a
2975-
// closed shadow root or unreachable frame; keep the gate fail-closed.
2960+
// A DOM scan cannot prove that the tree-observed dialog is the same
2961+
// element as a hidden match: a page may retain a hidden template while
2962+
// rendering the active challenge in a closed shadow root. Hidden,
2963+
// missing, and inconclusive scan results therefore remain diagnostic
2964+
// only and keep the gate fail-closed.
29762965
}
29772966
let pageUrl = String(toolResult.currentUrl || toolResult.pageUrl || '');
29782967
if (!pageUrl) {

src/firefox/src/agent/agent.js

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2617,23 +2617,12 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
26172617
? recheck.challenge.frameId
26182618
: null;
26192619
observedChallengeFrameUrl = String(recheck.challenge.frameUrl || '');
2620-
} else if (
2621-
recheck.inspectionComplete
2622-
&& recheck.hiddenChallenges?.some(hidden => (
2623-
hidden.frameId === (
2624-
Number.isInteger(observedChallengeFrameId)
2625-
? observedChallengeFrameId
2626-
: 0
2627-
)
2628-
&& hidden.normalizedLabel === challenge.normalizedLabel
2629-
))
2630-
) {
2631-
challenge = null;
26322620
}
2633-
// Only a completed scan that found the same dialog hidden in the same
2634-
// frame clears the tree observation. An unrelated hidden challenge,
2635-
// "not found", or an inconclusive scan cannot disprove a dialog in a
2636-
// closed shadow root or unreachable frame; keep the gate fail-closed.
2621+
// A DOM scan cannot prove that the tree-observed dialog is the same
2622+
// element as a hidden match: a page may retain a hidden template while
2623+
// rendering the active challenge in a closed shadow root. Hidden,
2624+
// missing, and inconclusive scan results therefore remain diagnostic
2625+
// only and keep the gate fail-closed.
26372626
}
26382627
let pageUrl = String(toolResult.currentUrl || toolResult.pageUrl || '');
26392628
if (!pageUrl) {

test/run.js

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4752,7 +4752,7 @@ test('CAPTCHA dialog parsing handles descendant-only labels and escaped quotes',
47524752
}
47534753
});
47544754

4755-
test('CAPTCHA mutation preflight ignores hidden and off-viewport verification dialogs', async () => {
4755+
test('CAPTCHA preflight ignores hidden dialogs while ambiguous all-tree reads fail closed', async () => {
47564756
for (const [build, gate, AgentClass] of [
47574757
['chrome', CaptchaGateCh, AgentCh],
47584758
['firefox', CaptchaGateFx, AgentFx],
@@ -4787,8 +4787,15 @@ test('CAPTCHA mutation preflight ignores hidden and off-viewport verification di
47874787
},
47884788
{},
47894789
);
4790-
assert.equal(observation.gate, null, `${build}: hidden dialog from an all-tree read armed the gate`);
4791-
assert.equal(agent._captchaGateStates.has(1), false, `${build}: hidden all-tree dialog persisted a gate`);
4790+
assert.ok(
4791+
observation.gate,
4792+
`${build}: ambiguous all-tree challenge was cleared by non-unique hidden evidence`,
4793+
);
4794+
assert.equal(
4795+
agent._captchaGateStates.has(1),
4796+
true,
4797+
`${build}: ambiguous all-tree challenge failed open`,
4798+
);
47924799
});
47934800
}
47944801
await withCaptchaFakePage(build, [
@@ -4918,8 +4925,8 @@ test('CAPTCHA visibility recheck sees shadow-rooted dialogs and keeps the gate o
49184925
assert.ok(observation.gate, `${build}: unrelated hidden challenge cleared the tree-only CAPTCHA gate`);
49194926
assert.equal(agent._captchaGateStates.has(1), true, `${build}: unrelated hidden challenge failed open`);
49204927

4921-
const matchingAgent = new AgentClass({});
4922-
const matchingObservation = await matchingAgent._observeCaptchaChallenge(
4928+
const sameLabelAgent = new AgentClass({});
4929+
const sameLabelObservation = await sameLabelAgent._observeCaptchaChallenge(
49234930
1,
49244931
'get_accessibility_tree',
49254932
{
@@ -4928,15 +4935,14 @@ test('CAPTCHA visibility recheck sees shadow-rooted dialogs and keeps the gate o
49284935
},
49294936
{},
49304937
);
4931-
assert.equal(
4932-
matchingObservation.gate,
4933-
null,
4934-
`${build}: matching hidden evidence did not clear the tree observation`,
4938+
assert.ok(
4939+
sameLabelObservation.gate,
4940+
`${build}: same-label hidden template cleared the tree-only CAPTCHA gate`,
49354941
);
49364942
assert.equal(
4937-
matchingAgent._captchaGateStates.has(1),
4938-
false,
4939-
`${build}: matching hidden evidence persisted a stale gate`,
4943+
sameLabelAgent._captchaGateStates.has(1),
4944+
true,
4945+
`${build}: same-label hidden template failed open`,
49404946
);
49414947
});
49424948

0 commit comments

Comments
 (0)