Repository navigation
fix(desktop): pin fill_cloud_connector_form to Omi's own MCP endpoint and ChatGPT's connector page - #21330
Conversation
… and ChatGPT's connector page fill_cloud_connector_form runs without an approval card on the grounds that it only fills a Claude or ChatGPT "add custom connector" form that is already open. server_url and the OAuth URLs were only checked for being non-empty, and the ChatGPT path did not confirm it was on ChatGPT's connector page, so that bound held only for the Claude path. The tool now validates its arguments before any UI step and refuses with a stable code when server_url is not this build's Omi MCP endpoint (server_url_not_omi), or when auth_url or token_url, if given, are not Omi's OAuth authorize and token endpoints (auth_url_not_omi, token_url_not_omi). The pinned endpoints come from MemoryExportDestination, the same source the connector setup uses. Matching is exact: https only, the same host, port and path, and no user info, query or fragment. The form always receives the pinned spelling, never the caller's. The ChatGPT path now looks only at supported browser windows whose text shows a chatgpt.com connector settings URL (exact host), and only inside the dialog that holds the connector form. It refuses with chatgpt_connector_form_not_verified otherwise and no longer scans other apps. Fields are filled inside that dialog, and when a field refuses a direct value the paste fallback runs only while that browser is frontmost and that field has focus. With submit it presses only the one button inside it whose whole label is the form's Add or Create label, after re-verifying the page and the form. The Claude path is unchanged apart from receiving the pinned URL. The argument checks and the pure page and form checks live in a new CloudConnectorFormRequest.swift, an extension of CloudConnectorFormAutomation, so the automation file does not grow.
|
Reviewed the full diff. This is a genuine security tightening of
Two things need a human maintainer before merge — both are judgment calls, not defects:
Code quality is high and CI is green at head. Leaving for maintainer review on those two questions rather than blocking on anything in the diff itself. by AI on behalf of David — if you need David’s attention urgently, please @Git-on-my-level and escalate with |
|
Thanks. On point 2: you're right that the ChatGPT route strings and the Add/Create label set are provider knowledge in the binary, which INV-INT-1 says not to add. I'd rather not ask for an exception to a locked invariant, so here are two ways to keep the fix inside it:
Either way the Claude path and the endpoint pin stay as they are. Tell me which you'd like, or whether the invariant owner is fine with the current narrowing reading, and I'll update the PR. On point 1, agreed that it's a maintainer's call. |
What changed and why
fill_cloud_connector_formfills the "add a custom connector" form in Claude or ChatGPT so the person can connect Omi's own MCP server. It runs without an approval card, as a documented exception in the approval work for #20938. That exception says the tool only fills a connector form that is already open. While reviewing #20975, the maintainers' reviewer noted that the tool "still takes an arbitraryserver_urland can press submit".That was a fair reading. The URL wasn't pinned, and the ChatGPT path didn't confirm the page, so the exception comment overstated the bound. It held only for the Claude path. This PR tightens the tool so the stated bound holds on both paths. It does not add an approval card.
1. Only Omi's own endpoints
The tool now checks its arguments before any UI step. Nothing is focused, typed or clicked when it refuses.
server_urlmust be this build's Omi MCP endpoint,MemoryExportDestination.mcpServerURL. That is the same value the "Connect your AI" setup passes, built from the backend environment for the current app (for examplehttps://api.omi.me/v1/mcpon the stable app). Anything else is refused withserver_url_not_omi.auth_urlandtoken_url, when given, must be Omi's OAuth endpoints,MemoryExportDestination.mcpAuthorizeURLandmcpTokenURL. Anything else is refused withauth_url_not_omiortoken_url_not_omi.:443cannot change what is typed.Error: <code>: this tool only enters Omi's own endpoints, so <field> must be <url>. Nothing was typed.A development build whose backend URL is plain http accepts no
server_url. That build was never a target for the hosted connectors.2. ChatGPT: only ChatGPT's connector form
The ChatGPT path now finds the form the way the Claude path does:
chatgpt.comconnector settings URL (chatgpt.com/#settings/Connectors…or#settings/Apps…, with or withouthttps://). The host must be exactlychatgpt.com. A look-alike host, a path that only mentionschatgpt.com, plain http, and other ChatGPT pages are refused.chatgpt_connector_form_not_verifiedand touches nothing.3. Submit stays opt-in and bound to the form
submitis still off unless the caller asks for it. On ChatGPT the tool re-checks the page and the form before pressing anything. It then presses only the one button inside that dialog whose whole label is the form's own Add or Create label (Add,Create,Create app, and the like). Before, it pressed the first button anywhere in the window whose label merely contained "add", "connect", "create" or "save". If no button or more than one button matches, nothing is pressed, and the result says "Submit skipped".The Claude path is unchanged apart from receiving the pinned URL.
The argument checks and the pure page and form checks are in a new
CloudConnectorFormRequest.swift, an extension ofCloudConnectorFormAutomation, so the automation file gets shorter rather than longer.The exception comment
The comment for this exception in
run-tool-capability.test.tsis part of the approval stack (#20973 / #20975) and is not onmain, so this PR cannot edit it. Once this lands, that comment should say exactly what is now enforced:Product invariants affected
INV-INT-1(product/invariants/integrations.md) is named becauseCloudConnectorFormAutomation.swiftmatches**/*CloudConnector*. This change only narrows what the existing automation may do. It adds refusals: the endpoint pin, the chatgpt.com page check, the dialog scope and the exact submit label. It adds no new way to find or click an element, and no new OCR. The ChatGPT page check compiles in ChatGPT's connector settings route, just as the Claude path already compiles inclaude.ai/customize/connectors. If ChatGPT moves that page, the tool refuses rather than filling an unverified page.How it was verified
xcrun swift build --build-testsindesktop/macos/Desktop: passes.xcrun swift test --filter 'CloudConnectorFormEndpointPinTests|BrowserAutomationTargetTests|SpatialOverlayRenderGeometryTests|SpatialOverlayDogfoodHarnessTests'ran 60 tests with 0 failures. That is the 11 new tests plus the existing Claude connector, setup-result and overlay tests.generate-tool-surfaces.mjs --checkreports "all outputs match".xcrun swift test --skip SubscriptionEntitlementServiceTestsindesktop/macos/Desktop): 7889 tests, 10 skipped, 2 failures. Both failures are known and unrelated to this change:DesktopAutomationWindowPresentationTests.testQuietPresentationAlsoParksNonactivatingOverlayPanels(display geometry) andRecentScreensMenuContentTests.testDayOldFrameShowsACalendarDateNotAClockTime(an Indian locale date format).PRE_PUSH_BASE_REMOTE=upstream OMI_PR_BODY_FILE=<this body> scripts/pre-push-singleflight upstream. It reports "PR preflight passed: 30 checks" and "Bounded pre-push gate passed".Packaged-app check (to run by hand). Use a packaged build of this branch with Accessibility granted.
chatgpt_connector_form_not_verifiedand nothing typed.Tests
CloudConnectorFormEndpointPinTests(new, 11 tests) runs the tool with a spy in place of its UI steps:server_urlis refused before any UI step, for both providers: another host,http://, look-alike hosts (omi.me.evil.com,api.omi.me.evil.com), another host with Omi in the path (evil.com/omi.me,evil.com/api.omi.me/v1/mcp), extra path (/v1/mcp/extra,/v1/mcp/sse, a trailing slash), a shorter path, a query, a fragment, user info, another port, a trailing-dot host, no scheme,ftp, and text that is not a URL. Each returnsserver_url_not_omi, and the UI steps never run.:443.MemoryExportDestination's.auth_urlortoken_url(another host, http, a look-alike host, extra path, a query, or the two swapped) is refused before any UI step with its own code. Omi's are accepted and pinned.server_url).chatgpt.com/#settings/Connectorswith or withouthttps://, its Advanced sub-route, and#settings/Appsare accepted. These are refused: another site, Claude's page,chatgpt.com.evil.com,evil.com/chatgpt.com/…, a query that mentions it,notchatgpt.com,http://chatgpt.com, a ChatGPT chat, ChatGPT's general settings, and a window title with no URL.Create,Create apporAddcounts. These never count: "Add files", "Connect Google Drive", "Save memory", "Created by you", "Address", a bare "Connect" or "Save", two matches, or no buttons.The existing Claude tests still pass: page-state classification, the state machine, the OCR candidates, the guidance overlays, and the setup result checks.
UI evidence
UI-Evidence: none -- tool argument and page checks only; no UI change
Failure class (fixes)
Failure-Class: none