{"record":{"id":"a10fe42760443fba","repo":"Budibase/budibase","slug":"invalid-sharepoint-pagination-url","errorCode":null,"errorMessage":"Invalid SharePoint pagination URL","messagePattern":"Invalid SharePoint pagination URL","errorType":"http","errorClass":"HTTPError","httpStatus":400,"severity":"error","filePath":"packages/server/src/sdk/workspace/ai/knowledgeSources/sharepoint/connection.ts","lineNumber":282,"sourceCode":"    const payload = await fetchSitesPage(nextLink)\n    for (const site of payload.value || []) {\n      if (!site?.id) {\n        continue\n      }\n      sitesById.set(site.id, {\n        id: site.id,\n        name: site.displayName || site.name,\n        webUrl: site.webUrl,\n      })\n    }\n\n    const nextPageLink = payload?.[\"@odata.nextLink\"]\n    if (!nextPageLink) {\n      nextLink = \"\"\n      continue\n    }\n    if (!isAllowedSharePointNextLink(nextPageLink)) {\n      throw new HTTPError(\"Invalid SharePoint pagination URL\", 400)\n    }\n    nextLink = nextPageLink\n  }\n  if (nextLink) {\n    console.warn(\n      \"Stopped fetching SharePoint sites due to reaching maximum page limit\",\n      {\n        lastNextLink: nextLink,\n      }\n    )\n  }\n\n  return Array.from(sitesById.values()).sort((a, b) =>\n    (a.name || a.id).localeCompare(b.name || b.id)\n  )\n}\n\nexport const fetchSharePointSitesByDatasourceAuthConfig = async (","sourceCodeStart":264,"sourceCodeEnd":300,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/server/src/sdk/workspace/ai/knowledgeSources/sharepoint/connection.ts#L264-L300","documentation":"While paging through Graph site results, each @odata.nextLink is validated by isAllowedSharePointNextLink, which requires the URL to match the Microsoft Graph base URL (protocol, host, port, path prefix). If a next link points anywhere else the code refuses to follow it — an SSRF/open-redirect protection — and throws a 400 HTTPError.","triggerScenarios":"A Graph response's @odata.nextLink is a relative URL, a different host (proxy/region redirect), or a malformed URL so isAllowedSharePointNextLink returns false.","commonSituations":"Environment reroutes Graph through a custom proxy/EGRESS host so next links come back with a different hostname; Graph changes pagination URL shape; mock/test Graph servers returning relative next links; corrupted or tampered responses.","solutions":["Ensure the environment talks directly to graph.microsoft.com — do not rewrite Graph response bodies through a proxy.","If a proxy is mandatory, make sure it preserves the absolute https://graph.microsoft.com next links.","Update SHAREPOINT_API_BASE_URL if your deployment targets a sovereign/different Graph endpoint so next links match it.","Capture the offending nextLink from server logs to diagnose why validation failed."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"import { isAllowedSharePointNextLink } from \"./connection\"\n// before following any next link yourself:\nif (!isAllowedSharePointNextLink(nextLink)) {\n  throw new Error(\"Refusing to follow non-Graph pagination URL\")\n}","typeGuard":"const isAbsoluteGraphUrl = (value: string): boolean => {\n  try {\n    const u = new URL(value)\n    return u.protocol === \"https:\" && u.hostname.endsWith(\"graph.microsoft.com\")\n  } catch {\n    return false\n  }\n}","tryCatchPattern":"try {\n  await fetchSharePointSitesByDatasourceAuthConfig(datasourceId, authConfigId)\n} catch (err) {\n  if (err instanceof HTTPError && err.message === \"Invalid SharePoint pagination URL\") {\n    // check proxy/EGRESS config rewriting graph.microsoft.com links\n  }\n  throw err\n}","preventionTips":["Do not route Microsoft Graph traffic through proxies that rewrite response URLs.","Keep SHAREPOINT_API_BASE_URL aligned with the Graph endpoint actually in use.","If mocking Graph in tests, return absolute next links on the same host as the base URL."],"tags":["sharepoint","ssrf","pagination","url-validation","microsoft-graph"],"backgroundTag":"invalid-pagination-url","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}