feat(connectors): add 9 knowledge base connectors - #6699
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryHigh Risk Overview Each connector implements listing, deferred content fetch, config validation, and tag mapping with provider-specific pagination, caps, and UI: the add-connector modal allows optional API keys when connector meta sets Security fixes: Microsoft Dataverse file upload outbound calls use Reviewed by Cursor Bugbot for commit eb1e556. Configure here. |
Greptile SummaryThe PR adds nine knowledge-base connectors and strengthens connector synchronization safeguards, while also hardening the existing Microsoft Dataverse tools against credential disclosure.
Confidence Score: 5/5The PR appears safe to merge because no blocking failure remains from the previous review threads. The arbitrary-host and redirect-based Dataverse bearer-token disclosures are addressed in current HEAD, including the upload route’s separate outbound fetch.
|
| Filename | Overview |
|---|---|
| apps/sim/tools/microsoft_dataverse/utils.ts | Normalizes environment URLs and restricts bearer-authenticated Dataverse requests to HTTPS origins under Microsoft’s supported Dataverse domains. |
| apps/sim/app/api/tools/microsoft-dataverse/upload-file/route.ts | Prevents the outbound upload PATCH from forwarding its OAuth bearer token to signed-storage redirect targets. |
| apps/sim/tools/microsoft_dataverse/upload_file.ts | Keeps the user-entered environment URL user-only and delegates the external upload to the independently hardened internal route. |
| apps/sim/lib/knowledge/connectors/sync-engine.ts | Adds reconciliation safeguards for suspiciously empty or collapsed connector listings. |
| apps/sim/connectors/registry.server.ts | Registers the new connector runtimes for server-side synchronization. |
Sequence Diagram
sequenceDiagram
participant Tool as Dataverse Tool
participant Route as Internal Upload Route
participant DV as Dataverse API
participant Storage as Signed Storage Host
Tool->>Route: Same-origin upload request
Route->>DV: PATCH file with OAuth bearer token
DV-->>Route: Redirect to signed storage URL
Route->>Storage: PATCH file body without Authorization
Storage-->>Route: Upload response
Route-->>Tool: Upload result
Reviews (6): Last reviewed commit: "fix(dataverse): strip the bearer token o..." | Re-trigger Greptile
17c5922 to
96519cf
Compare
|
@cursor review |
96519cf to
26103b2
Compare
|
@cursor review |
Box, Zoho Desk, PagerDuty, Trello, Microsoft Excel, Google Slides, Google Vault, Mintlify, and SFTP. Selected by intersecting the published connector catalogs of Glean, Onyx, Dust, Vectara, Writer, Guru, Elastic, Microsoft 365 Copilot, Notion AI, Unstructured, and Airbyte against services that already ship a Sim block, so OAuth providers, credentials, and icons are reused. Box was the largest gap, appearing in 7-8 of ~10 catalogs. Every connector was validated against live provider documentation twice, the second pass treating the first pass's conclusions as unproven. Notable correctness work that came out of that: Listing truncation. The sync engine hard-deletes documents past a cap that is not flagged with `listingCapped`, and five connectors had a path there — an empty Mintlify discovery, Zoho Desk's exact-multiple default caps, Trello's archived lists and 1000-card ceiling, a Google Vault cursor bailout, and a PagerDuty stalled page. The engine also gained a backstop: an empty or collapsed listing blocks deletion reconciliation until the same observation repeats on a consecutive sync, reconstructed from existing sync-log counters so no migration is needed. API alignment. `desk.zoho.ca` does not resolve (Canada is `desk.zohocloud.ca`, and Singapore and UAE were missing); `modifiedTime` is absent from Zoho's ticket list projection, so every ticket re-embedded on every sync; Trello's `dateLastActivity` is documented to miss some edits; PagerDuty's 10,000-record ceiling bounds `offset + limit`, not offset; Excel indexed dates as raw serial numbers while Google Sheets renders them; Google Vault truncated at roughly 249 matters. Security. SFTP followed symlinks in `getDocument` and composed unchecked server-supplied filenames into paths; it now also supports optional host-key fingerprint verification, which runs during key exchange before any password is sent. Trello interpolated user-supplied board ids into URL paths. Google Vault is narrowed to `ediscovery.readonly`. `getDataverseBaseUrl` accepted any host while attaching a bearer token, and is pinned to Microsoft's Dataverse domains — pre-existing shipped code, fixed here. Also adds `ConnectorAuthConfig.optional` so a public source can be configured without inventing an API key, and teaches the scope check that a granted read-write scope satisfies a required `.readonly` sibling. Microsoft Dataverse was built and then removed: its OAuth cannot complete consent. Dataverse requires a per-environment resource URI, the provider declares a static `https://dynamics.microsoft.com/user_impersonation` that is not an Entra Application ID URI, and the environment URL is only collected after the credential exists. That predates this change and also affects the 12 shipped Dataverse tools.
The host allowlist added alongside the connector work only constrains the initial destination. `secureFetchWithPinnedIP` follows redirects and keeps the `Authorization` header unless a tool opts out, so a redirect away from an allowed Dataverse origin would forward the caller's OAuth token to whatever host answers. Dataverse redirects in normal operation — file downloads hand back a signed storage URL, and environment hosts move between regional origins — so this is reachable without a compromised environment URL. Sets `stripAuthOnRedirect` on all 18 Dataverse tools, matching the existing GitHub job-logs and Windchill precedent.
26103b2 to
903c94e
Compare
- microsoft-excel: `fetchWorksheets` read only the first Graph page and never followed `@odata.nextLink`. A workbook with more sheets than fit in one page dropped the remainder from the listing without setting `listingCapped`, so the sync engine reconciled those documents away as deleted. The walk now pages, bounded by MAX_WORKSHEETS, and only follows a nextLink that stays on the Graph origin, since the link is server-supplied and carries the token. - google-slides: the listing `contentHash` covered only the file id and modified time, so toggling the speaker-notes option left every stored hash matching and no presentation was ever re-hydrated with the new scope. The setting is now part of the hash, in the single shared stub builder so the list and hydrate paths stay identical. - mintlify: `pathPrefix` filtered with a bare `startsWith`, so a prefix of `/guides` also matched a sibling like `/guides-archive`. It now shares the `/`-boundary rule `withinBasePath` already used, extracted as `isUnderPath`.
|
@cursor review |
…ash prefix - zoho-desk: `sortBy: 'createdTime'` is ascending — Zoho denotes descending with a `-` prefix — so the default 500-record caps kept the oldest tickets and articles and recent ones were never listed. Because the cap sets listingCapped, that stale tail could not reconcile away either. Now sorts `-createdTime`. Still ordering on createdTime rather than modifiedTime, so rows do not reshuffle mid-walk. - mintlify: `resolvePathPrefix` kept a trailing slash while `isUnderPath` accepts an exact match or `prefix + '/'`, so `/guides/` matched neither `/guides` nor `/guides/intro` and the source synced nothing. A regression from the previous round, which replaced a bare `startsWith`. The prefix is now normalized before comparison.
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 74cda71. Configure here.
…rect `upload_file` posts to an internal route rather than calling Dataverse directly, so the tool-level `stripAuthOnRedirect` added in 903c94e only covers the same-origin hop into that route. The route's own outbound PATCH carries the caller's OAuth token and left redirect stripping at its default, so a redirect to a signed storage host — which is exactly how Dataverse serves file operations — would have handed that host a reusable credential. The other 17 tools build the Dataverse URL directly, so the tool-level flag already covers them.
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit eb1e556. Configure here.
What
Adds 9 knowledge base connectors, closing the highest-frequency gaps in our catalog. Every service already shipped a block, so OAuth providers, credentials, and icons were reused — no new auth plumbing.
boxzoho-desktrellomicrosoft-excelgoogle-drivegoogle-vaultllms.txtSelected by surveying the published connector catalogs of Glean, Onyx, Dust, Vectara, Writer, Guru, Elastic, Microsoft 365 Copilot, Notion AI, Unstructured, and Airbyte, then intersecting the gaps with services that already have a Sim block. Box was the single biggest gap — 7-8 of ~10 catalogs, and the default content store in financial services, life sciences, and government.
Validation
Every connector was validated against live provider documentation twice, independently. The second pass was told to treat the first pass's conclusions as unproven — which mattered, because it found defects in the first pass's own fixes.
Data-loss class
The sync engine hard-deletes documents past a cap not flagged with
listingCapped. Paths found and fixed across both passes:llms.txtandsitemap.xml404, or an HTTP-200 HTML interstitial, whichdocs.cursor.comreally serves) reconciled the whole KB away. A sitemap-index child that 404s was silently skipped despite a comment claiming failure was fatal. The origin-level index returned early with 13 of 306 pages for a sub-path site.listingCappedlatched permanently on any list of 1000+ cards; then the fix for that stopped flagging multi-page lists while resting on an ordering guarantee Trello does not document.from <= 4999ceiling and 422'd the whole listing at the documented 5000 maximum.more: truewith zero incidents ended the listing unflagged.Because five of ten audited connectors had some path here, the engine also gained a backstop: an empty or collapsed listing now blocks deletion reconciliation until the same observation repeats on a consecutive sync. No migration — the previous run is reconstructed from existing sync-log counters. Corroboration is only trusted from a run that could not have been incremental, otherwise a bad listing confirms itself.
Correctness
desk.zoho.cadoes not resolve. Canada isdesk.zohocloud.ca. All 11 data centers were then re-verified by DNS and an authenticated probe returning401 UNAUTHORIZED, which proves the host serves the Desk v1 API. Singapore and UAE were missing entirely.modifiedTimeis absent fromGET /tickets, so the stub and hydrated hashes never matched and every ticket re-embedded on every sync, forever.dateLastActivitywithout creating an action, and never published which. The hash now folds inbadgescounters, which arrive with the listing at no extra cost.42019, not1/15/2015), unlike Google Sheets. Graph documents thatRange.textis independent of cell width, so the#######objection does not apply. A content-format token forces one re-index, since the metadata-only hash would otherwise keep stale content.offset + limit, so the guard admitted a request that 400s after any short page. The incremental window was also only half pinned.extracted_textgave up before polling when a representation was not yet generated..mdroute 404s hydrated every page to nothing whilevalidateConfigpassed. The HTML fallback then indexed<script>contents: 294KB of RSC payload per page, 98% noise.Security
getDocumentusedstat, which follows symlinks. Filenames fromreaddirwere composed into paths unchecked. Host-key verification was absent entirely; an optional fingerprint now verifies during key exchange, before any password is sent. It is opt-in and blank by default, so an unconfigured source retains the original MITM exposure — IP pinning does not mitigate this, since the pin derives from the same DNS answer an attacker would poison.ediscovery(which grants delete on matters and holds) toediscovery.readonly.getDataverseBaseUrlaccepted any host while every request attaches a bearer token. Pinned to Microsoft's Dataverse domains. This is pre-existing shipped code, fixed here.Removed: Microsoft Dataverse
Built, then cut. Its OAuth cannot complete consent, and never could. Dataverse requires a per-environment resource (
<environment-url>/user_impersonation); the provider declares a statichttps://dynamics.microsoft.com/user_impersonation, which is not an Entra Application ID URI. The scope is fixed at Better Auth config time with no per-connection override, and the environment URL is only collected after the credential exists — so nothing can supply it at authorize time. Entra rejects at/authorizewithAADSTS500011.This predates the PR and affects the 12 shipped
tools/microsoft_dataverse/*tools. Added in a bulk tool-authoring PR (#3257) with zero tests; the later "align with live API docs" PR (#5481) touched no OAuth file. A real fix needs a contract, route, and UI change plus an Entra app-registration permission, so it belongs in its own change. One-minute confirmation: open the authorize URL in a browser signed into any work tenant.Also cut earlier: Google Groups (no public API returns conversations — the Migration API is insert-only) and Workday (native file APIs need OAuth we haven't wired; only custom-report rows were reachable).
Claims corrected during validation
Stated in an earlier revision of this description and since disproven:
GET /status_pages/{id}/posts/{post_id}/postmortem. ButStatusPagePost.linked_resource.typeis an untyped string with no documented incident join, so it belongs in a separate status-pages connector, not the incident walk..well-known/llms.txtis not a real Mintlify location — 404s on every site checked.sourceConfigonce before the pagination loop, so enabled kinds cannot change mid-sync. The defensive branch is gone.beforepagination is not a reliable completeness guarantee, only a usable dedup bound.Known gaps
created_at:ascplus the 10k ceiling leaves a permanent hole on large accounts.ascis correct for offset-pagination integrity, so the fix is time-window chunking, not flipping todesc.[markdown]representation would preserve table structure that flatextracted_textdestroys.docs.anthropic.com→platform.claude.com); needsurlonSecureFetchResponse.addis invisible in sync counters. Shared with google-docs; belongs in a follow-up covering both.Still missing from the catalog
A generic web crawler (6/10 catalogs, backstops every source not in the catalog), Bitbucket, Guru, Coda, and a database/warehouse source. None have blocks today.
Testing
bun run type-checkcleanbiome checkclean across 229 filesbun run check:api-validationpassesNot exercised against live tenants. Validation was documentation-based, with live DNS and HTTP probes where a host or response shape could be checked without credentials.