Skip to content

feat(sailpoint): add SailPoint (IGA) integration - #6092

Open
mzxchandra wants to merge 6 commits into
stagingfrom
feat/sailpoint-iga
Open

feat(sailpoint): add SailPoint (IGA) integration#6092
mzxchandra wants to merge 6 commits into
stagingfrom
feat/sailpoint-iga

Conversation

@mzxchandra

Copy link
Copy Markdown
Contributor

SailPoint (IGA) integration

Adds a SailPoint Identity Security Cloud integration for identity-governance workflows.

What's included

  • 27 tools across the IGA surface:
    • Reads: search / search_count / search_aggregate, list+get identities, accounts (+ account entitlements), entitlements, roles (+ role entitlements), access profiles (+ entitlements), sources, account activities, campaigns, certifications (+ review items).
    • Writes: request_access, cancel_access_request, get_access_request_status.
    • Files: load_accounts / load_entitlements (CSV account/entitlement aggregation via multipart).
  • Block blocks/blocks/sailpoint.ts (IntegrationType.Security, AuthMode.ApiKey) whose longDescription enumerates the exact PAT scopes and the service-identity caveat.
  • Two internal routes (/api/tools/sailpoint/query, /api/tools/sailpoint/load) that do the token exchange, per-tenant host + version resolution, and file forwarding server-side.
  • Contract, icon, registry entries, and generated docs.

Auth model

Client-credentials / Personal Access Token against the per-tenant host https://{tenant}.api.identitynow.com, mirroring the in-repo CrowdStrike pattern. No OAuth redirect, no oauth.ts/auth.ts/env.ts changes.

  • In-process token caching keyed on tenant:clientId:apiVersion, refreshed on 401.
  • 429 backoff honoring Retry-After (SailPoint's limit is 100 req / client_id / API version / 10s).
  • API version is user-selectable (v2025 default, v2024, v3) — never a silent fallback.

Correctness details (verified against the v2025 OpenAPI spec)

  • requestType = GRANT_ACCESS / REVOKE_ACCESS / MODIFY_ACCESS; item type = ACCESS_PROFILE / ROLE / ENTITLEMENT.
  • Access-request constraints enforced pre-submission via superRefine: revoke = one identity + one item + comment; grant-with-entitlements caps at 25 items / 10 identities.
  • Per-endpoint limit caps (search 10000, roles/role-entitlements 50, otherwise 250).
  • Empty userAuth reads (identities/entitlements/roles/search) return complete: false + a permission-gap warnings[] diagnostic — the service-identity/API-Management-client gotcha surfaced at runtime.

Verification

  • bun run check:api-validation:strict ✓, check:bare-icons ✓, biome ✓, typecheck clean for the new files.
  • 10 route tests pass (token caching, 429 backoff, empty-200 diagnostic, revoke superRefine rejection, 202 write, multipart aggregation, error propagation).
  • Docs generated via scripts/generate-docs.ts.

Note: the block icon is a clean placeholder SailPoint mark (monochrome currentColor); swap in the official brand SVG when available.

🤖 Generated with Claude Code

Add a SailPoint Identity Security Cloud integration with 27 tools spanning
identity-governance reads (search + count + aggregate, identities, accounts,
entitlements, roles, access profiles, sources, account activities, campaigns,
certifications, and entitlement expansion), the access-request write path
(request, cancel, status), and CSV account/entitlement aggregation.

Auth uses a service-identity Personal Access Token via the OAuth2
client-credentials grant against the per-tenant host
(https://{tenant}.api.identitynow.com), resolved server-side in two internal
routes (/api/tools/sailpoint/query and /load) with in-process token caching
and Retry-After 429 backoff. The block enumerates the exact PAT scopes and the
service-identity caveat. Access-request constraints (revoke one identity + one
item, grant 25-entitlement / 10-identity caps, comment-on-revoke) are enforced
pre-submission, and empty userAuth reads surface a permission-gap diagnostic.
@vercel

vercel Bot commented Jul 30, 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 30, 2026 8:11am

Request Review

@cursor

cursor Bot commented Jul 30, 2026

Copy link
Copy Markdown

PR Summary

High Risk
Introduces identity-governance write paths (grant/revoke access, CSV aggregations) and handles PAT secrets server-side; mistakes could affect real tenant access, though host allowlisting and pre-submit validation reduce exposure.

Overview
Adds a SailPoint Identity Security Cloud integration so workflows can search and govern identities, run certifications, and submit access requests from Sim.

27 tools proxy through two server routes (/api/tools/sailpoint/query and /load): PAT client-credentials auth with cached tokens, tenant host allowlisting (SSRF guard), 401 refresh and 429 backoff, plus Zod contracts with access-request rules (e.g. single-identity revoke) and empty-read warnings when PAT user context may be insufficient.

A SailPoint workflow block, tool registry entries, SailPointIcon, integration metadata, and generated docs (sailpoint.mdx) wire the surface into the product; route tests cover token caching, rate limits, and load/aggregation paths.

Reviewed by Cursor Bugbot for commit 531ae54. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread apps/sim/app/api/tools/sailpoint/client.ts
Comment thread apps/sim/app/api/tools/sailpoint/query/route.ts
Comment thread apps/sim/blocks/blocks/sailpoint.ts
@greptile-apps

greptile-apps Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Prior SailPoint review findings are addressed on HEAD; this follow-up found no incomplete or unsafe fixes and no new thread-eligible issues.

  • Tenant host resolution allowlists *.api.identitynow.com / *.api.identitynowgov.com and validates bare tenant subdomains before any credentialed request.
  • Search aggregate requires non-empty aggregationsDsl, sends aggregationType: DSL, and returns the aggregation object under item.
  • Block UI maps “Include Total Count” to count: true for list/search pagination paths.

Confidence Score: 5/5

Safe to merge; previously reported SailPoint SSRF, search-aggregate, and total-count issues are fixed and no remaining blocking failures were found on this follow-up.

No blocking failure remains. Host resolution only targets allowed SailPoint hosts, aggregate requests require aggregations DSL, and the block can request X-Total-Count via count mapping.

Important Files Changed

Filename Overview
apps/sim/app/api/tools/sailpoint/client.ts SSRF allowlist and bare-tenant validation are in place; regression coverage rejects non-SailPoint hosts and accepts full identitynow hosts.
apps/sim/lib/api/contracts/tools/sailpoint.ts searchAggregateSchema requires a non-empty aggregationsDsl object so missing aggregations fail validation before the API call.
apps/sim/app/api/tools/sailpoint/query/route.ts Aggregate path forwards aggregationsDsl with aggregationType DSL and preserves AggregationResult as item; count query param is still honored when provided.
apps/sim/blocks/blocks/sailpoint.ts Include Total Count sub-block and applyPagination map count; Search Aggregate requires aggregationsDsl with JSON wand support.

Reviews (5): Last reviewed commit: "fix(sailpoint): adaptive token TTL buffe..." | Re-trigger Greptile

Comment thread apps/sim/app/api/tools/sailpoint/query/route.ts
Comment thread apps/sim/app/api/tools/sailpoint/client.ts
Comment thread apps/sim/app/api/tools/sailpoint/load/route.ts
…te, retry/count fixes

- Restrict tenant host resolution to *.api.identitynow.com / *.api.identitynowgov.com
  (or a bare tenant subdomain) so the client-credentials request can never post the PAT
  secret to an attacker-controlled or internal host. Throws on any other host.
- Make search_aggregate functional: add an aggregationsDsl input (tool/contract/route/block)
  and preserve the AggregationResult object instead of dropping it through the list handler.
- Retry the token exchange on 429 with Retry-After backoff, matching sailpointFetch.
- Rebuild the multipart FormData per attempt so 401/429 retries never reuse a consumed body.
- Expose an "Include Total Count" toggle so totalCount is reachable from the block UI.
- Regenerate docs. Add route tests for the SSRF guard and aggregate object preservation.
@mzxchandra

Copy link
Copy Markdown
Contributor Author

@greptile

@mzxchandra

Copy link
Copy Markdown
Contributor Author

@cursor review

Comment thread apps/sim/lib/api/contracts/tools/sailpoint.ts
Comment thread apps/sim/app/api/tools/sailpoint/client.ts
Comment thread apps/sim/app/api/tools/sailpoint/query/route.ts
Comment thread apps/sim/app/api/tools/sailpoint/query/route.ts
Comment thread apps/sim/blocks/blocks/sailpoint.ts
Comment thread apps/sim/blocks/blocks/sailpoint.ts
…r, fix review-item filters

- Require aggregationsDsl on search_aggregate (contract + tool) and send aggregationType: DSL so
  /search/aggregate always receives a valid aggregations definition.
- Coerce searchAfter cursor elements to strings instead of dropping non-string values, and expose
  a searchAfter input on the block so deep pagination past 10k is reachable from the UI.
- Correct the certification review-item filter fields (entitlements / access profiles / roles) to
  comma-separated ID filters instead of true/false placeholders.
@mzxchandra

Copy link
Copy Markdown
Contributor Author

@greptile

@mzxchandra

Copy link
Copy Markdown
Contributor Author

@cursor review

# Conflicts:
#	scripts/check-api-validation-contracts.ts

@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 52d5372. Configure here.

Comment thread apps/sim/app/api/tools/sailpoint/client.ts
Include a hash of client_secret in the token cache key so a caller with a matching
tenant/clientId but the wrong secret cannot reuse another principal's cached bearer token -
a mismatched secret now misses the cache and fails the token exchange. Regression test added.
@mzxchandra

Copy link
Copy Markdown
Contributor Author

@greptile

@mzxchandra

Copy link
Copy Markdown
Contributor Author

@cursor review

Comment thread apps/sim/app/api/tools/sailpoint/client.ts
Comment thread apps/sim/app/api/tools/sailpoint/query/route.ts
…missing header

- Cap the token cache expiry buffer at the smaller of 60s and 10% of the lifetime so a short-lived
  token still caches instead of expiring immediately.
- Return an error for search_count when SailPoint provides no X-Total-Count (and no numeric body)
  instead of reporting a misleading total of 0. Tests added for both count paths.
@mzxchandra

Copy link
Copy Markdown
Contributor Author

@greptile

@mzxchandra

Copy link
Copy Markdown
Contributor 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 531ae54. Configure here.

@mzxchandra

Copy link
Copy Markdown
Contributor Author

@waleedlatif1

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