Skip to content

Add skill-gated cross-tab OTP email reader - #332

Merged
esokullu merged 6 commits into
mainfrom
codex/otp-cross-tab-helper
Sep 2, 2026
Merged

Add skill-gated cross-tab OTP email reader#332
esokullu merged 6 commits into
mainfrom
codex/otp-cross-tab-helper

Conversation

@esokullu

@esokullu esokullu commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Summary

  • expose one fixed read_email_verification_message tool only while the bundled OTP helper is active on Mid/Full
  • inspect an already-open signed-in supported webmail tab without exposing general tab listing or switching to the model
  • open a selected opaque message only in a temporary inactive clone, preserve the verification tab, and close the clone after the read
  • keep Compact unchanged and mirror behavior across Chrome and Firefox
  • fail closed on ambiguous mailboxes/messages, provider lookalikes, private-context crossing, destination churn, and stale candidates
  • document provider/trace exposure and the narrow exception to the general no-tab-management boundary

Validation

  • npm test
    • provider-limit checks passed
    • 33/33 toolbar-guard tests passed
    • 2192/2192 main tests passed
    • offline relevance benchmark completed
    • 60/60 injection-security checks passed
  • git diff --check

Dependency

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-02T13:33:10.060793Z 75c042a Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7cc8be227a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/chrome/src/agent/otp-email-tool.js Outdated
Comment on lines +68 to +69
const tokens = key.split(' ').filter(token => token.length >= 2 && !ignored.has(token));
return [...new Set([key, ...tokens].filter(Boolean))];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Require a discriminative service match

For multiword issuers, every token of length two or more is accepted and later matched with some, so common tokens such as of in “Bank of America” or me in “ID.me” classify unrelated inbox rows as service matches. Those rows' previews—and potentially unrelated verification codes—are then returned to the model, risking both incorrect OTP selection and disclosure of unrelated email content; require the complete normalized identity or at least one sufficiently discriminative token.

Useful? React with 👍 / 👎.

Comment thread src/chrome/src/agent/agent.js Outdated
Comment on lines +18687 to +18688
const directTree = await this._otpEmailMessageTree(mailboxTab.id, observed.tree);
if (observed.tree?.conversationRootRefId) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Detect open messages outside Gmail

When the selected Outlook, Yahoo, Proton, Fastmail, Zoho, Yandex, or iCloud tab already displays the requested message, this branch never recognizes it as a message because conversationRootRefId is only emitted by the Gmail-specific detector in src/chrome/src/content/accessibility-tree.js. The visible message is consequently parsed as an inbox: it either reports no matching message or clones the same message URL and tries to click a non-navigating article/link before timing out. Add provider-neutral trusted message-view detection or directly extract an already-open supported-provider message.

Useful? React with 👍 / 👎.

@esokullu
esokullu force-pushed the codex/otp-cross-tab-helper branch from 7cc8be2 to 817d4e8 Compare September 2, 2026 11:57
@esokullu

esokullu commented Sep 2, 2026

Copy link
Copy Markdown
Owner Author

@codex pls review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 817d4e82b3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/chrome/src/agent/otp-email-tool.js Outdated
const path = url.pathname;
const hash = url.hash;
switch (provider) {
case 'gmail': return /(?:^#|\/)(?:inbox|all|sent|starred|important|trash|spam|search|label\/[^/]+)\/[A-Za-z0-9_-]+/i.test(hash);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject Gmail search-result routes as messages

When Gmail is open on a search-results URL such as #search/github, this regex treats the search query itself as a message ID. _otpEmailMessageTree then sets detected even without a message root, so inspect returns a broad search-results excerpt as stage: "message" instead of presenting opaque candidates; this can expose content from several matching rows and cause an older or unrelated code to be selected. Require an additional path segment containing an actual thread ID for Gmail search routes.

Useful? React with 👍 / 👎.

@esokullu
esokullu force-pushed the codex/otp-cross-tab-helper branch from 817d4e8 to 75c042a Compare September 2, 2026 13:16
@esokullu

esokullu commented Sep 2, 2026

Copy link
Copy Markdown
Owner Author

@codex pls review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 75c042ab76

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/chrome/src/agent/agent.js Outdated
Comment on lines +18617 to +18619
const rootRef = String(tree?.conversationRootRefId || '').trim()
|| (messageRoute ? otpOpenMessageRootRef(tree?.pageContent, service) : '');
if (!rootRef) return { tree, detected: messageRoute, detection: messageRoute ? 'provider_route' : '' };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Require a scoped root before treating a route as a message

On a supported non-Gmail message URL where the accessibility tree has no unique article/document root, the new provider-route fallback marks the entire visible mailbox tree as a detected message. The subsequent excerpt search can match the requested service in an inbox/sidebar row and return that row's code or neighboring unrelated email text instead of the open message. A failed subtree read similarly falls back to the broad tree while remaining detected; require a successfully read trusted or unique semantic message root before returning stage: "message".

Useful? React with 👍 / 👎.

Comment on lines +18562 to +18563
_otpEmailSkillActive(tabId, mode = this._effectiveRunMode(tabId), tier = this._resolvePromptTier()) {
return this._activeSkillRecords(tabId, mode, tier).some(skill => skill.id === OTP_EMAIL_SKILL_ID);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Require the packaged skill provenance before granting mailbox access

When a restored/imported custom-skill record uses the ID otp-verification-code-helper, this ID-only check activates the privileged cross-tab mailbox reader even if the record is ordinary user or URL content. Skill normalization preserves caller-supplied IDs, and startup skips installing the packaged skill when that ID already exists, so a crafted configuration can replace the bundled instructions yet retain private mailbox access. Bind activation to the verified packaged built-in record and expected source, rather than the ID alone.

Useful? React with 👍 / 👎.

@esokullu

esokullu commented Sep 2, 2026

Copy link
Copy Markdown
Owner Author

@codex pls review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 75c042ab76

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +18804 to +18808
const click = await this.executeTool(helperTab.id, 'click_ax', {
ref_id: helperCandidate.clickRef,
expectedDocumentToken: helperRead.tree.documentToken,
expectedPageUrl: helperRead.tree.refScopeUrl || helperRead.liveUrl,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve Ask mode's read-only mailbox semantics

When open_message is used from Ask mode, this direct click_ax invocation bypasses the normal tool-loop capability gate, even though opening an unread message in Gmail, Outlook, and other supported providers commonly marks it read on the server. Using an inactive disposable tab does not isolate that account-level mutation, so a supposedly read-only Ask run can alter the user's mailbox without a click grant; require the normal consequential-action gate or use a genuinely non-mutating message-read path.

Useful? React with 👍 / 👎.

Comment thread src/chrome/src/agent/agent.js Outdated
Comment on lines +18621 to +18626
const subtree = await this.executeTool(targetTabId, 'get_accessibility_tree', {
ref_id: rootRef,
filter: 'all',
maxDepth: 15,
maxChars: 6000,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Consume Gmail thread continuations before extracting a code

When an expanded Gmail conversation exceeds 6,000 characters, this anchored read returns only page 1 with hasMore/nextPage, but _otpEmailMessageTree accepts it as the complete message tree and later returns a successful message result. In a resend thread, an older matching code can be present on page 1 while the newest code is on a later page, causing the agent to select an obsolete OTP despite the skill's newest-resend rule; follow the continuation to completion or fail closed on truncated message trees.

Useful? React with 👍 / 👎.

@esokullu

esokullu commented Sep 2, 2026

Copy link
Copy Markdown
Owner Author

@codex pls review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

esokullu and others added 2 commits September 2, 2026 22:52
open_message now dispatches only against a one-use grant minted by the
permission gate, so a call that never passed the capability x host prompt
cannot create a helper tab or click in the mailbox. The gate and the handler
also derive the session key the same way, from the raw service argument, so
astral characters can no longer split them across a UTF-16 slice.

Gmail routes are parsed instead of pattern matched: an open message needs a
thread-id-shaped tail, which keeps #inbox/p2 and #search/github/p2 out of the
message branch that returned a whole listing as one message. Threads opened
from a later page still resolve.

Mailbox reads peek the abort flag rather than consuming it, so a stop during a
read still ends the run. They also refuse to read a tab that is navigating away
from the mailbox, which stops a foreign URL from becoming the host the click
prompt is charged to. Failures after the helper tab opens now say the session
is gone and that inspect has to run again, and provider error strings lose
their accessibility refs before they reach the model.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GKsX7CYmaEp2rjeVs9EBAL
_otpEmailOpenFailure copied its input with a spread, so any field a helper
result happened to carry would have gone to the model along with the error.
Nothing reaches it that way today, but the results it wraps are the same shape
that carries the accessibility tree, the tab record, and the live mailbox URL,
and the model is not allowed to see any of those. It now builds the response
from a fixed set of fields instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GKsX7CYmaEp2rjeVs9EBAL
@esokullu
esokullu merged commit f136ebe into main Sep 2, 2026
4 of 5 checks passed
@esokullu
esokullu deleted the codex/otp-cross-tab-helper branch September 3, 2026 01:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant