Skip to content

feat(sharepoint): validate against Graph API and add delete/update/download tools#5369

Merged
waleedlatif1 merged 6 commits into
stagingfrom
worktree-sharepoint-validate
Jul 2, 2026
Merged

feat(sharepoint): validate against Graph API and add delete/update/download tools#5369
waleedlatif1 merged 6 commits into
stagingfrom
worktree-sharepoint-validate

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • Ran a full audit of the SharePoint integration against live Microsoft Graph API docs
  • Fixed real bugs: malformed nested $expand syntax in get_list, reversed site-resolution precedence in create_list, unsanitized field fallback in add_list_items, missing URL encoding in read_page, and an incorrect root/serverRelativeUrl field shape (Graph's root facet has no properties)
  • Fixed V1 block gaps vs V2: upload_file required flag, missing required on pageName/listDisplayName, wrong site-picker mimeType, dead subBlock references
  • Added 8 new tools — delete_list_item, get_list_item, delete_page, update_page, publish_page, download_file, delete_file, get_drive_item — all covered by the OAuth scopes already granted (Sites.Read.All / Sites.ReadWrite.All / Sites.Manage.All), no scope changes
  • Added an opt-in stripAuthOnRedirect option to secureFetchWithPinnedIP so the new download route doesn't resend the bearer token to the preauthenticated redirect target Graph returns (default off, zero behavior change for existing callers)
  • Deduped read-only list-item field sanitization into a shared utils.ts helper

Type of Change

  • Bug fix
  • New feature

Testing

Tested manually — verified every tool against live Graph API docs, confirmed backwards compatibility (no renamed/removed tool IDs, params, or outputs), confirmed OAuth scope neutrality, ran lint/typecheck/API-validation-strict clean.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

…wnload tools

- Fix bugs found validating existing tools against live Graph docs: malformed
  nested $expand syntax in get_list, reversed site-resolution precedence in
  create_list, unsanitized field fallback in add_list_items, missing URL
  encoding in read_page, and an incorrect root/serverRelativeUrl field shape
- Fix V1 block gaps vs V2: upload_file required flag, missing required on
  pageName/listDisplayName, wrong site-picker mimeType, dead subBlock refs
- Add 8 new tools within already-granted scopes for CRUD completion:
  delete_list_item, get_list_item, delete_page, update_page, publish_page,
  download_file, delete_file, get_drive_item
- Add opt-in stripAuthOnRedirect option to secureFetchWithPinnedIP so the
  SharePoint file-download route doesn't resend the bearer token to the
  preauthenticated redirect target (default off, no behavior change elsewhere)
- Dedupe read-only list-item field sanitization into a shared utils helper
@vercel

vercel Bot commented Jul 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jul 2, 2026 3:31pm

Request Review

@cursor

cursor Bot commented Jul 2, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Adds destructive SharePoint mutations and a server route that accepts OAuth access tokens and streams file bytes; redirect auth stripping is scoped and opt-in but touches shared fetch infrastructure.

Overview
Expands the SharePoint block and sharepoint_v2 with eight new operations: update/publish/delete page, get/delete list item, download file, get drive item, and delete file—wired through new tools, registry entries, block sub-blocks, inputs/outputs, and drive-only vs site-scoped UI rules.

Adds POST /api/tools/sharepoint/download-file: internal auth, Zod contract, metadata check (reject folders), pinned Graph fetch with stripAuthOnRedirect and size cap, returns base64 file payload. secureFetchWithPinnedIP gains opt-in stripAuthOnRedirect so bearer tokens are not forwarded on redirects (used for Graph’s preauthenticated download URL).

Fixes Graph integration bugs: encodeURIComponent on site/drive/list/page IDs in URLs, corrected list $expand (items(expand=fields)), shared sanitizeListItemFields / escapeHtml, site ID resolution order, upload URL encoding, V1 block required fields and site picker mimeType, and columnDefinitions only mapped for create-list.

Reviewed by Cursor Bugbot for commit 5a076d5. Configure here.

Comment thread apps/sim/tools/sharepoint/update_page.ts Outdated
Comment thread apps/sim/tools/sharepoint/update_page.ts Outdated
…dence

- URL-encode siteId/groupId everywhere it's interpolated into a Graph
  request path (Graph site IDs like "host,guid,guid" contain commas that
  must be encoded) - addresses Cursor Bugbot findings on update_page,
  delete_page, publish_page, and applies the same fix consistently across
  every other sharepoint tool for consistency
- Fix create_page's site-resolution precedence (was siteSelector before
  siteId, inconsistent with every sibling tool)
@greptile-apps

greptile-apps Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds 8 new SharePoint tools (update/publish/delete page, get/delete list item, download/delete file, get drive item), fixes real Graph API bugs in existing tools (site-ID precedence, URL encoding, $expand syntax, HTML injection in canvas web parts), and adds stripAuthOnRedirect to secureFetchWithPinnedIP so the bearer token is not forwarded to the pre-authenticated CDN redirect Graph returns for file downloads.

  • Bug fixes: siteSelector/siteId precedence corrected in create_list and create_page, encodeURIComponent applied throughout URL builders in read_page, get_list, list_sites, and update_list; escapeHtml helper added and applied in create_page and update_page; read-only field sanitisation centralised into a shared utils.ts helper.
  • New tools: eight Graph API operations added, all within existing OAuth scopes; the download path correctly strips the auth header on redirect using the new opt-in stripAuthOnRedirect flag.
  • One regression: get_list.ts changes the nested $expand syntax from the valid OData v4 form items($expand=fields) to the invalid items(expand=fields), which causes Graph to silently drop item field values when a caller requests both columns and items on the same list endpoint.

Confidence Score: 4/5

Safe to merge after fixing the OData nested-expand syntax in get_list.ts; all other changes are well-structured and consistent with existing patterns.

The change in get_list.ts from items($expand=fields) to items(expand=fields) removes the required $ prefix from a nested OData system query option. Microsoft Graph silently ignores the malformed inner option, so any caller that sets both includeColumns and includeItems will receive list items with no field values — a silent data-loss regression on a code path that the PR explicitly claims to validate.

apps/sim/tools/sharepoint/get_list.ts — the nested $expand syntax needs to be restored to items($expand=fields).

Important Files Changed

Filename Overview
apps/sim/tools/sharepoint/get_list.ts URL-encoding improvements and type-safety cleanup are correct, but the nested $expand syntax was changed from valid OData (items($expand=fields)) to invalid (items(expand=fields)), which silently drops item field values when both columns and items are requested.
apps/sim/app/api/tools/sharepoint/download-file/route.ts New route for file download — validates URLs with DNS, uses secureFetchWithPinnedIP with stripAuthOnRedirect for the 302 redirect to the pre-authenticated CDN, enforces MAX_FILE_SIZE, and correctly guards folders vs. files.
apps/sim/lib/core/security/input-validation.server.ts Adds opt-in stripAuthOnRedirect option that omits the Authorization header from recursive secureFetchWithPinnedIP calls on redirect; default-off, zero impact on existing callers.
apps/sim/tools/sharepoint/utils.ts Adds shared escapeHtml (escapes &, <, >, ", ') and sanitizeListItemFields helpers, deduplicating logic previously copied across add_list_items and update_list.
apps/sim/tools/sharepoint/update_page.ts New tool for updating SharePoint page title and content; correct site-ID precedence, uses escapeHtml for the canvas web-part innerHtml, and requires at least one of pageTitle or pageContent.
apps/sim/tools/sharepoint/download_file.ts Thin tool that forwards to the internal /api/tools/sharepoint/download-file route (consistent with the upload pattern), passing driveId and driveItemId correctly.
apps/sim/blocks/blocks/sharepoint.ts Registers 8 new operations, corrects mimeType for the site-selector picker, fixes required flags for existing fields, and properly gates columnDefinitions to create_list only.
apps/sim/tools/sharepoint/add_list_items.ts Deduplicated body-building logic into resolveSanitizedFields; fallback in transformResponse now wrapped in try/catch so a malformed fallback no longer turns a successful create into an error.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant UI as Block UI
    participant Tool as SharePoint Tool
    participant Route as /api/tools/sharepoint/*
    participant Graph as Microsoft Graph API
    participant CDN as Pre-auth CDN

    Note over UI,Graph: New: Download File flow
    UI->>Tool: download_file(driveId, driveItemId)
    Tool->>Route: "POST /download-file {accessToken, driveId, itemId}"
    Route->>Graph: "GET /drives/{id}/items/{id} (metadata)"
    Graph-->>Route: "{ name, file: { mimeType } }"
    Route->>Graph: "GET /drives/{id}/items/{id}/content"
    Graph-->>Route: 302 redirect to CDN URL
    Note over Route: stripAuthOnRedirect strips Bearer token
    Route->>CDN: GET pre-auth URL (no Authorization)
    CDN-->>Route: Binary content
    Route-->>Tool: "{ file: { name, mimeType, data (base64), size } }"

    Note over UI,Graph: New: Page mutation flow
    UI->>Tool: update_page / publish_page / delete_page
    Tool->>Graph: "PATCH/POST/DELETE /sites/{siteId}/pages/{pageId}/microsoft.graph.sitePage"
    Graph-->>Tool: 200 or 204
    Tool-->>UI: "{ success, page/published/deleted }"

    Note over UI,Graph: New: List item lookup/delete
    UI->>Tool: get_list_item / delete_list_item
    Tool->>Graph: "GET or DELETE /sites/{id}/lists/{id}/items/{id}"
    Graph-->>Tool: "{ id, fields } or 204"
    Tool-->>UI: "{ item } or { deleted, itemId }"
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant UI as Block UI
    participant Tool as SharePoint Tool
    participant Route as /api/tools/sharepoint/*
    participant Graph as Microsoft Graph API
    participant CDN as Pre-auth CDN

    Note over UI,Graph: New: Download File flow
    UI->>Tool: download_file(driveId, driveItemId)
    Tool->>Route: "POST /download-file {accessToken, driveId, itemId}"
    Route->>Graph: "GET /drives/{id}/items/{id} (metadata)"
    Graph-->>Route: "{ name, file: { mimeType } }"
    Route->>Graph: "GET /drives/{id}/items/{id}/content"
    Graph-->>Route: 302 redirect to CDN URL
    Note over Route: stripAuthOnRedirect strips Bearer token
    Route->>CDN: GET pre-auth URL (no Authorization)
    CDN-->>Route: Binary content
    Route-->>Tool: "{ file: { name, mimeType, data (base64), size } }"

    Note over UI,Graph: New: Page mutation flow
    UI->>Tool: update_page / publish_page / delete_page
    Tool->>Graph: "PATCH/POST/DELETE /sites/{siteId}/pages/{pageId}/microsoft.graph.sitePage"
    Graph-->>Tool: 200 or 204
    Tool-->>UI: "{ success, page/published/deleted }"

    Note over UI,Graph: New: List item lookup/delete
    UI->>Tool: get_list_item / delete_list_item
    Tool->>Graph: "GET or DELETE /sites/{id}/lists/{id}/items/{id}"
    Graph-->>Tool: "{ id, fields } or 204"
    Tool-->>UI: "{ item } or { deleted, itemId }"
Loading

Reviews (6): Last reviewed commit: "fix(sharepoint): close V1 block input/ou..." | Re-trigger Greptile

Comment thread apps/sim/tools/sharepoint/update_page.ts
Comment thread apps/sim/tools/sharepoint/update_page.ts
Comment thread apps/sim/tools/sharepoint/add_list_items.ts Outdated
…hrowing

- create_page/update_page only escaped quotes when building innerHtml;
  add a shared escapeHtml() helper that also escapes &, <, > so page
  content with angle brackets/ampersands doesn't corrupt the canvas layout
- add_list_items' fields fallback (sanitized re-derivation when Graph's
  response omits fields) could throw on a malformed fallback input after
  the item was already created successfully; catch and fall back to
  undefined instead of failing an already-successful response
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile review

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/blocks/blocks/sharepoint.ts
…te_list

Both V1 and V2 tools.config.params mapped columnDefinitions onto pageContent
whenever columnDefinitions was truthy, with no operation check. Stale
list-column JSON left in block state after switching to create_page/
update_page would silently replace the user's intended page text. Gate
the mapping on operation === create_list (V1) / sharepoint_create_list (V2).
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile review

Greptile flagged the content fetch as unbounded - a large file would
buffer entirely in memory before base64-encoding into the JSON response.
Pass maxResponseBytes: MAX_FILE_SIZE (100MB, same constant used by the
upload path) to secureFetchWithPinnedIP so oversized files reject early
with a clear PayloadSizeLimitError instead of exhausting memory.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile review

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor 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.

✅ 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 52793b2. Configure here.

Final validation pass (4 parallel audit agents against live Graph docs)
surfaced remaining gaps:

- apps/sim/app/api/tools/sharepoint/upload/route.ts: siteId/driveId were
  not encodeURIComponent-ed in the Graph upload URL, unlike every other
  sharepoint route/tool - same encoding-gap class fixed everywhere else
- read_page.ts: transformResponse recomputed siteId with a raw `||` in
  3 spots instead of the optionalTrim(...) || optionalTrim(...) || 'root'
  precedence used by request.url and every sibling tool
- SharepointBlock (V1, legacy/hidden-from-toolbar but still executable
  for existing saved workflows): listItemFields and listItemId were
  missing `required` for update_list despite the tool requiring them;
  maxPages/groupId/includeColumns/includeItems/nextPageUrl subBlocks
  were entirely absent, making those tool params unreachable from the
  UI; block-level outputs were missing site/pages/content/totalPages/
  nextPageUrl/lists/skippedFiles/skippedCount/errors even though the
  underlying tools return them - V2 already covered all of these
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile review

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor 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.

✅ 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 5a076d5. Configure here.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

Re: the get_list.ts nested $expand confidence note — verified directly against the live Microsoft Graph docs (learn.microsoft.com/en-us/graph/api/list-get). The documented example is literally:

GET /sites/{site-id}/lists/{list-id}?expand=columns,items(expand=fields)

Our items(expand=fields) (no $ inside the nested parens) matches this exactly, character-for-character. No change needed — this was already the fix applied in an earlier review round and two independent audit passes since have re-confirmed it against live docs.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile review

@waleedlatif1 waleedlatif1 merged commit 070f554 into staging Jul 2, 2026
18 checks passed
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