Skip to content

feat(integrations): SAP S/4HANA - #4301

Merged
waleedlatif1 merged 14 commits into
stagingfrom
waleedlatif1/sap-integration
Apr 27, 2026
Merged

feat(integrations): SAP S/4HANA#4301
waleedlatif1 merged 14 commits into
stagingfrom
waleedlatif1/sap-integration

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • New SAP S/4HANA integration: 37 tools across Business Partner, Customer, Supplier, Sales Order, Product, Purchase Order/Requisition, Supplier Invoice, Outbound/Inbound Delivery, Material Stock/Documents, Billing Document + a generic OData v2 query escape hatch
  • Single internal proxy route handles BTP UAA token caching, CSRF fetch+retry on writes, and OData error/payload normalization
  • Supports Cloud Public Edition, Cloud Private Edition (RISE), and on-premise — with OAuth 2.0 client credentials or HTTP Basic auth selectable per deployment
  • Block, registry entries, icon, integrations landing entry, and generated docs

Type of Change

  • New feature

Testing

Tested manually

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)

@vercel

vercelBot commented Apr 26, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
docsReadyReadyPreview, CommentApr 27, 2026 9:56pm

Request Review

@cursor

cursorBot commented Apr 26, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Introduces a new server-side proxy that fetches OAuth tokens, forwards authenticated requests to customer-provided SAP endpoints, and performs CSRF retries, so regressions or validation gaps could impact outbound request security and write behavior.

Overview
Adds a full SAP S/4HANA integration: new docs page (sap_s4hana.mdx), tool metadata entries, and a new sap_s4hana integration entry for the integrations landing pages.

Implements a new /api/tools/sap_s4hana/proxy route that validates inputs, blocks unsafe URLs, caches OAuth client-credentials tokens, fetches CSRF tokens for write calls, retries on CSRF failures, and normalizes OData v2 responses/errors.

Registers ~37 new sap_s4hana_* tools and a new SapS4HanaBlock in the Sim block/tool registries, and updates icon mapping to include SapS4HanaIcon plus fixes several SVGs to use useId()-scoped IDs to avoid gradient/path collisions.

Reviewed by Cursor Bugbot for commit 6403b56. Configure here.

@greptile-apps

greptile-appsBot commented Apr 26, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces a complete SAP S/4HANA integration with 37 tools, a single internal proxy route (/api/tools/sap_s4hana/proxy) that handles BTP UAA OAuth token caching, CSRF fetch-and-retry, OData error normalization, and SSRF protection, plus a block, registry entries, icon, and docs. Previous security findings (SSRF via unvalidated URLs, token cache unboundedness, no fetch timeouts, CSRF cache key collision, private IP ranges, query-string injection) have all been addressed in follow-up commits.

Confidence Score: 5/5

Safe to merge — all prior P0/P1 security issues are fixed; only two minor P2 findings remain.

All critical security issues from previous review rounds have been resolved (SSRF, token cache, CSRF key, private IPs, timeouts, path injection). Remaining findings are P2: response body has no size cap, and the joinSetCookies fallback regex can misbehave on cookie values with commas. Neither blocks correctness in standard SAP environments.

apps/sim/app/api/tools/sap_s4hana/proxy/route.ts — lines 359-368 (cookie splitting fallback) and 479-481 (unbounded response buffering).

Important Files Changed

FilenameOverview
apps/sim/app/api/tools/sap_s4hana/proxy/route.tsCore proxy route handling BTP UAA token caching, CSRF fetch+retry, SSRF validation, and OData normalization — well-implemented with prior security issues resolved; minor concerns around response body size limit and cookie fallback parsing.
apps/sim/tools/sap_s4hana/utils.tsShared utilities (proxy body builder, OData query builder, JSON input parser, OData key quoter, response transformer) — return type of parseJsonInput correctly updated to T
apps/sim/tools/sap_s4hana/types.tsType definitions for all 37 tool parameter interfaces — thorough, consistent, no issues.
apps/sim/blocks/blocks/sap_s4hana.tsBlock config wiring all 37 operations with conditional sub-blocks and auth fields — credential params correctly use user-only visibility per convention.
apps/sim/tools/sap_s4hana/odata_query.tsGeneric OData escape-hatch tool with normalizeQuery handling both JSON-object and query-string forms; $format=json injected automatically when absent.
apps/sim/tools/sap_s4hana/create_sales_order.tsCreates a sales order with deep insert of to_Item; validates items is a non-empty array before sending.
apps/sim/tools/sap_s4hana/update_sales_order.tsPATCH update tool using quoteOdataKey for key escaping; defaults If-Match to * (unconditional) with clear documentation.

Sequence Diagram

sequenceDiagram
participant Tool as Tool (client)
participant Proxy as /api/tools/sap_s4hana/proxy
participant BTP as SAP BTP UAA
participant SAP as SAP S/4HANA OData
Tool->>Proxy: POST {deploymentType, authType, service, path, method, query, body}
Proxy->>Proxy: checkInternalAuth + Zod validation + SSRF checks
alt OAuth client credentials
Proxy->>Proxy: tokenCacheKey lookup
opt cache miss / expired
Proxy->>BTP: POST /oauth/token (grant_type=client_credentials)
BTP-->>Proxy: {access_token, expires_in}
Proxy->>Proxy: rememberToken (LRU, max 500)
end
end
alt Write method (POST/PATCH/PUT/DELETE/MERGE)
Proxy->>SAP: GET /$metadata (X-CSRF-Token: Fetch)
SAP-->>Proxy: x-csrf-token + Set-Cookie
end
Proxy->>SAP: {method} /sap/opu/odata/sap/{service}{path}?{query}
SAP-->>Proxy: OData JSON response
alt 403 + CSRF required
Proxy->>SAP: GET /$metadata (re-fetch CSRF)
SAP-->>Proxy: refreshed csrf token
Proxy->>SAP: retry {method} with new CSRF token
SAP-->>Proxy: OData JSON response
end
Proxy->>Proxy: unwrapOdata (strip .d / .d.results wrapper)
Proxy-->>Tool: {success, output: {status, data}}
Loading

Reviews (13): Last reviewed commit: "fix(icons): remove unused mask in Pipedr..." | Re-trigger Greptile

Comment threadapps/sim/app/api/tools/sap_s4hana/proxy/route.ts
Comment threadapps/sim/app/api/tools/sap_s4hana/proxy/route.ts
Comment threadapps/sim/app/api/tools/sap_s4hana/proxy/route.ts
Comment threadapps/sim/tools/sap_s4hana/utils.ts Outdated
Comment threadapps/sim/blocks/blocks/sap_s4hana.ts
Comment threadapps/sim/app/api/tools/sap_s4hana/proxy/route.ts
@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@greptile

@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@cursor review

Comment threadapps/sim/app/api/tools/sap_s4hana/proxy/route.ts
Comment threadapps/sim/app/api/tools/sap_s4hana/proxy/route.ts
Comment threadapps/sim/app/api/tools/sap_s4hana/proxy/route.ts
@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@greptile

@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@cursor review

Comment threadapps/sim/app/api/tools/sap_s4hana/proxy/route.ts
@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@greptile

@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@cursor review

Comment threadapps/sim/app/api/tools/sap_s4hana/proxy/route.ts
Comment threadapps/sim/app/api/tools/sap_s4hana/proxy/route.ts
@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@greptile

@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@cursor review

Comment threadapps/sim/app/api/tools/sap_s4hana/proxy/route.ts Outdated
Comment threadapps/sim/app/(landing)/integrations/data/integrations.json
@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@greptile

@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@cursor review

Comment threadapps/sim/app/api/tools/sap_s4hana/proxy/route.ts
Comment threadapps/sim/app/api/tools/sap_s4hana/proxy/route.ts
waleedlatif1and others added 12 commits April 27, 2026 14:17
- Validate baseUrl/tokenUrl in Zod schema and at runtime to prevent SSRF
(https-only, deny loopback/link-local/cloud-metadata hosts)
- Cap proxy token cache at 500 entries with LRU eviction
- Add 30s timeout to outbound token, CSRF, and OData fetches
- Make parseJsonInput return T | undefined so missing input is type-safe
- Reset authType when deploymentType changes and surface OAuth fields
whenever auth is not basic, so cloud_public users always see clientId/
clientSecret after switching from a basic-auth private deployment
- Reject OData service names that are not uppercase identifiers and
paths containing ".." or "." traversal segments
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…fenses
- Permit ";v=NNNN" suffix on ServiceName regex so the four delivery tools
(API_OUTBOUND_DELIVERY_SRV;v=0002, API_INBOUND_DELIVERY_SRV;v=0002) pass
schema validation
- Restrict subdomain to RFC 1123 label characters and region to lowercase
alphanumeric short codes; run the constructed cloud_public host through
assertSafeExternalUrl so a crafted subdomain (e.g. "evil.com#") cannot
redirect requests carrying SAP credentials
- Block RFC-1918 (10/8, 172.16/12, 192.168/16), 127/8, 169.254/16, and
0.0.0.0 via isPrivateIPv4, plus IPv4-mapped IPv6 variants
(::ffff:10.0.0.1, ::10.0.0.1) so private internal hosts cannot be
reached from baseUrl, tokenUrl, or the resolved cloud_public URL
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The WHATWG URL parser normalizes IPv4-mapped IPv6 addresses to hex form
(e.g. [::ffff:169.254.169.254] → [::ffff:a9fe:a9fe]), which slipped past
the dotted-decimal-only extractor. Decode the trailing two 16-bit hex
groups back into IPv4 octets and run them through isPrivateIPv4. Also
add isPrivateOrLoopbackIPv6 so pure IPv6 loopback (::, ::1), unique
local addresses (fc00::/7), and link-local (fe80::/10) cannot be reached.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… secret
- buildOdataUrl skips request query params when called with an internal
pathOverride so the /$metadata CSRF probe never carries user OData
options ($filter, $top, $select), which were causing write operations
through the generic odata_query tool to fail.
- tokenCacheKey now mixes a sha256 hash of clientSecret into the cache
key so two tenants sharing the same tokenUrl + clientId but different
secrets get isolated entries (no cross-tenant token leak).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…scriptions
- ServicePath validator now rejects "?" and "#" so a caller can't smuggle
query options through the path field (e.g.,
"/A_BusinessPartner?$format=atomsvc"); the Zod refine now reports
".." / "." segments, "?", and "#" together.
- Update Customer / Update Supplier / Update Purchase Requisition tool
descriptions exceeded the docs generator's 600-char regex window, so
they were rendering with empty descriptions on the integrations
landing page. Trimmed them to fit while keeping the limited-fields
note and the If-Match guidance, then regenerated integrations.json
and tool docs.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…kie split
- ServicePath now also rejects %2e/%2E, %2f/%2F, %5c/%5C, %3f/%3F, %23
so a caller cannot smuggle ".." / "." / "/" / "\" / "?" / "#" past the
validator and have SAP's ABAP/ICM gateway decode them server-side.
- joinSetCookies fallback regex now allows the ", " separator that's
used when multiple Set-Cookie values are folded onto one header line
(older runtimes without Headers.getSetCookie). Prevents CSRF cookies
from being concatenated into a single value during write operations.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… array
- buildOdataUrl now constructs query strings manually with
encodeURIComponent and restores literal "$" so OData system options
($filter, $top, $select, $expand, $orderby, $skip, $format) reach
SAP and any intermediary proxies/WAFs as-is, not as "%24filter".
URLSearchParams was percent-encoding "$" to "%24" which most ICMs
decode but some intermediaries silently drop, returning unfiltered
results.
- create_sales_order now rejects an empty items array (matches
create_purchase_requisition) so callers get a clear client-side
error instead of an opaque SAP validation failure on the deep-insert.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…rection
Why: resolveHost previously preferred baseUrl unconditionally. A caller
sending deploymentType=cloud_public with a baseUrl pointing elsewhere
would obtain a real SAP UAA token, then forward it as Bearer to the
attacker host. Zod superRefine did not validate baseUrl for cloud_public.
Fix: resolveHost now constructs the SAP host from subdomain when
deploymentType is cloud_public and only uses baseUrl for cloud_private
and on_premise (where it is already SSRF-checked in superRefine).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Why: hardcoded SVG gradient/mask IDs collide when an icon renders more
than once on a page (e.g. integrations listing). All other icons in this
file use React's useId() — these were inconsistent.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Why: IAMIcon, IdentityCenterIcon, STSIcon, SESIcon, and SecretsManagerIcon
all used hardcoded `id='xxxGradient'` values that collide when an icon
renders more than once on a page (e.g. integrations listing).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@waleedlatif1
waleedlatif1force-pushed the waleedlatif1/sap-integration branch from 1619c65 to d02d946CompareApril 27, 2026 21:17
@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@greptile

@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@cursor review

Comment threadapps/sim/app/api/tools/sap_s4hana/proxy/route.ts
…ection
Why: resolveTokenUrl previously honored caller-supplied tokenUrl
regardless of deploymentType, mirroring the same redirection class as
the prior baseUrl bug. A cloud_public caller could send tokenUrl to an
attacker host, causing the proxy to POST clientId:clientSecret as Basic
auth to it. superRefine for cloud_public did not validate tokenUrl.
Fix: derive UAA URL from subdomain+region for cloud_public; only honor
tokenUrl for cloud_private/on_premise (already SSRF-checked).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@greptile

@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@cursor review

Comment threadapps/sim/components/icons.tsx Outdated
Why: the <mask> element had no consumer (no mask='url(#...)' anywhere
in the SVG), so both it and the maskId variable were dead code.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@greptile

@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@cursor review

@cursorcursorBot 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 6403b56. Configure here.

Sign up for freeto 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

@waleedlatif1